Python Decorators Made Easy, vol2
Python decorators is one of these topics that you certainly know how to use but it may be not so obvious how to build one. In this article I will try to explain what a decorator is, how to use and build one. At the end of the article we will go over decorator with arguments and how they differ from standard ones.
Problem introduction
A decorator is a design pattern in Python which allows us to add more functionalities to functions and methods without changing the entire code base. What does that mean?
Suppose you have the following function, that returns some content to the user requesting it. Such function are commonly found in web frameworks like Django and Flask.
def main_view(request):
return "Some content pulled from database"Function looks a bit abstract written in this way but bear with me, we we will get there. When a user hits a particular URL, that URL is mapped to a function aka view that returns some content (most of the time, that content is pulled from some sort of a database). Since we don’t have a running application and cannot make a “real” request, we have to build one. Keep in mind that we are not going to build a full HTTP request but a mock one that has only two fields — user and email. Here is a look at our request.
request = {
'user' : 'Jordan',
'email' : '[email protected]',
}When our view gets hit by a random user regardless of his status, some content from the database will be returned to him. What we want to do in this scenario is build a functionality that allows only whitelisted domains to be able to retrieve that content. Sounds easy enough right? Lets first build our whitelist.
# whitelisted domains
whitelist = ['example.com', 'example.net', 'example.org']Once we have a whitelist in place, lets check if the users email is whitelisted and if it does we will return the content to him. Otherwise we will raise a permission error saying that the domain is not whielisted.
def main_view(request):
domain = request.get('email').split('@')[1]
if domain in whitelist:
return "Some content pulled from database"
raise PermissionError('Your domain is blacklisted')Since our user’s email is registered on example.com domain (which is whitelisted), the content is returned to him and if that is not the case an error raised.
>>> python .\main.py
'Some content pulled from database'>>> python .\main.py
Traceback (most recent call last):
File "main.py", line 32, in <module>
print(main_view(request))
File "main.py", line 30, in main_view
raise PermissionError('Your domain is blacklisted')
PermissionError: Your domain is blacklistedEverything seems to be working as expected with one view. Imagine now that you have 20–30 or even 100 views in our application. Should you paste that snippet of code in each of them? Nope of course. Python’s DRY principle encourages you not to repeat yourself and that is what we are about to do.
Decorators in actions
We saw that we are able to implement new functionalities without decorators but as I already mentioned if you need to re-use that snipped of code in other function it because just one hell of copy and paste. The code base starts to look ugly, the code is prone to mistakes during that process, an overall hell in my opinion. Let’s modify our code and transform it to a decorator.
def is_auth(func):
def wrapper(inner_request):
domain = inner_request.get('email').split('@')[1]
if domain in whitelist:
return func(inner_request)
raise PermissionError('Your domain is blacklisted')
return wrapperOur view is exactly as we have written it in the beginning, but we are adding the decorator on top of it to well… decorate the function
@is_auth
def main_view(request):
return "Some content pulled from database"When we execute our main_view function, we are getting the same result as before but without changing one line of our initial function.
>>> main_view(request)
'Some content pulled from database'How did we get here you may ask. Well, that is the beauty of python decorators — its simple, clean and very clever design pattern. Lets dive in a bit more into it.
First we got a function called is_auth which is our main decorator. Since decorators are wrappers of another function we pass a function as argument to that decorator. The main_view function arguments are passed to a wrapper function inside the decorator. At that point we simple extract the domain from the passed dictionary (request argument) and check if that domain is whitelisted. If it does we simply return the content of main_view function. Otherwise we raise a PermissionError to indicated that user’s domain is blacklisted.
In the beginning I said that we are going to cover more advanced topic such as passing arguments in the decorator. Going back to our example, instead of calling the whitelist list inside our wrapper function, we could pass it as an argument through the decorator function. Our decorated function is going to look like this.
def is_whitelisted(inner_list):
def decorator(func):
def wrapper(inner_request):
domain = inner_request.get('email').split('@')[1]
if domain in inner_list:
return func(inner_request)
raise PermissionError('Your domain is blacklisted')
return wrapper
return decorator@is_whitelisted(whitelist)
def main_view(request):
return "Some content pulled from database"Passing arguments through decorator consist of wrapping the decorator in yet another function. The difference is that in the first decorating function, we are passing decorator’s arguments, in the second decorating function we are passing the decorated function and in the third decorating function (wrapper) we are passing the arguments of decorated function.
When we execute the code we get exactly the same output.
python .\main.py
'Some content pulled from database'If another request comes form a user that belongs to a domain that is not whitelisted we would get an error.
request = {
'user' : 'John',
'email' : '[email protected]'
}>>> python .\main.pyTraceback (most recent call last):
File "main.py", line 24, in <module>
print(main_view(request))
File "main.py", line 16, in wrapper
raise PermissionError('Your domain is blacklisted')
PermissionError: Your domain is blacklistedConclusion
In this article we have go through what python decorators are and how we can use them in a certain project. The topic may seem a bit more advanced and does not cover all aspects of decorator function. If you want to read a bit more about it, please check out another article of mine where that topic is covered. As always I hope that this has been informative to you and I would like to thank you for reading.
Happy decorating!





