3

This question is a follow-up to this brilliant answer on decorators in Python :

I use the given "snippet to make any decorator accept generically any argument".

Then I have this (here simplified) decorator:

@decorator_with_args
def has_permission_from_kwarg(func, *args, **kwargs):
    """Decorator to check simple access/view rights by the kwarg."""
    def wrapper(*args_1, **kwargs_1):
        if 'kwarg' in kwargs_1:
            kwarg = kwargs_1['kwarg']
        else:
            raise HTTP403Error()

        return func(*args_1, **kwargs_1)

    return wrapper
  1. Working with this decorator, no problem it does the job very well.
  2. Testing a similar decorator that does not require absolutely the kwargs, same outcome.
  3. But testing this decorator with the following mock does not work:

    def test_can_access_kwarg(self):
        """Test simple permission decorator."""
        func = Mock(return_value='OK')
        decorated_func = has_permission_from_slug()(func(kwarg=self.kwarg))
        # It will raise at the following line, whereas the kwarg is provided...
        response = decorated_func()
        self.assertTrue(func.called)
        self.assertEqual(response, 'OK')
    

It returns me the exception I am raising when I do not have a 'kwarg' keyword-argument...

Does anyone has a clue how to test (by mocking would be preferable) such a decorator decorated by another decorator that requires the access to one of the keyword arguments passed to the function ?

Community
  • 1
  • 1
Rmatt
  • 1,287
  • 1
  • 16
  • 30

1 Answers1

2
decorated_func = has_permission_from_slug()(func(kwarg=self.kwarg))

This will:

  1. Execute func(kwarg=self.kwarg)
  2. Generate an instance of the actual decorator.
  3. Call the decorator on the result of the func-call (i.e. the result).
  4. Return the wrapper which will then later try to call the results from step 3 (which would fail).

    response = decorated_func()

This will then call the returned wrapper with no arguments, so **kwargs_1 is empty. Also, if you wrapper wouldn’t raise an exception in this case, the subsequent call of func(..) would throw an exception because the return value of func (the original one) is probably not callable (see above).

What you probably want to do instead, or at least what your decorator supports, is this:

decorated_func = has_permission_from_kwarg()(func)
response = decorated_func(kwarg=self.kwarg)

Or, if you want to pass your kwarg in the decorator like this:

decorated_func = has_permission_from_kwarg(kwarg=self.kwarg)(func)
response = decorated_func()

Then you need to adjust or decorator to actually use kwargs, and not kwargs_1 in the check (the latter are the arguments to the decorated function).


I’m testing your original decorator definition (with no changes) and the decorator_with_args as defined in the linked answer with the following code:

class HTTP403Error (Exception):
    pass

def func (*args, **kwargs):
    print('func {}; {}'.format(args, kwargs))

my_kwarg = 'foo'
decorated_func = has_permission_from_kwarg()(func)
decorated_func(kwarg=my_kwarg)
decorated_func(not_kwarg=my_kwarg)

As expected, I get func (); {'kwarg': 'foo'} printed for the first call, and a HTTP403 exception for the second.

poke
  • 369,085
  • 72
  • 557
  • 602
  • Hi poke, thanks for your answer ! I should have said it before, but for sure I tried also what you proposed. Unfortunately: The first sample fails, since `response = decorated_func(kwarg=self.kwarg)` returns "TypeError: 'str' object is not callable" The second works, but does not fit to my original scope, since I want to test a decorator that works using kwargs_1 in my code. – Rmatt Nov 13 '12 at 11:55
  • Had a small mistake in the last code example, now that works too. The first solution worked before though. – poke Nov 13 '12 at 12:02
  • Fine, but the second example still does not fit to my purpose. The first one still fails. It does the job to go beyond the kwarg check, but fails with the TypeError at line `return func(*args_1, **kwargs_1)` – Rmatt Nov 13 '12 at 12:06
  • My mistake, actually after having tested the call by giving the parameter func(kwarg=self.kwarg), I had let it like `decorated_func = has_permission_from_kwarg()(func())`, as a function called !!! Sorry for this. And at the end, also using the Mock, the first code snippet you had posted works. Thanks for your help poke ! – Rmatt Nov 13 '12 at 12:34