diff --git a/CHANGES b/CHANGES index ff0a3e83..87e4911b 100644 --- a/CHANGES +++ b/CHANGES @@ -70,6 +70,7 @@ Release date to be decided. - Added the ``JSONIFY_PRETTYPRINT_REGULAR`` configuration variable. - Flask now orders JSON keys by default to not trash HTTP caches due to different hash seeds between different workers. +- Added `appcontext_pushed` and `appcontext_popped` signals. Version 0.9 ----------- diff --git a/docs/api.rst b/docs/api.rst index 096741ae..c9bb1a85 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -535,7 +535,22 @@ Signals 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. + teardown. The sender is the application. + +.. data:: appcontext_pushed + + This signal is sent when an application context is pushed. The sender + is the application. + + .. versionadded:: 0.10 + +.. data:: appcontext_popped + + This signal is sent when an application context is popped. The sender + is the application. This usually falls in line with the + :data:`appcontext_tearing_down` signal. + + .. versionadded:: 0.10 .. data:: message_flashed diff --git a/docs/signals.rst b/docs/signals.rst index 4d96cc14..799b5a91 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -291,6 +291,45 @@ The following signals exist in Flask: 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_pushed + :noindex: + + This signal is sent when an application context is pushed. The sender + is the application. This is usually useful for unittests in order to + temporarily hook in information. For instance it can be used to + set a resource early onto the `g` object. + + Example usage:: + + from contextlib import contextmanager + from flask import appcontext_pushed + + @contextmanager + def user_set(app, user): + def handler(sender, **kwargs): + g.user = user + with appcontext_pushed.connected_to(handler, app): + yield + + And in the testcode:: + + def test_user_me(self): + with user_set(app, 'john'): + c = app.test_client() + resp = c.get('/users/me') + assert resp.data == 'username=john' + + .. versionadded:: 0.10 + +.. data:: appcontext_popped + + This signal is sent when an application context is popped. The sender + is the application. This usually falls in line with the + :data:`appcontext_tearing_down` signal. + + .. versionadded:: 0.10 + + .. data:: flask.message_flashed :noindex: diff --git a/flask/__init__.py b/flask/__init__.py index 978a4a4c..9ec14323 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -34,7 +34,8 @@ from .templating import render_template, render_template_string # the signals from .signals import signals_available, template_rendered, request_started, \ request_finished, got_request_exception, request_tearing_down, \ - appcontext_tearing_down, message_flashed + appcontext_tearing_down, appcontext_pushed, \ + appcontext_popped, message_flashed # We're not exposing the actual json module but a convenient wrapper around # it. diff --git a/flask/ctx.py b/flask/ctx.py index 6ea3158f..19f42047 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -18,6 +18,7 @@ from werkzeug.exceptions import HTTPException from .globals import _request_ctx_stack, _app_ctx_stack from .module import blueprint_is_module +from .signals import appcontext_pushed, appcontext_popped class _AppCtxGlobals(object): @@ -166,6 +167,7 @@ class AppContext(object): """Binds the app context to the current context.""" self._refcnt += 1 _app_ctx_stack.push(self) + appcontext_pushed.send(self.app) def pop(self, exc=None): """Pops the app context.""" @@ -177,6 +179,7 @@ class AppContext(object): rv = _app_ctx_stack.pop() assert rv is self, 'Popped wrong app context. (%r instead of %r)' \ % (rv, self) + appcontext_popped.send(self.app) def __enter__(self): self.push() diff --git a/flask/signals.py b/flask/signals.py index 14b728c6..7bd0385a 100644 --- a/flask/signals.py +++ b/flask/signals.py @@ -50,4 +50,6 @@ 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') +appcontext_pushed = _signals.signal('appcontext-pushed') +appcontext_popped = _signals.signal('appcontext-popped') message_flashed = _signals.signal('message-flashed') diff --git a/flask/testsuite/signals.py b/flask/testsuite/signals.py index 94262f17..e061932d 100644 --- a/flask/testsuite/signals.py +++ b/flask/testsuite/signals.py @@ -96,6 +96,30 @@ class SignalsTestCase(FlaskTestCase): finally: flask.got_request_exception.disconnect(record, app) + def test_appcontext_signals(self): + app = flask.Flask(__name__) + recorded = [] + def record_push(sender, **kwargs): + recorded.append('push') + def record_pop(sender, **kwargs): + recorded.append('push') + + @app.route('/') + def index(): + return 'Hello' + + flask.appcontext_pushed.connect(record_push, app) + flask.appcontext_popped.connect(record_pop, app) + try: + with app.test_client() as c: + rv = c.get('/') + self.assert_equal(rv.data, b'Hello') + self.assert_equal(recorded, ['push']) + self.assert_equal(recorded, ['push', 'pop']) + finally: + flask.appcontext_pushed.disconnect(record_push, app) + flask.appcontext_popped.disconnect(record_pop, app) + def test_flash_signal(self): app = flask.Flask(__name__) app.config['SECRET_KEY'] = 'secret'