Decorators

What is it?

  • "Functional wrapper"
  • Something that allows us to run some common code before and/or after a method is executed
def decorator(some_function):
    def wrapper(*args, *kwargs):
        # do some stuff before some_function is called
        some_function()
        # do some stuff after some_function is called
    return wrapper
@decorator
def my_awesome_function():
    print "hello world"
def validate_ints(fn):
    def wrapper(a, b, *args, *kwargs):
        try:
            a = int(a)
            b = int(b)
        except ValueError, exc:
            logger.info("Both parameters must be an integer")
            raise exc

        return some_function(a, b)
    return wrapper
@validate_ints
def adding(a, b):
    return a + b

What if we want our decorators to have their own parameters?

def validate_numbers(num_type):
    def outter_wrapper(fn):
        def inner_wrapper(a, b, *args, *kwargs):
            try:
                a = num_type(a)
                b = num_type(b)
            except ValueError, exc:
                logger.error("Both parameters must be of type {}".format(
                    type(num_type)))
                raise exc

            return some_function(a, b)
        return inner_wrapper
    return outter_wrapper
@validate_numbers(int)
def adding(a, b):
    return a + b

@validate_numbers(float)
def division(a, b):
    return a / b

args + kwargs still apply

def decorator(some_function, *args, **kwargs):
    def wrapper(*args, *kwargs):
        # do some stuff before some_function is called
        some_function()
        # do some stuff after some_function is called
    return wrapper
def decorator_with_parms(param1, *args, **kwargs):
    def outter_wrapper(fn):
        def inner_wrapper(arg1, *args, *kwargs):
            # do some stuff before some_function is called
            some_function(arg1, *args, **kwargs)
            # do some stuff after some_function is called
        return inner_wrapper
    return outter_wrapper

"But my imports explicitly reference my function and not the decorator...how does this work!?!"

  • When the method is imported, the wrapping occurs
  • When the method is called, the wrapped method is what is actually getting executed.

So what's so special about writing tests against decorated methods?

What happens if we would like to write unit tests for a method we have wrapped with a decorator?

  • Typically we just import the method into our tests.py
  • Call it
  • Run assertions against results
def decorator(some_function, *args, **kwargs):
    def wrapper(*args, *kwargs):
        if not check_authentication():
            raise NotAuthenticatedError

        return some_function()
    return wrapper
@decorator
def hello_world():
    return "Hello world!"
from some.place import hello_world

class TestStuff(TestCase):
    def test_results(self):
        self.assertEqual("Hello world!", hello_world())

This test will fail and raise a "NotAuthenticatedError" exception.

Solution

Before importing the module you want to test that is wrapped with a decorator:

  • import the decorator
  • mock it
  • import the module which contains the method you want to test - you'll call the method from that module reference.

The secret to testing/mocking methods that have been decorated lies in understanding:

  • When are things being imported?
  • What is that I need to mock?

Helpful resources about decorators

Python Decorators

By Juan

Python Decorators

What are they? How do we test our code when it's wrapped in a decorator?

  • 196