[Fixed]-How do I access the request from the request_finished signal callback?

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.

👤dalore

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.

Leave a comment