Added documentation for appcontext and teardown handlers

This commit is contained in:
Armin Ronacher 2012-04-09 17:29:00 +01:00
parent 34bbd3100b
commit 9bed20c07c
10 changed files with 287 additions and 64 deletions

View file

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

View file

@ -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>' % (

View file

@ -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')

View file

@ -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()