Added documentation for appcontext and teardown handlers
This commit is contained in:
parent
34bbd3100b
commit
9bed20c07c
10 changed files with 287 additions and 64 deletions
14
docs/api.rst
14
docs/api.rst
|
|
@ -469,8 +469,18 @@ Signals
|
|||
.. data:: request_tearing_down
|
||||
|
||||
This signal is sent when the application is tearing down the request.
|
||||
This is always called, even if an error happened. No arguments are
|
||||
provided.
|
||||
This is always called, even if an error happened. An `exc` keyword
|
||||
argument is passed with the exception that caused the teardown.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
The `exc` parameter was added.
|
||||
|
||||
.. data:: appcontext_tearing_down
|
||||
|
||||
This signal is sent when the application is tearing down the
|
||||
application context. This is always called, even if an error happened.
|
||||
An `exc` keyword argument is passed with the exception that caused the
|
||||
teardown.
|
||||
|
||||
.. currentmodule:: None
|
||||
|
||||
|
|
|
|||
88
docs/appcontext.rst
Normal file
88
docs/appcontext.rst
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
.. _app_context:
|
||||
|
||||
The Application Context
|
||||
=======================
|
||||
|
||||
.. versionadded:: 0.9
|
||||
|
||||
One of the design ideas behind Flask is that there are two different
|
||||
“states” in which code is executed. The application setup state in which
|
||||
the application implicitly is on the module level. It starts when the
|
||||
:class:`Flask` object is instantiated, and it implicitly ends when the
|
||||
first request comes in. While the application is in this state a few
|
||||
assumptions are true:
|
||||
|
||||
- the programmer can modify the application object safely.
|
||||
- no request handling happened so far
|
||||
- you have to have a reference to the application object in order to
|
||||
modify it, there is no magic proxy that can give you a reference to
|
||||
the application object you're currently creating or modifying.
|
||||
|
||||
On the contrast, during request handling, a couple of other rules exist:
|
||||
|
||||
- while a request is active, the context local objects
|
||||
(:data:`flask.request` and others) point to the current request.
|
||||
- any code can get hold of these objects at any time.
|
||||
|
||||
There is a third state which is sitting in between a little bit.
|
||||
Sometimes you are dealing with an application in a way that is similar to
|
||||
how you interact with applications during request handling just that there
|
||||
is no request active. Consider for instance that you're sitting in an
|
||||
interactive Python shell and interacting with the application, or a
|
||||
command line application.
|
||||
|
||||
The application context is what powers the :data:`~flask.current_app`
|
||||
context local.
|
||||
|
||||
Purpose of the Application Context
|
||||
----------------------------------
|
||||
|
||||
The main reason for the application's context existance is that in the
|
||||
past a bunch of functionality was attached to the request context in lack
|
||||
of a better solution. Since one of the pillar's of Flask's design is that
|
||||
you can have more than one application in the same Python process.
|
||||
|
||||
So how does the code find the “right” application? In the past we
|
||||
recommended passing applications around explicitly, but that caused issues
|
||||
with libraries that were not designed with that in mind for libraries for
|
||||
which it was too inconvenient to make this work.
|
||||
|
||||
A common workaround for that problem was to use the
|
||||
:data:`~flask.current_app` proxy later on, which was bound to the current
|
||||
request's application reference. Since however creating such a request
|
||||
context is an unnecessarily expensive operation in case there is no
|
||||
request around, the application context was introduced.
|
||||
|
||||
Creating an Application Context
|
||||
-------------------------------
|
||||
|
||||
To make an application context there are two ways. The first one is the
|
||||
implicit one: whenever a request context is pushed, an application context
|
||||
will be created alongside if this is necessary. As a result of that, you
|
||||
can ignore the existance of the application context unless you need it.
|
||||
|
||||
The second way is the explicit way using the
|
||||
:meth:`~flask.Flask.app_context` method::
|
||||
|
||||
from flask import Flask, current_app
|
||||
|
||||
app = Flask(__name__)
|
||||
with app.app_context():
|
||||
# within this block, current_app points to app.
|
||||
print current_app.name
|
||||
|
||||
The application context is also used by the :func:`~flask.url_for`
|
||||
function in case a ``SERVER_NAME`` was configured. This allows you to
|
||||
generate URLs even in the absence of a request.
|
||||
|
||||
Locality of the Context
|
||||
-----------------------
|
||||
|
||||
The application context is created and destroyed as necessary. It never
|
||||
moves between threads and it will not be shared between requests. As such
|
||||
it is the perfect place to store database connection information and other
|
||||
things. The internal stack object is called :data:`flask._app_ctx_stack`.
|
||||
Extensions are free to store additional information on the topmost level,
|
||||
assuming they pick a sufficiently unique name.
|
||||
|
||||
For more information about that, see :ref:`extension-dev`.
|
||||
|
|
@ -18,6 +18,7 @@ instructions for web development with Flask.
|
|||
config
|
||||
signals
|
||||
views
|
||||
appcontext
|
||||
reqcontext
|
||||
blueprints
|
||||
extensions
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
.. _extension-dev:
|
||||
|
||||
Flask Extension Development
|
||||
===========================
|
||||
|
||||
|
|
@ -152,6 +154,11 @@ What to use depends on what you have in mind. For the SQLite 3 extension
|
|||
we will use the class-based approach because it will provide users with an
|
||||
object that handles opening and closing database connections.
|
||||
|
||||
What's important about classes is that they encourage to be shared around
|
||||
on module level. In that case, the object itself must not under any
|
||||
circumstances store any application specific state and must be shareable
|
||||
between different application.
|
||||
|
||||
The Extension Code
|
||||
------------------
|
||||
|
||||
|
|
@ -159,7 +166,13 @@ Here's the contents of the `flask_sqlite3.py` for copy/paste::
|
|||
|
||||
import sqlite3
|
||||
|
||||
from flask import _request_ctx_stack
|
||||
# Find the stack on which we want to store the database connection.
|
||||
# Starting with Flask 0.9, the _app_ctx_stack is the correct one,
|
||||
# before that we need to use the _request_ctx_stack.
|
||||
try:
|
||||
from flask import _app_ctx_stack as stack
|
||||
except ImportError:
|
||||
from flask import _request_ctx_stack as stack
|
||||
|
||||
|
||||
class SQLite3(object):
|
||||
|
|
@ -172,26 +185,28 @@ Here's the contents of the `flask_sqlite3.py` for copy/paste::
|
|||
self.app = None
|
||||
|
||||
def init_app(self, app):
|
||||
self.app = app
|
||||
self.app.config.setdefault('SQLITE3_DATABASE', ':memory:')
|
||||
self.app.teardown_request(self.teardown_request)
|
||||
self.app.before_request(self.before_request)
|
||||
app.config.setdefault('SQLITE3_DATABASE', ':memory:')
|
||||
# Use the newstyle teardown_appcontext if it's available,
|
||||
# otherwise fall back to the request context
|
||||
if hasattr(app, 'teardown_appcontext'):
|
||||
app.teardown_appcontext(self.teardown)
|
||||
else:
|
||||
app.teardown_request(self.teardown)
|
||||
|
||||
def connect(self):
|
||||
return sqlite3.connect(self.app.config['SQLITE3_DATABASE'])
|
||||
|
||||
def before_request(self):
|
||||
ctx = _request_ctx_stack.top
|
||||
ctx.sqlite3_db = self.connect()
|
||||
|
||||
def teardown_request(self, exception):
|
||||
ctx = _request_ctx_stack.top
|
||||
ctx.sqlite3_db.close()
|
||||
def teardown(self, exception):
|
||||
ctx = stack.top
|
||||
if hasattr(ctx, 'sqlite3_db'):
|
||||
ctx.sqlite3_db.close()
|
||||
|
||||
@property
|
||||
def connection(self):
|
||||
ctx = _request_ctx_stack.top
|
||||
ctx = stack.top
|
||||
if ctx is not None:
|
||||
if not hasattr(ctx, 'sqlite3_db'):
|
||||
ctx.sqlite3_db = self.connect()
|
||||
return ctx.sqlite3_db
|
||||
|
||||
|
||||
|
|
@ -204,14 +219,19 @@ So here's what these lines of code do:
|
|||
factory pattern for creating applications. The ``init_app`` will set the
|
||||
configuration for the database, defaulting to an in memory database if
|
||||
no configuration is supplied. In addition, the ``init_app`` method attaches
|
||||
``before_request`` and ``teardown_request`` handlers.
|
||||
the ``teardown`` handler. It will try to use the newstyle app context
|
||||
handler and if it does not exist, falls back to the request context
|
||||
one.
|
||||
3. Next, we define a ``connect`` method that opens a database connection.
|
||||
4. Then we set up the request handlers we bound to the app above. Note here
|
||||
that we're attaching our database connection to the top request context via
|
||||
``_request_ctx_stack.top``. Extensions should use the top context and not the
|
||||
``g`` object to store things like database connections.
|
||||
5. Finally, we add a ``connection`` property that simplifies access to the context's
|
||||
database.
|
||||
4. Finally, we add a ``connection`` property that on first access opens
|
||||
the database connection and stores it on the context.
|
||||
|
||||
Note here that we're attaching our database connection to the top
|
||||
application context via ``_app_ctx_stack.top``. Extensions should use
|
||||
the top context for storing their own information with a sufficiently
|
||||
complex name. Note that we're falling back to the
|
||||
``_request_ctx_stack.top`` if the application is using an older
|
||||
version of Flask that does not support it.
|
||||
|
||||
So why did we decide on a class-based approach here? Because using our
|
||||
extension looks something like this::
|
||||
|
|
@ -241,19 +261,38 @@ for creating apps::
|
|||
Keep in mind that supporting this factory pattern for creating apps is required
|
||||
for approved flask extensions (described below).
|
||||
|
||||
.. admonition:: Note on ``init_app``
|
||||
|
||||
Using _request_ctx_stack
|
||||
------------------------
|
||||
As you noticed, ``init_app`` does not assign ``app`` to ``self``. This
|
||||
is intentional! Class based Flask extensions must only store the
|
||||
application on the object when the application was passed to the
|
||||
constructor. This tells the extension: I am not interested in using
|
||||
multiple applications.
|
||||
|
||||
In the example above, before every request, a ``sqlite3_db`` variable is assigned
|
||||
to ``_request_ctx_stack.top``. In a view function, this variable is accessible
|
||||
using the ``connection`` property of ``SQLite3``. During the teardown of a
|
||||
request, the ``sqlite3_db`` connection is closed. By using this pattern, the
|
||||
*same* connection to the sqlite3 database is accessible to anything that needs it
|
||||
for the duration of the request.
|
||||
When the extension needs to find the current application and it does
|
||||
not have a reference to it, it must either use the
|
||||
:data:`~flask.current_app` context local or change the API in a way
|
||||
that you can pass the application explicitly.
|
||||
|
||||
End-Of-Request Behavior
|
||||
-----------------------
|
||||
|
||||
Using _app_ctx_stack
|
||||
--------------------
|
||||
|
||||
In the example above, before every request, a ``sqlite3_db`` variable is
|
||||
assigned to ``_app_ctx_stack.top``. In a view function, this variable is
|
||||
accessible using the ``connection`` property of ``SQLite3``. During the
|
||||
teardown of a request, the ``sqlite3_db`` connection is closed. By using
|
||||
this pattern, the *same* connection to the sqlite3 database is accessible
|
||||
to anything that needs it for the duration of the request.
|
||||
|
||||
If the :data:`~flask._app_ctx_stack` does not exist because the user uses
|
||||
an old version of Flask, it is recommended to fall back to
|
||||
:data:`~flask._request_ctx_stack` which is bound to a request.
|
||||
|
||||
Teardown Behavior
|
||||
-----------------
|
||||
|
||||
*This is only relevant if you want to support Flask 0.6 and older*
|
||||
|
||||
Due to the change in Flask 0.7 regarding functions that are run at the end
|
||||
of the request your extension will have to be extra careful there if it
|
||||
|
|
@ -270,7 +309,6 @@ pattern is a good way to support both::
|
|||
else:
|
||||
app.after_request(close_connection)
|
||||
|
||||
|
||||
Strictly speaking the above code is wrong, because teardown functions are
|
||||
passed the exception and typically don't return anything. However because
|
||||
the return value is discarded this will just work assuming that the code
|
||||
|
|
|
|||
|
|
@ -6,27 +6,7 @@ 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.
|
||||
|
||||
One of the design ideas behind Flask is that there are two different
|
||||
“states” in which code is executed. The application setup state in which
|
||||
the application implicitly is on the module level. It starts when the
|
||||
:class:`Flask` object is instantiated, and it implicitly ends when the
|
||||
first request comes in. While the application is in this state a few
|
||||
assumptions are true:
|
||||
|
||||
- the programmer can modify the application object safely.
|
||||
- no request handling happened so far
|
||||
- you have to have a reference to the application object in order to
|
||||
modify it, there is no magic proxy that can give you a reference to
|
||||
the application object you're currently creating or modifying.
|
||||
|
||||
On the contrast, during request handling, a couple of other rules exist:
|
||||
|
||||
- while a request is active, the context local objects
|
||||
(:data:`flask.request` and others) point to the current request.
|
||||
- any code can get hold of these objects at any time.
|
||||
|
||||
The magic that makes this works is internally referred in Flask as the
|
||||
“request context”.
|
||||
It is recommended that you read the :api:`app-context` chapter first.
|
||||
|
||||
Diving into Context Locals
|
||||
--------------------------
|
||||
|
|
@ -107,6 +87,10 @@ 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.
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -268,4 +268,27 @@ The following signals exist in Flask:
|
|||
from flask import request_tearing_down
|
||||
request_tearing_down.connect(close_db_connection, app)
|
||||
|
||||
As of Flask 0.9, this will also be passed an `exc` keyword argument
|
||||
that has a reference to the exception that caused the teardown if
|
||||
there was one.
|
||||
|
||||
.. data:: flask.appcontext_tearing_down
|
||||
:noindex:
|
||||
|
||||
This signal is sent when the app context is tearing down. This is always
|
||||
called, even if an exception is caused. Currently functions listening
|
||||
to this signal are called after the regular teardown handlers, but this
|
||||
is not something you can rely on.
|
||||
|
||||
Example subscriber::
|
||||
|
||||
def close_db_connection(sender, **extra):
|
||||
session.close()
|
||||
|
||||
from flask import request_tearing_down
|
||||
appcontext_tearing_down.connect(close_db_connection, app)
|
||||
|
||||
This will also be passed an `exc` keyword argument that has a reference
|
||||
to the exception that caused the teardown if there was one.
|
||||
|
||||
.. _blinker: http://pypi.python.org/pypi/blinker
|
||||
|
|
|
|||
68
flask/app.py
68
flask/app.py
|
|
@ -35,7 +35,7 @@ from .module import blueprint_is_module
|
|||
from .templating import DispatchingJinjaLoader, Environment, \
|
||||
_default_template_ctx_processor
|
||||
from .signals import request_started, request_finished, got_request_exception, \
|
||||
request_tearing_down
|
||||
request_tearing_down, appcontext_tearing_down
|
||||
|
||||
# a lock used for logger initialization
|
||||
_logger_lock = Lock()
|
||||
|
|
@ -364,6 +364,14 @@ class Flask(_PackageBoundObject):
|
|||
#: .. versionadded:: 0.7
|
||||
self.teardown_request_funcs = {}
|
||||
|
||||
#: A list of functions that are called when the application context
|
||||
#: is destroyed. Since the application context is also torn down
|
||||
#: if the request ends this is the place to store code that disconnects
|
||||
#: from databases.
|
||||
#:
|
||||
#: .. versionadded:: 0.9
|
||||
self.teardown_appcontext_funcs = []
|
||||
|
||||
#: A dictionary with lists of functions that can be used as URL
|
||||
#: value processor functions. Whenever a URL is built these functions
|
||||
#: are called to modify the dictionary of values in place. The key
|
||||
|
|
@ -1106,10 +1114,42 @@ class Flask(_PackageBoundObject):
|
|||
that they will fail. If they do execute code that might fail they
|
||||
will have to surround the execution of these code by try/except
|
||||
statements and log ocurring errors.
|
||||
|
||||
When a teardown function was called because of a exception it will
|
||||
be passed an error object.
|
||||
"""
|
||||
self.teardown_request_funcs.setdefault(None, []).append(f)
|
||||
return f
|
||||
|
||||
@setupmethod
|
||||
def teardown_appcontext(self, f):
|
||||
"""Registers a function to be called when the application context
|
||||
ends. These functions are typically also called when the request
|
||||
context is popped.
|
||||
|
||||
Example::
|
||||
|
||||
ctx = app.app_context()
|
||||
ctx.push()
|
||||
...
|
||||
ctx.pop()
|
||||
|
||||
When ``ctx.pop()`` is executed in the above example, the teardown
|
||||
functions are called just before the app context moves from the
|
||||
stack of active contexts. This becomes relevant if you are using
|
||||
such constructs in tests.
|
||||
|
||||
Since a request context typically also manages an application
|
||||
context it would also be called when you pop a request context.
|
||||
|
||||
When a teardown function was called because of an exception it will
|
||||
be passed an error object.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
self.teardown_appcontext_funcs.append(f)
|
||||
return f
|
||||
|
||||
@setupmethod
|
||||
def context_processor(self, f):
|
||||
"""Registers a template context processor function."""
|
||||
|
|
@ -1485,23 +1525,39 @@ class Flask(_PackageBoundObject):
|
|||
self.save_session(ctx.session, response)
|
||||
return response
|
||||
|
||||
def do_teardown_request(self):
|
||||
def do_teardown_request(self, exc=None):
|
||||
"""Called after the actual request dispatching and will
|
||||
call every as :meth:`teardown_request` decorated function. This is
|
||||
not actually called by the :class:`Flask` object itself but is always
|
||||
triggered when the request context is popped. That way we have a
|
||||
tighter control over certain resources under testing environments.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
Added the `exc` argument. Previously this was always using the
|
||||
current exception information.
|
||||
"""
|
||||
if exc is None:
|
||||
exc = sys.exc_info()[1]
|
||||
funcs = reversed(self.teardown_request_funcs.get(None, ()))
|
||||
bp = _request_ctx_stack.top.request.blueprint
|
||||
if bp is not None and bp in self.teardown_request_funcs:
|
||||
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
|
||||
exc = sys.exc_info()[1]
|
||||
for func in funcs:
|
||||
rv = func(exc)
|
||||
if rv is not None:
|
||||
return rv
|
||||
request_tearing_down.send(self)
|
||||
request_tearing_down.send(self, exc=exc)
|
||||
|
||||
def do_teardown_appcontext(self, exc=None):
|
||||
"""Called when an application context is popped. This works pretty
|
||||
much the same as :meth:`do_teardown_request` but for the application
|
||||
context.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
if exc is None:
|
||||
exc = sys.exc_info()[1]
|
||||
for func in reversed(self.teardown_appcontext_funcs):
|
||||
func(exc)
|
||||
appcontext_tearing_down.send(self, exc=exc)
|
||||
|
||||
def app_context(self):
|
||||
"""Binds the application only. For as long as the application is bound
|
||||
|
|
|
|||
18
flask/ctx.py
18
flask/ctx.py
|
|
@ -9,6 +9,8 @@
|
|||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
from .globals import _request_ctx_stack, _app_ctx_stack
|
||||
|
|
@ -86,8 +88,11 @@ class AppContext(object):
|
|||
"""Binds the app context to the current context."""
|
||||
_app_ctx_stack.push(self)
|
||||
|
||||
def pop(self):
|
||||
def pop(self, exc=None):
|
||||
"""Pops the app context."""
|
||||
if exc is None:
|
||||
exc = sys.exc_info()[1]
|
||||
self.app.do_teardown_appcontext(exc)
|
||||
rv = _app_ctx_stack.pop()
|
||||
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
|
||||
% (rv, self)
|
||||
|
|
@ -197,13 +202,18 @@ class RequestContext(object):
|
|||
if self.session is None:
|
||||
self.session = self.app.make_null_session()
|
||||
|
||||
def pop(self):
|
||||
def pop(self, exc=None):
|
||||
"""Pops the request context and unbinds it by doing that. This will
|
||||
also trigger the execution of functions registered by the
|
||||
:meth:`~flask.Flask.teardown_request` decorator.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
Added the `exc` argument.
|
||||
"""
|
||||
self.preserved = False
|
||||
self.app.do_teardown_request()
|
||||
if exc is None:
|
||||
exc = sys.exc_info()[1]
|
||||
self.app.do_teardown_request(exc)
|
||||
rv = _request_ctx_stack.pop()
|
||||
assert rv is self, 'Popped wrong request context. (%r instead of %r)' \
|
||||
% (rv, self)
|
||||
|
|
@ -231,7 +241,7 @@ class RequestContext(object):
|
|||
(tb is not None and self.app.preserve_context_on_exception):
|
||||
self.preserved = True
|
||||
else:
|
||||
self.pop()
|
||||
self.pop(exc_value)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s \'%s\' [%s] of %s>' % (
|
||||
|
|
|
|||
|
|
@ -49,3 +49,4 @@ request_started = _signals.signal('request-started')
|
|||
request_finished = _signals.signal('request-finished')
|
||||
request_tearing_down = _signals.signal('request-tearing-down')
|
||||
got_request_exception = _signals.signal('got-request-exception')
|
||||
appcontext_tearing_down = _signals.signal('appcontext-tearing-down')
|
||||
|
|
|
|||
|
|
@ -53,6 +53,18 @@ class AppContextTestCase(FlaskTestCase):
|
|||
self.assert_equal(flask.current_app._get_current_object(), app)
|
||||
self.assert_equal(flask._app_ctx_stack.top, None)
|
||||
|
||||
def test_app_tearing_down(self):
|
||||
cleanup_stuff = []
|
||||
app = flask.Flask(__name__)
|
||||
@app.teardown_appcontext
|
||||
def cleanup(exception):
|
||||
cleanup_stuff.append(exception)
|
||||
|
||||
with app.app_context():
|
||||
pass
|
||||
|
||||
self.assert_equal(cleanup_stuff, [None])
|
||||
|
||||
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue