Search code examples
pythontestingdecoratorpython-decoratorsintrospection

Accessing original decorated function for test purposes


I'm using a decorator(@render_to from the django_annoying package) in a view function.

But the thing is that I wanted to get the original dict that is returned by the view function for test purposes, instead of the HttpResponse object that the decorator returns.

The decorator uses @wraps (from functools).

If there is no way to access this, then do you have any idea of how to test this?


Solution

  • The wrapped function will be available as a function closure cell. Which cell exactly depends on how many closure variables there are.

    For a simple wrapper where the only closure variable is the function-to-wrap, it'll be the first one:

    wrapped = decorated.func_closure[0].cell_contents
    

    but you may have to inspect all func_closure values.

    Demo using the functools.wraps() example decorator:

    >>> from functools import wraps
    >>> def my_decorator(f):
    ...     @wraps(f)
    ...     def wrapper(*args, **kwds):
    ...         print 'Calling decorated function'
    ...         return f(*args, **kwds)
    ...     return wrapper
    ... 
    >>> @my_decorator
    ... def example():
    ...     """Docstring"""
    ...     print 'Called example function'
    ... 
    >>> example
    <function example at 0x107ddfaa0>
    >>> example.func_closure
    (<cell at 0x107de3d70: function object at 0x107dc3b18>,)
    >>> example.func_closure[0].cell_contents
    <function example at 0x107dc3b18>
    >>> example()
    Calling decorated function
    Called example function
    >>> example.func_closure[0].cell_contents()
    Called example function
    

    Looking at the source code for @render_to you don't have to worry about this though; the wrapped function will be stored in the first closure slot, guaranteed.

    If this was Python 3 instead, the wrapped function can be accessed with the __wrapped__ attribute instead:

    >>> example.__wrapped__
    <function example at 0x103329050>
    

    And if you had access to the decorator code itself, you can easily add that same reference in Python 2 code too:

    def my_decorator(f):
        @wraps(f)
        def wrapper(*args, **kwds):
            # implementation
    
        wrapper.__wrapped__ = f
        return wrapper
    

    making introspection just that little bit easier.