Global Django requests

Saturday 28 August 2010This is more than 14 years old. Be careful.

As my Django sites get larger and larger, there inevitably comes a point where I want access to the current request from deep inside some function that doesn’t have the request object. The latest reason was that I wanted a model class helper to have access to the session so it could access some debug flags.

The first option for making this work was to pass the request object through two or three layers of code that otherwise didn’t need a request. This meant changing the signature and callers of the two or three functions, which felt messy. The prospect of it made me unhappy.

The second option was to create a way for any code invoked as part of a request to get access to the request, even if it weren’t passed to it explicitly. I think of this as a global request object.

Of course, there isn’t really a single global request object, since there can be many threads, each of which is handling a separate request. We need a way to associate the request with a thread, and then to get the request for our thread. This middleware does the job nicely:

from django.utils.thread_support import currentThread
_requests = {}

def get_request():
    return _requests[currentThread()]

class GlobalRequestMiddleware(object):
    def process_request(self, request):
        _requests[currentThread()] = request

I didn’t write this code, I got it from here and here, not sure who wrote it first. I’m also not sure why Django provides its own currentThread function when the Python standard module threading provides thread locals to achieve the same effect.

There’s another way to get a global request object, but you probably won’t like it:

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

This function looks at the stack frames of all of its callers, looking for one with a first argument named “request”. If found, it returns the value. The problem with this function is that it can be fooled, and will return the “request” it finds regardless of its type.

In its defense: GlobalRequestMiddleware requires more machinery, and adds a tiny tax to every request. If your need for the global request object is rare, the frame-based get_request() may be better for you. Also, it’s a (nasty) technique that can be adapted to other situations.

Comments

[gravatar]
Any reason to not use threading.local instead of a normal dict? I've been eyeing doing the same so I can attach some request parameters to logs automatically.
[gravatar]
@Noah, I don't know why Django uses the technique it does. In a few places in the Django code, they use this method of keying a dict with django.utils.thread_support.currentThread. If you look in thread_support.py, it's just threading.currentThread with a catch of ImportError. I don't know in what environment you wouldn't be able to import threading.
[gravatar]
Hi Ned. How are you doing? My only comment is that the stack search method becomes much less of a hack if instead of relying on the name of the variable, you actually access the variable to test if it is a request. For example, in certain circumstances you're doing this when you know that the request object you're looking for will have an attribute of 'foo', and want to test if 'foo' is (some value, true, whatever) -- then, instead of relying on the name, just say, does it have a foo attribute? If so, it's what we're looking for.

The caveat about the 'certain circumstances' is because you might be looking for something on the request when you're not sure whether it will be there at all, and so testing some random stack variable for that thing will simply give you a false negative. In that case, just check the stack variables for something requests always have but hardly anything else ever would, such as META.

-Eddie
[gravatar]
Our threading module is a light wrapper and dates back to when we supported Python 2.3 with its not-quite-complete support for the stuff we needed; these days it can probably be pulled (and I'll see if there's a ticket on that).
[gravatar]
Ferran Pegueroles 1:52 AM on 30 Aug 2010
I think there is a memory leak in dictionary solution, as the dictinary is never emptied.
I think you should implement process_response() in the nmiddleware to empty delete the key from the dictionary.
This is not required if you use the threading.local as Noah said.
[gravatar]
I am currently engaged in a running argument on how to manage internal communication in large event-centric applications. My position, with apologies to Churchill, is that having a global dictionary through which arbitrary messages can be passed and state tracked is a terrible approach, but better than the alternatives. Thanks for giving me some more ammunition :-).
[gravatar]
Another solution is to use signals:

Attach a curried signal handler with the request as a parameter in process_request, detach it from process_response.
[gravatar]
With Django's newer class-based views, the stack inspecting code needs to be updated:
if code.co_varnames[:1] == ("request",):
    return frame.f_locals["request"]
elif code.co_varnames[:2] == ("self", "request",):
    return frame.f_locals["request"]

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
Comment text is Markdown.