decorator inside class not getting values
Question: I have a decorator that validates a json response that I obtain using
requests. I also wrapped some requests logic into a class which accepts a
schema that the decorator needs to validate. I tried to use the decorator on
the get function of the class but i get a type error each way I try to do it:

    
    
    TypeError: get() takes exactly 1 argument (0 given)
    

I know that if you use a decorator on a class method you need to set self=None
in the decorator.

Here is the code:

    
    
    schema = {'pid': int,
              'description': str}
    
    def validator(schema):
        def _validator(f):
            @wraps(f)
            def wrapped(self=None):
                print f
                check = all([isinstance(v, schema[k]) for k,v in f().iteritems() if v])
                return f() if check else None
            return wrapped
        return _validator
    
    
    class Data(object):
        ''' Base request wrapper to use for all apis
        accepts a validator schema to check the type of response values. '''
    
        def __init__(self, base_url, schema=None, debug=False):
            self.base_url = base_url
            self.schema = schema
    
        def __getattr__(self,key):
            new_base = self.append_to_url(self.base_url, key)
            return self.__class__(base_url=new_base)
    
        def __getitem__(self,key):
            return self.__getattr__(key)
    
        # def __call__(self, **kwargs):
        #     self.base_url = self.base_url[:-1]
        #     return self.get(self.base_url, **kwargs)
    
        def append_to_url(self, base_url, param):
            return '{}{}/'.format(base_url, param)
    
        @validator(schema)
        def get(self, **kwargs):
            try:
                r = requests.get(self.base_url[:-1], **kwargs)
                r.raise_for_status()
                return r.json()
    
            except requests.exceptions.ConnectionError as e:
                raise errors.ApiError(e)
    
            except requests.exceptions.HTTPError as e:
                raise errors.ApiError(e)
    
    product_details = Data('my_api_url', schema).shoes['10'].get()
    

I think this happens because in my validator I initialize f() which is the get
function is expecting self. I tried to pass self to f() in the decorator but
that also yields the same error. I even tried to have the get function
initialize on the __call__ method but that yields the get function is
expecting 1 arg and 2 were given.

There must be a more efficient or pythonic way to achieve this. I know there
is a library called voluptuous that does tons of validations but I wanted to
try something simple as a learning exercise.


Answer: It's not the `wrapped` function call that's failing, it's the `get` call.
Everywhere you call the `get` you call it like `f()`, i.e. without any
arguments.

A working example, properly passing `self` argument to the wrapped function:

    
    
    import functools
    
    def validator(schema):
        def _validator(f):
            @functools.wraps(f)
            def wrapper(self):
                # pass self to the wrapped function
                result = f(self)
                return 'wrapped {} {}'.format(schema, result)
            return wrapper
        return _validator
    
    class Data(object):
        @validator('test')
        def get(self):
            return 'data'
    
    print(Data().get())
    # wrapped test data
    

While you can use `self` or other such specific arguments directly, using
`*args` and `**kwargs` instead makes the decorator more flexible, e.g. enables
it to be used for non-bound functions too:

    
    
    import functools
    
    def validator(schema):
        def _validator(f):
            @functools.wraps(f)
            def wrapped(*args, **kwargs):
                # pass aribitrary args to the wrapped function
                # for greater flexibility
                result = f(*args, **kwargs)
                return 'wrapped {} {}'.format(schema, result)
            return wrapped
        return _validator
    
    class Data(object):
        @validator('test')
        def get(self):
            return 'data'
    
    @validator('test2')
    def get():
        return 'data2'
    
    print(Data().get())
    # wrapped test data
    print(get())
    # wrapped test2 data2
    

