rework context docs

This commit is contained in:
David Lord 2018-04-08 10:43:40 -07:00
parent 47a00864c3
commit 2411707492
No known key found for this signature in database
GPG key ID: 7A1C87E3F5BC42A8
7 changed files with 534 additions and 367 deletions

View file

@ -1,224 +1,267 @@
.. currentmodule:: flask
.. _request-context:
The Request Context
===================
This document describes the behavior in Flask 0.7 which is mostly in line
with the old behavior but has some small, subtle differences.
The request context keeps track of the request-level data during a
request. Rather than passing the request object to each function that
runs during a request, the :data:`request` and :data:`session` proxies
are accessed instead.
It is recommended that you read the :ref:`app-context` chapter first.
This is similar to the :doc:`/appcontext`, which keeps track of the
application-level data independent of a request. A corresponding
application context is pushed when a request context is pushed.
Diving into Context Locals
--------------------------
Say you have a utility function that returns the URL the user should be
redirected to. Imagine it would always redirect to the URL's ``next``
parameter or the HTTP referrer or the index page::
Purpose of the Context
----------------------
from flask import request, url_for
When the :class:`Flask` application handles a request, it creates a
:class:`Request` object based on the environment it received from the
WSGI server. Because a *worker* (thread, process, or coroutine depending
on the server) handles only one request at a time, the request data can
be considered global to that worker during that request. Flask uses the
term *context local* for this.
def redirect_url():
return request.args.get('next') or \
request.referrer or \
url_for('index')
Flask automatically *pushes* a request context when handling a request.
View functions, error handlers, and other functions that run during a
request will have access to the :data:`request` proxy, which points to
the request object for the current request.
As you can see, it accesses the request object. If you try to run this
from a plain Python shell, this is the exception you will see:
>>> redirect_url()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'request'
Lifetime of the Context
-----------------------
That makes a lot of sense because we currently do not have a request we
could access. So we have to make a request and bind it to the current
context. The :attr:`~flask.Flask.test_request_context` method can create
us a :class:`~flask.ctx.RequestContext`:
When a Flask application begins handling a request, it pushes a request
context, which also pushes an :doc:`/appcontext`. When the request ends
it pops the request context then the application context.
>>> ctx = app.test_request_context('/?next=http://example.com/')
The context is unique to each thread (or other worker type).
:data:`request` cannot be passed to another thread, the other thread
will have a different context stack and will not know about the request
the parent thread was pointing to.
This context can be used in two ways. Either with the ``with`` statement
or by calling the :meth:`~flask.ctx.RequestContext.push` and
:meth:`~flask.ctx.RequestContext.pop` methods:
Context locals are implemented in Werkzeug. See :doc:`werkzeug:local`
for more information on how this works internally.
>>> ctx.push()
From that point onwards you can work with the request object:
Manually Push a Context
-----------------------
>>> redirect_url()
u'http://example.com/'
If you try to access :data:`request`, or anything that uses it, outside
a request context, you'll get this error message:
Until you call `pop`:
.. code-block:: pytb
>>> ctx.pop()
RuntimeError: Working outside of request context.
Because the request context is internally maintained as a stack you can
push and pop multiple times. This is very handy to implement things like
internal redirects.
This typically means that you attempted to use functionality that
needed an active HTTP request. Consult the documentation on testing
for information about how to avoid this problem.
This should typically only happen when testing code that expects an
active request. One option is to use the
:meth:`test client <Flask.test_client>` to simulate a full request. Or
you can use :meth:`~Flask.test_request_context` in a ``with`` block, and
everything that runs in the block will have access to :data:`request`,
populated with your test data. ::
def generate_report(year):
format = request.args.get('format')
...
with app.test_request_context(
'/make_report/2017', data={'format': 'short'}):
generate_report()
If you see that error somewhere else in your code not related to
testing, it most likely indicates that you should move that code into a
view function.
For information on how to use the request context from the interactive
Python shell, see :doc:`/shell`.
For more information of how to utilize the request context from the
interactive Python shell, head over to the :ref:`shell` chapter.
How the Context Works
---------------------
If you look into how the Flask WSGI application internally works, you will
find a piece of code that looks very much like this::
The :meth:`Flask.wsgi_app` method is called to handle each request. It
manages the contexts during the request. Internally, the request and
application contexts work as stacks, :data:`_request_ctx_stack` and
:data:`_app_ctx_stack`. When contexts are pushed onto the stack, the
proxies that depend on them are available and point at information from
the top context on the stack.
def wsgi_app(self, environ):
with self.request_context(environ):
try:
response = self.full_dispatch_request()
except Exception as e:
response = self.make_response(self.handle_exception(e))
return response(environ, start_response)
When the request starts, a :class:`~ctx.RequestContext` is created and
pushed, which creates and pushes an :class:`~ctx.AppContext` first if
a context for that application is not already the top context. While
these contexts are pushed, the :data:`current_app`, :data:`g`,
:data:`request`, and :data:`session` proxies are available to the
original thread handling the request.
The method :meth:`~Flask.request_context` returns a new
:class:`~flask.ctx.RequestContext` object and uses it in combination with
the ``with`` statement to bind the context. Everything that is called from
the same thread from this point onwards until the end of the ``with``
statement will have access to the request globals (:data:`flask.request`
and others).
Because the contexts are stacks, other contexts may be pushed to change
the proxies during a request. While this is not a common pattern, it
can be used in advanced applications to, for example, do internal
redirects or chain different applications together.
The request context internally works like a stack: The topmost level on
the stack is the current active request.
:meth:`~flask.ctx.RequestContext.push` adds the context to the stack on
the very top, :meth:`~flask.ctx.RequestContext.pop` removes it from the
stack again. On popping the application's
:func:`~flask.Flask.teardown_request` functions are also executed.
After the request is dispatched and a response is generated and sent,
the request context is popped, which then pops the application context.
Immediately before they are popped, the :meth:`~Flask.teardown_request`
and :meth:`~Flask.teardown_appcontext` functions are are executed. These
execute even if an unhandled exception occurred during dispatch.
Another thing of note is that the request context will automatically also
create an :ref:`application context <app-context>` when it's pushed and
there is no application context for that application so far.
.. _callbacks-and-errors:
Callbacks and Errors
--------------------
What happens if an error occurs in Flask during request processing? This
particular behavior changed in 0.7 because we wanted to make it easier to
understand what is actually happening. The new behavior is quite simple:
Flask dispatches a request in multiple stages which can affect the
request, response, and how errors are handled. The contexts are active
during all of these stages.
1. Before each request, :meth:`~flask.Flask.before_request` functions are
executed. If one of these functions return a response, the other
functions are no longer called. In any case however the return value
is treated as a replacement for the view's return value.
A :class:`Blueprint` can add handlers for these events that are specific
to the blueprint. The handlers for a blueprint will run if the blueprint
owns the route that matches the request.
2. If the :meth:`~flask.Flask.before_request` functions did not return a
response, the regular request handling kicks in and the view function
that was matched has the chance to return a response.
#. Before each request, :meth:`~Flask.before_request` functions are
called. If one of these functions return a value, the other
functions are skipped. The return value is treated as the response
and the view function is not called.
3. The return value of the view is then converted into an actual response
object and handed over to the :meth:`~flask.Flask.after_request`
functions which have the chance to replace it or modify it in place.
#. If the :meth:`~Flask.before_request` functions did not return a
response, the view function for the matched route is called and
returns a response.
4. At the end of the request the :meth:`~flask.Flask.teardown_request`
functions are executed. This always happens, even in case of an
unhandled exception down the road or if a before-request handler was
not executed yet or at all (for example in test environments sometimes
you might want to not execute before-request callbacks).
#. The return value of the view is converted into an actual response
object and passed to the :meth:`~Flask.after_request`
functions. Each function returns a modified or new response object.
Now what happens on errors? If you are not in debug mode and an exception is not
caught, the 500 internal server handler is called. In debug mode
however the exception is not further processed and bubbles up to the WSGI
server. That way things like the interactive debugger can provide helpful
debug information.
#. After the response is returned, the contexts are popped, which calls
the :meth:`~Flask.teardown_request` and
:meth:`~Flask.teardown_appcontext` functions. These functions are
called even if an unhandled exception was raised at any point above.
An important change in 0.7 is that the internal server error is now no
longer post processed by the after request callbacks and after request
callbacks are no longer guaranteed to be executed. This way the internal
dispatching code looks cleaner and is easier to customize and understand.
If an exception is raised before the teardown functions, Flask tries to
match it with an :meth:`~Flask.errorhandler` function to handle the
exception and return a response. If no error handler is found, or the
handler itself raises an exception, Flask returns a generic
``500 Internal Server Error`` response. The teardown functions are still
called, and are passed the exception object.
If debug mode is enabled, unhandled exceptions are not converted to a
``500`` response and instead are propagated to the WSGI server. This
allows the development server to present the interactive debugger with
the traceback.
The new teardown functions are supposed to be used as a replacement for
things that absolutely need to happen at the end of request.
Teardown Callbacks
------------------
~~~~~~~~~~~~~~~~~~
The teardown callbacks are special callbacks in that they are executed at
a different point. Strictly speaking they are independent of the actual
request handling as they are bound to the lifecycle of the
:class:`~flask.ctx.RequestContext` object. When the request context is
popped, the :meth:`~flask.Flask.teardown_request` functions are called.
The teardown callbacks are independent of the request dispatch, and are
instead called by the contexts when they are popped. The functions are
called even if there is an unhandled exception during dispatch, and for
manually pushed contexts. This means there is no guarantee that any
other parts of the request dispatch have run first. Be sure to write
these functions in a way that does not depend on other callbacks and
will not fail.
This is important to know if the life of the request context is prolonged
by using the test client in a with statement or when using the request
context from the command line::
During testing, it can be useful to defer popping the contexts after the
request ends, so that their data can be accessed in the test function.
Using the :meth:`~Flask.test_client` as a ``with`` block to preserve the
contexts until the with block exits.
with app.test_client() as client:
resp = client.get('/foo')
# the teardown functions are still not called at that point
# even though the response ended and you have the response
# object in your hand
.. code-block:: python
# only when the code reaches this point the teardown functions
# are called. Alternatively the same thing happens if another
# request was triggered from the test client
from flask import Flask, request
It's easy to see the behavior from the command line:
app = Flask(__name__)
>>> app = Flask(__name__)
>>> @app.teardown_request
... def teardown_request(exception=None):
... print 'this runs after request'
...
>>> ctx = app.test_request_context()
>>> ctx.push()
>>> ctx.pop()
this runs after request
>>>
@app.route('/')
def hello():
print('during view')
return 'Hello, World!'
@app.teardown_request
def show_teardown(exception):
print('after with block')
with app.test_request_context():
print('during with block')
# teardown functions are called after the context with block exits
with app.test_client():
client.get('/')
# the contexts are not popped even though the request ended
print(request.path)
# the contexts are popped and teardown functions are called after
# the client with block exists
Signals
~~~~~~~
If :data:`~signals.signals_available` is true, the following signals are
sent:
#. :data:`request_started` is sent before the
:meth:`~Flask.before_request` functions are called.
#. :data:`request_finished` is sent after the
:meth:`~Flask.after_request` functions are called.
#. :data:`got_request_exception` is sent when an exception begins to
be handled, but before an :meth:`~Flask.errorhandler` is looked up or
called.
#. :data:`request_tearing_down` is sent after the
:meth:`~Flask.teardown_request` functions are called.
Context Preservation on Error
-----------------------------
At the end of a request, the request context is popped and all data
associated with it is destroyed. If an error occurs during development,
it is useful to delay destroying the data for debugging purposes.
When the development server is running in development mode (the
``FLASK_ENV`` environment variable is set to ``'development'``), the
error and data will be preserved and shown in the interactive debugger.
This behavior can be controlled with the
:data:`PRESERVE_CONTEXT_ON_EXCEPTION` config. As described above, it
defaults to ``True`` in the development environment.
Do not enable :data:`PRESERVE_CONTEXT_ON_EXCEPTION` in production, as it
will cause your application to leak memory on exceptions.
Keep in mind that teardown callbacks are always executed, even if
before-request callbacks were not executed yet but an exception happened.
Certain parts of the test system might also temporarily create a request
context without calling the before-request handlers. Make sure to write
your teardown-request handlers in a way that they will never fail.
.. _notes-on-proxies:
Notes On Proxies
----------------
Some of the objects provided by Flask are proxies to other objects. The
reason behind this is that these proxies are shared between threads and
they have to dispatch to the actual object bound to a thread behind the
scenes as necessary.
Some of the objects provided by Flask are proxies to other objects. The
proxies are accessed in the same way for each worker thread, but
point to the unique object bound to each worker behind the scenes as
described on this page.
Most of the time you don't have to care about that, but there are some
exceptions where it is good to know that this object is an actual proxy:
- The proxy objects do not fake their inherited types, so if you want to
perform actual instance checks, you have to do that on the instance
that is being proxied (see `_get_current_object` below).
- if the object reference is important (so for example for sending
:ref:`signals`)
- The proxy objects cannot fake their type as the actual object types.
If you want to perform instance checks, you have to do that on the
object being proxied.
- If the specific object reference is important, for example for
sending :ref:`signals` or passing data to a background thread.
If you need to get access to the underlying object that is proxied, you
can use the :meth:`~werkzeug.local.LocalProxy._get_current_object` method::
If you need to access the underlying object that is proxied, use the
:meth:`~werkzeug.local.LocalProxy._get_current_object` method::
app = current_app._get_current_object()
my_signal.send(app)
Context Preservation on Error
-----------------------------
If an error occurs or not, at the end of the request the request context
is popped and all data associated with it is destroyed. During
development however that can be problematic as you might want to have the
information around for a longer time in case an exception occurred. In
Flask 0.6 and earlier in debug mode, if an exception occurred, the
request context was not popped so that the interactive debugger can still
provide you with important information.
Starting with Flask 0.7 you have finer control over that behavior by
setting the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable. By
default it's linked to the setting of ``DEBUG``. If the application is in
debug mode the context is preserved. If debug mode is set to off, the context
is not preserved.
Do not force activate ``PRESERVE_CONTEXT_ON_EXCEPTION`` if debug mode is set to off
as it will cause your application to leak memory on exceptions. However,
it can be useful during development to get the same error preserving
behavior as debug mode when attempting to debug an error that
only occurs under production settings.