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 + bWhat 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 / bargs + 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