Search code examples
pythonpython-decorators

Access parameters in parameterized decorators


I'm trying to understand python decorator from this doc. And was writing my own decorator required to handle API Exceptions.

But in my decorator, I'm not getting how to access arguments (method, api, and data) inside my Custom Decorator.

I know, I haven't passed method anywhere because I'm not getting where to pass so that I can accept it in my decorator.

Here is my decorator:

import requests as r
import json
def handle_api_exceptions(http_api_call):
    """ """

    def wrapper(*args, **kwargs):
        """ """
        response = {}
        try:
            response = http_api_call(*args, **kwargs)
            if response.ok:
                result =  response.json()
                if result.get('code', 200) in INVALID_STATUS_CODES: #INVALID_STATUS_CODES = [1801, 1803, 1806,... ]
                    response = {"data":{}, "status":False}
                else:
                    return result
            else:
                capture_failed_requests(method, api, data, response, 
                                    error=None, reason=response.reason)
                return {}
        except r.exceptions.ConnectionError as e:
            capture_failed_requests(method, api, data, response, error=e,
                                         reason="Connection Aborted")
            return {}
        except json.decoder.JSONDecodeError as e:
            capture_failed_requests(method, api, data, response, error=e,
                                         reason="Invalid Response")
            return {}
        except r.exceptions.ReadTimeout as e:
            capture_failed_requests(method, api, data, response, error=e,
                                         reason="Request Timed Out")
            return {} 
        except Exception as e:
            capture_failed_requests(method, api, data, response, error=e,
                                         reason="Internal Server Error")
        return {}
    return wrapper

Custom GET, POST API requests:

@handle_api_exceptions
def get(self, api, data, token=None):
    """ """
    if token:data.update(self.get_token(token))
    response = r.get(api, data, verify=self.config.SSL_VERIFY,
                      timeout=self.config.REQUEST_TIMEOUT)
    return response

@handle_api_exceptions
def post(self, api, data, token=None):
    """ """
    if token:
        data.update(self.get_secret_token(token))
    response = r.post(api, data, verify=self.config.SSL_VERIFY,
                      timeout=self.config.REQUEST_TIMEOUT)
    return response        


def post_call(self):
    """ """
    api = "http://192.168.0.24/api/v1/reset/"
    data = {"k1":[], "k2":{}, "id":123456} #-- Some Key val
    return self.post(api, data, token="SOME_SECRET_TOKEN")

Query is : How to pass method, api, and data in capture_failed_requests()?


Solution

  • data and api are in the args inside wrapper. method you will have to provide separately, e.g. by parameterising the decorator (see e.g. python decorators with parameters). You could therefore do this as follows:

    def handle_api_exceptions(method):
        def decorator(http_api_call): 
            def wrapper(api, data, *args, **kwargs):
                ...
    

    and your decorating would become e.g.

    @handle_api_exceptions(method='GET')
    def get(self, api, data, token=None):
        ...
    

    Alternatively, you could use method = http_api_call.__name__ (which would give you 'get'), and avoid the extra layer of nesting and duplication of the method name.


    Note that I have removed the empty docstrings - either write an actual docstring (I like Google-style, but YMMV) or don't have one at all. If your linting rules require docstrings, that's because whoever set it up wants you to write useful ones, not just cheat on it.