Decorators in Python are equivalent to Custom Attributes in C#. Let’s check them out.
Decorators in Python are a way to modify or enhance functions or classes without directly changing their source code.
They use the concept of closures and higher-order functions to wrap the original function or class, adding functionality before or after the wrapped code executes.
It is similar to Router guards with Vue Router or custom attributes or middleware in C#.
Let’s break down step by step what you can code on and how it works .
Basic Example
Let’s start with the basics. You define a Python decorator as follows:
1
2
3
4
5
6
7
defmy_decorator(original_function):defwrapper_function(*args,**kwargs):# Code to execute before the `original_function`result=original_function(*args,**kwargs)# Code to execute after the `original_function`returnresultreturnwrapper_function
Now, let’s look at specific use cases.
Decorator without input from the caller
This is the simplest form of a decorator. It doesn’t have any arguments other than the function it’s decorating. In between, it prints out In decorator before calling {function name} and Function {function name} called. Completing decorator logic....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
deflog_function_call(func):defwrapper(*args,**kwargs):print(f"In decorator before calling <{func.__name__}>")result=func(*args,**kwargs)print(f"Function <{func.__name__}> called. Completing decorator logic...")returnresultreturnwrapper@log_function_calldefgreet(name):print(f"Hello, {name}!")greet("Alice")# Ouputs:# "In decorator before calling <greet># "Function <greet> called. Completing decorator logic...
In this example, the log_function_call decorator adds logging before and after the function call without needing any input from the caller.
Decorator with input from the caller
When you need to pass arguments to the decorator itself, you need to add another layer of functions.
Ordering your decorators is crucial and will depend on your business logic. Take time to identify the proper order.
Practical Example
Let’s say we have this endpoint intercepting an incoming call webhook:
1
2
3
4
5
6
7
8
9
10
11
fromtwilio.twiml.voice_responseimportVoiceResponsefromapp.modules.callimportcallfromapp.commons.decoratorsimportneed_xml_output,log_headers,validate_twilio_request@call.route('/incoming',methods=['POST'])@validate_twilio_request@log_headers@need_xml_output()defredirecting_call()->VoiceResponse:# find whom to redirect the call to...
The decorators we want to look at are log_headers and validate_twilio_request.
deflog_headers(f):@wraps(f)defdecorated_function(*args,**kwargs):try:current_app.logger.info(f"Request Headers: {dict(request.headers)}")returnf(*args,**kwargs)exceptExceptionase:current_app.logger.error(f"Exception in decorator <log_headers>: {e}")returndecorated_functiondefvalidate_twilio_request(f):print("Decorator validate_twilio_request called")@wraps(f)defdecorated_function(*args,**kwargs):try:# List all the data that make a signaturecurrent_app.logger.debug("Inside decorated function")auth_token=current_app.config['TWILIO_AUTH_TOKEN']url=_get_url()post_data=request.form# The X-Twilio-Signature headertwilio_signature=request.headers.get('X-Twilio-Signature','')current_app.logger.debug(f"AUTH_TOKEN={auth_token}")current_app.logger.debug(f"url={url}")current_app.logger.debug(f"post_data={post_data}")current_app.logger.debug(f"twilio_signature={twilio_signature}")# Create a RequestValidator objectvalidator=RequestValidator(auth_token)# Validate the requestifnotvalidator.validate(url,post_data,twilio_signature):# If the request is not valid, return a 403 Forbidden errorabort(403)# If the request is valid, call the decorated functionreturnf(*args,**kwargs)exceptExceptionase:current_app.logger.error(f"Exception in decorator <validate_twilio_request>: {e}")abort(500)returndecorated_function
Now, a problem may arise with the log_headers decorator that fails to execute and trace headers. Why?
In Python, decorators are applied from bottom to top. In our use case, we’d have an equivalent to this:
1
2
# ORDER OF EXECUTIONresult=need_xml_output(log_headers(validate_twilio_request(call.route(args)))(redirecting_call))
The decorator validate_twilio_request fails if the signature in the X-Twilio-Signature header is incorrect and therefore the log_headers won’t execute at all because of the raised error: