Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I access the request from the request_finished signal callback?

Tags:

django

How do I obtain and use the HttpRequest using the request_finished signal?

Interested in extracting the url for logging purposes.

Current code looks something like this:

import logging

def write_to_file(sender, **kwargs):
    logging.debug(type(sender))
    logging.debug(dir(sender))

from django.core.signals import request_finished
request_finished.connect(write_to_file)

generates this

2010-03-03 13:18:44,602 DEBUG <type 'type'>
2010-03-03 13:18:44,602 DEBUG ['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', '_get_traceback', 'apply_response_fixes', 'get_response', 'handle_uncaught_exception', 'initLock', 'load_middleware', 'request_class', 'response_fixes']
like image 207
John Mee Avatar asked Mar 03 '10 02:03

John Mee


1 Answers

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.

like image 174
dalore Avatar answered Sep 28 '22 08:09

dalore