Browse Source

Added documentation for appcontext and teardown handlers

pull/483/head
Armin Ronacher 13 years ago
parent
commit
9bed20c07c
  1. 14
      docs/api.rst
  2. 88
      docs/appcontext.rst
  3. 1
      docs/contents.rst.inc
  4. 98
      docs/extensiondev.rst
  5. 26
      docs/reqcontext.rst
  6. 23
      docs/signals.rst
  7. 68
      flask/app.py
  8. 18
      flask/ctx.py
  9. 1
      flask/signals.py
  10. 12
      flask/testsuite/appctx.py

14
docs/api.rst

@ -469,8 +469,18 @@ Signals
.. data:: request_tearing_down .. data:: request_tearing_down
This signal is sent when the application is tearing down the request. This signal is sent when the application is tearing down the request.
This is always called, even if an error happened. No arguments are This is always called, even if an error happened. An `exc` keyword
provided. 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 .. currentmodule:: None

88
docs/appcontext.rst

@ -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`.

1
docs/contents.rst.inc

@ -18,6 +18,7 @@ instructions for web development with Flask.
config config
signals signals
views views
appcontext
reqcontext reqcontext
blueprints blueprints
extensions extensions

98
docs/extensiondev.rst

@ -1,3 +1,5 @@
.. _extension-dev:
Flask Extension Development 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 we will use the class-based approach because it will provide users with an
object that handles opening and closing database connections. 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 The Extension Code
------------------ ------------------
@ -159,7 +166,13 @@ Here's the contents of the `flask_sqlite3.py` for copy/paste::
import sqlite3 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): class SQLite3(object):
@ -172,26 +185,28 @@ Here's the contents of the `flask_sqlite3.py` for copy/paste::
self.app = None self.app = None
def init_app(self, app): def init_app(self, app):
self.app = app app.config.setdefault('SQLITE3_DATABASE', ':memory:')
self.app.config.setdefault('SQLITE3_DATABASE', ':memory:') # Use the newstyle teardown_appcontext if it's available,
self.app.teardown_request(self.teardown_request) # otherwise fall back to the request context
self.app.before_request(self.before_request) if hasattr(app, 'teardown_appcontext'):
app.teardown_appcontext(self.teardown)
else:
app.teardown_request(self.teardown)
def connect(self): def connect(self):
return sqlite3.connect(self.app.config['SQLITE3_DATABASE']) return sqlite3.connect(self.app.config['SQLITE3_DATABASE'])
def before_request(self): def teardown(self, exception):
ctx = _request_ctx_stack.top ctx = stack.top
ctx.sqlite3_db = self.connect() if hasattr(ctx, 'sqlite3_db'):
def teardown_request(self, exception):
ctx = _request_ctx_stack.top
ctx.sqlite3_db.close() ctx.sqlite3_db.close()
@property @property
def connection(self): def connection(self):
ctx = _request_ctx_stack.top ctx = stack.top
if ctx is not None: if ctx is not None:
if not hasattr(ctx, 'sqlite3_db'):
ctx.sqlite3_db = self.connect()
return ctx.sqlite3_db 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 factory pattern for creating applications. The ``init_app`` will set the
configuration for the database, defaulting to an in memory database if configuration for the database, defaulting to an in memory database if
no configuration is supplied. In addition, the ``init_app`` method attaches 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. 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 4. Finally, we add a ``connection`` property that on first access opens
that we're attaching our database connection to the top request context via the database connection and stores it on the context.
``_request_ctx_stack.top``. Extensions should use the top context and not the
``g`` object to store things like database connections. Note here that we're attaching our database connection to the top
5. Finally, we add a ``connection`` property that simplifies access to the context's application context via ``_app_ctx_stack.top``. Extensions should use
database. 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 So why did we decide on a class-based approach here? Because using our
extension looks something like this:: 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 Keep in mind that supporting this factory pattern for creating apps is required
for approved flask extensions (described below). 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 When the extension needs to find the current application and it does
to ``_request_ctx_stack.top``. In a view function, this variable is accessible not have a reference to it, it must either use the
using the ``connection`` property of ``SQLite3``. During the teardown of a :data:`~flask.current_app` context local or change the API in a way
request, the ``sqlite3_db`` connection is closed. By using this pattern, the that you can pass the application explicitly.
*same* connection to the sqlite3 database is accessible to anything that needs it
for the duration of the request.
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 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 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: else:
app.after_request(close_connection) app.after_request(close_connection)
Strictly speaking the above code is wrong, because teardown functions are Strictly speaking the above code is wrong, because teardown functions are
passed the exception and typically don't return anything. However because passed the exception and typically don't return anything. However because
the return value is discarded this will just work assuming that the code the return value is discarded this will just work assuming that the code

26
docs/reqcontext.rst

@ -6,27 +6,7 @@ The Request Context
This document describes the behavior in Flask 0.7 which is mostly in line This document describes the behavior in Flask 0.7 which is mostly in line
with the old behavior but has some small, subtle differences. with the old behavior but has some small, subtle differences.
One of the design ideas behind Flask is that there are two different It is recommended that you read the :api:`app-context` chapter first.
“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”.
Diving into Context Locals 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 stack again. On popping the application's
:func:`~flask.Flask.teardown_request` functions are also executed. :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:
Callbacks and Errors Callbacks and Errors

23
docs/signals.rst

@ -268,4 +268,27 @@ The following signals exist in Flask:
from flask import request_tearing_down from flask import request_tearing_down
request_tearing_down.connect(close_db_connection, app) 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 .. _blinker: http://pypi.python.org/pypi/blinker

68
flask/app.py

@ -35,7 +35,7 @@ from .module import blueprint_is_module
from .templating import DispatchingJinjaLoader, Environment, \ from .templating import DispatchingJinjaLoader, Environment, \
_default_template_ctx_processor _default_template_ctx_processor
from .signals import request_started, request_finished, got_request_exception, \ 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 # a lock used for logger initialization
_logger_lock = Lock() _logger_lock = Lock()
@ -364,6 +364,14 @@ class Flask(_PackageBoundObject):
#: .. versionadded:: 0.7 #: .. versionadded:: 0.7
self.teardown_request_funcs = {} 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 #: A dictionary with lists of functions that can be used as URL
#: value processor functions. Whenever a URL is built these functions #: value processor functions. Whenever a URL is built these functions
#: are called to modify the dictionary of values in place. The key #: 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 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 will have to surround the execution of these code by try/except
statements and log ocurring errors. 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) self.teardown_request_funcs.setdefault(None, []).append(f)
return 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 @setupmethod
def context_processor(self, f): def context_processor(self, f):
"""Registers a template context processor function.""" """Registers a template context processor function."""
@ -1485,23 +1525,39 @@ class Flask(_PackageBoundObject):
self.save_session(ctx.session, response) self.save_session(ctx.session, response)
return response return response
def do_teardown_request(self): def do_teardown_request(self, exc=None):
"""Called after the actual request dispatching and will """Called after the actual request dispatching and will
call every as :meth:`teardown_request` decorated function. This is call every as :meth:`teardown_request` decorated function. This is
not actually called by the :class:`Flask` object itself but is always not actually called by the :class:`Flask` object itself but is always
triggered when the request context is popped. That way we have a triggered when the request context is popped. That way we have a
tighter control over certain resources under testing environments. 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, ())) funcs = reversed(self.teardown_request_funcs.get(None, ()))
bp = _request_ctx_stack.top.request.blueprint bp = _request_ctx_stack.top.request.blueprint
if bp is not None and bp in self.teardown_request_funcs: if bp is not None and bp in self.teardown_request_funcs:
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp])) funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
exc = sys.exc_info()[1]
for func in funcs: for func in funcs:
rv = func(exc) rv = func(exc)
if rv is not None: request_tearing_down.send(self, exc=exc)
return rv
request_tearing_down.send(self) 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): def app_context(self):
"""Binds the application only. For as long as the application is bound """Binds the application only. For as long as the application is bound

18
flask/ctx.py

@ -9,6 +9,8 @@
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import sys
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
from .globals import _request_ctx_stack, _app_ctx_stack from .globals import _request_ctx_stack, _app_ctx_stack
@ -86,8 +88,11 @@ class AppContext(object):
"""Binds the app context to the current context.""" """Binds the app context to the current context."""
_app_ctx_stack.push(self) _app_ctx_stack.push(self)
def pop(self): def pop(self, exc=None):
"""Pops the app context.""" """Pops the app context."""
if exc is None:
exc = sys.exc_info()[1]
self.app.do_teardown_appcontext(exc)
rv = _app_ctx_stack.pop() rv = _app_ctx_stack.pop()
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \ assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
% (rv, self) % (rv, self)
@ -197,13 +202,18 @@ class RequestContext(object):
if self.session is None: if self.session is None:
self.session = self.app.make_null_session() 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 """Pops the request context and unbinds it by doing that. This will
also trigger the execution of functions registered by the also trigger the execution of functions registered by the
:meth:`~flask.Flask.teardown_request` decorator. :meth:`~flask.Flask.teardown_request` decorator.
.. versionchanged:: 0.9
Added the `exc` argument.
""" """
self.preserved = False 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() rv = _request_ctx_stack.pop()
assert rv is self, 'Popped wrong request context. (%r instead of %r)' \ assert rv is self, 'Popped wrong request context. (%r instead of %r)' \
% (rv, self) % (rv, self)
@ -231,7 +241,7 @@ class RequestContext(object):
(tb is not None and self.app.preserve_context_on_exception): (tb is not None and self.app.preserve_context_on_exception):
self.preserved = True self.preserved = True
else: else:
self.pop() self.pop(exc_value)
def __repr__(self): def __repr__(self):
return '<%s \'%s\' [%s] of %s>' % ( return '<%s \'%s\' [%s] of %s>' % (

1
flask/signals.py

@ -49,3 +49,4 @@ request_started = _signals.signal('request-started')
request_finished = _signals.signal('request-finished') request_finished = _signals.signal('request-finished')
request_tearing_down = _signals.signal('request-tearing-down') request_tearing_down = _signals.signal('request-tearing-down')
got_request_exception = _signals.signal('got-request-exception') got_request_exception = _signals.signal('got-request-exception')
appcontext_tearing_down = _signals.signal('appcontext-tearing-down')

12
flask/testsuite/appctx.py

@ -53,6 +53,18 @@ class AppContextTestCase(FlaskTestCase):
self.assert_equal(flask.current_app._get_current_object(), app) self.assert_equal(flask.current_app._get_current_object(), app)
self.assert_equal(flask._app_ctx_stack.top, None) 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(): def suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()

Loading…
Cancel
Save