Skip to content

Instantly share code, notes, and snippets.

@simon-weber
Last active July 31, 2018 13:58
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save simon-weber/7755289 to your computer and use it in GitHub Desktop.
Save simon-weber/7755289 to your computer and use it in GitHub Desktop.
An example of using a StackContext to store request data globally in Tornado. See https://groups.google.com/d/msg/python-tornado/8izNLhYjyHw/TNKGa9fgvpUJ for motivation and further discussion.
import tornado
class RequestContextHandler(tornado.web.RequestHandler):
def _execute(self, transforms, *args, **kwargs):
# following the example of:
# https://github.com/bdarnell/tornado_tracing/blob/master/tornado_tracing/recording.py
global_data = {} # add whatever here, e.g. self.request
with tornado.stack_context.StackContext(functools.partial(ThreadRequestContext, **global_data)):
super(RequestContextHandler, self)._execute(transforms, *args, **kwargs)
# elsewhere, use ThreadRequestContext.data => a dict
import threading
class ThreadRequestContext(object):
"""A context manager that saves some per-thread state globally.
Intended for use with Tornado's StackContext.
Provide arbitrary data as kwargs upon creation,
then use ThreadRequestContext.data to access it.
"""
_state = threading.local()
_state.data = {}
class __metaclass__(type):
# property() doesn't work on classmethods,
# see http://stackoverflow.com/q/128573/1231454
@property
def data(cls):
if not hasattr(cls._state, 'data'):
return {}
return cls._state.data
def __init__(self, **data):
self._data = data
def __enter__(self):
self._prev_data = self.__class__.data
self.__class__._state.data = self._data
def __exit__(self, *exc):
self.__class__._state.data = self._prev_data
del self._prev_data
return False
@HelloGrayson
Copy link

Can you add a practical example of how to use this? Literally just access ThreadRequestContext.data from anywhere and it will respect the context switches?

@HelloGrayson
Copy link

For some reason when I try and use this gist (tornado 4.1) I get this exception:

Traceback (most recent call last):
  File "/home/uber/zipkintegration/clay-tornado/env/local/lib/python2.7/site-packages/tornado/http1connection.py", line 236, in _read_message
    delegate.finish()
  File "/home/uber/zipkintegration/clay-tornado/env/local/lib/python2.7/site-packages/tornado/httpserver.py", line 269, in finish
    self.delegate.finish()
  File "/home/uber/zipkintegration/clay-tornado/env/local/lib/python2.7/site-packages/tornado/web.py", line 1898, in finish
    self.execute()
  File "/home/uber/zipkintegration/clay-tornado/env/local/lib/python2.7/site-packages/tornado/web.py", line 1930, in execute
    f.add_done_callback(lambda f: f.exception())
AttributeError: 'NoneType' object has no attribute 'add_done_callback'

When I update line 13 to include a return I don't get the exception anymore:

        with tornado.stack_context.StackContext(functools.partial(ThreadRequestContext, **global_data)):
            return super(RequestContextHandler, self)._execute(transforms, *args, **kwargs)

You think this is the correct fix? Perhaps we can update the gist to reflect this.

@EricBuist
Copy link

For which version of Python and Tornado is this for? I'm trying to use this and that just completely fails. The RequestThreadContext.data throws a NameError: data is not a property of the class. The metaclass seems just to not work. I tried to replace it with a class method and change data for data() in my code. It works, but the thread local information is not transmitted across method boundaries. Use of internal undocumented Tornado method (_execute, saw that nowhere) and funky metaclass stuff is likely to bind this to very specific environments.
I'm using Python 3.4.3 and Tornado 3.2.

@simon-weber
Copy link
Author

Hey, sorry for missing these comments -- I didn't realize github doesn't notify on gist comments and mentions don't work. Shoot me a tweet to let me know if you post something new.

likely to bind this to very specific environments.

Ah, yeah. This is working for me in python 2.7.x + tornado 3.2.2, but I haven't tested it elsewhere.

Can you add a practical example of how to use this?

I hook this into a root logging filter, so all logging from the application can have the user id prepended when it's available.

@eddyzhou
Copy link

Not work well with coroutine

@virtuald
Copy link

Those who come by here may want to try https://gist.github.com/virtuald/50bf7cacdc8cfb05e323f350539f0efa instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment