Ever wanted to add the time taken for a response to your API, so the client knows how long the server took to send a response?
I first noticed this cool feature on AWX, a management platform for ansible playbooks.
Their response headers looked like this:
So I checked out their source code and copied how they did it:
In generics.py
the package extends the base DRF ApiView
class and adds a bit of stuff, the important stuff for us is:
def finalize_response(self, request, response, *args, **kwargs):
...
response = super().finalize_response(request, response, *args, **kwargs)
time_started = getattr(self, 'time_started', None)
response['X-API-Node'] = settings.CLUSTER_HOST_ID
...
def initialize_request(self, request, *args, **kwargs):
...
self.time_started = time.time()
...
So I created a class extending for ApiView
:
class APIView(views.APIView):
'''
Add timing to the base APIView class
'''
def initialize_request(self, request, *args, **kwargs):
self.time_started = time.time()
return super().initialize_request(request, *args, **kwargs)
def finalize_response(self, request, response, *args, **kwargs):
response = super().finalize_response(request, response, *args, **kwargs)
time_started = getattr(self, 'time_started', None)
if time_started:
time_elapsed = time.time() - self.time_started
response['X-API-Time'] = '%0.3fs' % time_elapsed
return response
Now all I needed to do was extend from my_package.ApiView
instead of restframework.views.ApiView
and I would get an X-API-Time
header.
It worked well for a while, but then I needed to use more Generic Class Based Views (Which extend from ApiView)…check out CDRF. So I could just add these methods, to the extended versions of restframework.generics.ListCreateView
etc.
But that just doesn’t feel right. Simply because I can see I am repeating myself multiple times. I just be able to define this functionality once and have all similar objects (those that extend from ApiView) have that functionality…
Using a Mixin to add the Time taken to Respond
I just created a mixin, by extracting those functions into a single class that inherits from nothing:
class TimedAPIMixin:
def initialize_request(self, request, *args, **kwargs):
self.time_started = time.time()
return super().initialize_request(request, *args, **kwargs)
def finalize_response(self, request, response, *args, **kwargs):
response = super().finalize_response(request, response, *args, **kwargs)
time_started = getattr(self, 'time_started', None)
if time_started:
time_elapsed = time.time() - self.time_started
response['X-API-Time'] = '%0.3fs' % time_elapsed
return response
Using them in the other views:
from rest_framework import views
from rest_framework import generics
class APIView(TimedAPIMixin, views.APIView):
pass
class ListCreateAPIView(TimedAPIMixin, generics.ListCreateAPIView):
pass
class ListAPIView(TimedAPIMixin, generics.ListAPIView):
pass
It only works if the mixin is the first thing it inherits from, otherwise the first class will take preference.