4👍
Django documentation for request_finished state they provide the class not the instance (not sure why, it would have been more useful to provide the instance). https://docs.djangoproject.com/en/1.9/ref/signals/#request-finished
So the signal just let’s you know a request has finished, but not which request or any detail of it. You have 2 options to get the request. One, which has been mentioned, is to store the request into thread local storage in a middleware.
Here is an example which stores the request. But you can use it store up functions that will be called at the end.
import collections
import threading
import structlog
from django.utils.cache import patch_vary_headers
logger = structlog.get_logger()
thread_locals = threading.local()
def get_current_request():
"""
This will return the current request object
but if the response has been returned the request
object will be cleaned up
"""
return getattr(thread_locals, 'request', None)
def request_queue(func, func_id=None, *args, **kwargs):
"""
Helper function to queue up a function
and arguments to be run at the end of the request
if no request, will run straight away
Usage:
request_queue(function_to_run, args=(args1, args2), kwargs={'key':'value'})
"""
request = get_current_request()
if not request:
# run the func
func(*args, **kwargs)
return
# else
# use the supplied id if given
if not func_id:
# otherwise use the memory address
func_id = id(func)
# store the func and arguments as a tuple under the func id
request.queue[func_id] = (func, args, kwargs)
class RequestQueueMiddleware(object):
"""
Use this middleware to get access to the request object
and to use it to queue functions to run
"""
def process_request(self, request):
thread_locals.request = request
# each request gets a new queue
request.queue = collections.OrderedDict()
def process_exception(self, request, exception):
self.process_queue(request)
self.cleanup()
def process_response(self, request, response):
self.process_queue(request)
self.cleanup()
return response
def cleanup(self):
try:
del thread_locals.request
except AttributeError:
pass
def process_queue(self, request):
if not request:
request = get_current_request()
if request and hasattr(request, 'queue'):
for (func, args, kwargs) in getattr(request, 'queue', {}).itervalues():
func(*args, **kwargs)
del request.queue
The function get_current_request
can be imported and used in any other method when you need access to the current request.
The function request_queue
allows you to queue up a function and arguments to be executed. A feature is that you could queue up an expensive function many times and it will only be executed once.
So in your request_finished
handler you can call get_current_request
to get the current request. But in the above implementation you will need to remove the cleanup code. I don’t know if keeping the request object on thread local storage will leak.
The other option which doesn’t require any middleware is to inspect the stack frames until you find the request.
def get_request():
"""Walk up the stack, return the nearest first argument named "request"."""
frame = None
try:
for f in inspect.stack()[1:]:
frame = f[0]
code = frame.f_code
if code.co_varnames and code.co_varnames[0] == "request":
return frame.f_locals['request']
finally:
del frame
If you have any other variable called request it will break. Could be adapted to check type as well.
3👍
I think I’ve found the simplest solution.
Looking through the official Django rep, I’ve found the only use of requests_finished
here.
Now I can easily override this method in the entry point in manage.py
:
from django.http.response import HttpResponseBase
class OverriddenHttpResponseBase:
@staticmethod
def close(self):
for closable in self._closable_objects:
try:
closable.close()
except Exception:
pass
self.closed = True
# here you can access your request using self._closable_objects
# you can either send it to request_finished
signals.request_finished.send(sender=<whatever data you want>)
# or code your stuff here without using request_finished at all
if __name__ == '__main__':
HttpResponseBase.close = OverriddenHttpResponseBase.close
-3👍
Try
sender.request_class.get_full_path()
or
sender.request_class._get_request()
Or, if you want to try doing this with middleware, as mountainswhim suggested, here’s a snippet that demos request timing using middleware.