How to properly type decorator and decorated function?

445 Views Asked by At

I have a decorator which should catch errors from the request:

def catch_request_errors(func):
    @functools.wraps(func)
    def inner_function(*args, **kwargs):  # -> Optional[Dict]
        try:
            response = func(*args, **kwargs)
        except Exception as e:
            capture_message(str(e), level='error')
            errors = {'error': str(e)}
            return errors

        if not response.ok:
            capture_message(response.text, level='error')
            errors = {'error': str(response.text)}
            return errors

        return None

    return inner_function

The decorator itself returns Optional[Dict] - when error occurs should return {'error': 'error_msg_here'} or None if there are no errors.

Here is my decorated function, which actually returns Response without decorator, but with decorator it returns Optional[Dict]:

@catch_request_errors
def make_request(self, url: str, data: Dict): # -> Optional[Dict]
    return requests.post(url, data)  # but there is actually -> Response

I had to "hard type" like that to bamboozle my IDE, but how should I do it properly?

request_errors = self.make_request(settings.DOCUMENT_GENERATOR_URL, data)  # type: Optional[Dict]
2

There are 2 best solutions below

0
On BEST ANSWER

Somewhat similar solution like in subprocess.Popen.comunicate

def catch_request_errors(func):
    @functools.wraps(func)
    def inner_function(*args, **kwargs):
        response = None
        errors = None
        try:
            response = func(*args, **kwargs)
        except Exception as e:
            capture_message(str(e), level='error')
            errors = {'error': str(e)}

        if response is not None and not response.ok:
            capture_message(response.text, level='error')
            errors = {'error': str(response.text)}

        return response, errors

    return inner_function
@catch_request_errors
def make_request(self, url, data):
    return requests.post(url, data)
response, errors = make_request(URL, DATA)
if errors is not None:
    # handle errors
else:
    # code ...
0
On

In case someone is having similar problem, I am posting my solution with types, thanks to @woblob

Decorator:

def catch_request_errors(func: Callable[[str, Dict], Response]) -> Callable[[Tuple[Any], Dict[str, Any]],
                                                                            Tuple[Response, Optional[Dict]]]:
    @functools.wraps(func)
    def inner_function(*args, **kwargs) -> Tuple[Response, Optional[Dict]]:
        response = None
        errors = None
        try:
            response = func(*args, **kwargs)
        except Exception as e:
            capture_message(str(e), level='error')
            errors = {'error': str(e)}

        if response and not response.ok:
            capture_message(response.text, level='error')
            errors = {'error': str(response.text)}

        return response, errors

    return inner_function
@catch_request_errors
def make_request(self, url: str, data: Dict) -> Response:
    return requests.post(url, data)
response, errors = self.make_request(settings.DOCUMENT_GENERATOR_URL, data)