mirror of https://github.com/pallets/flask.git
Added support for signals
This commit is contained in:
parent
a59dfe4a77
commit
e0712b47c6
5
CHANGES
5
CHANGES
|
@ -28,6 +28,11 @@ Release date to be announced, codename to be decided.
|
|||
same name on the application object.
|
||||
- added a :func:`flask.make_response` function that simplifies
|
||||
creating response object instances in views.
|
||||
- added signalling support based on blinker. This feature is currently
|
||||
optional and supposed to be used by extensions and applications. If
|
||||
you want to use it, make sure to have `blinker`_ installed.
|
||||
|
||||
.. _blinker: http://pypi.python.org/pypi/blinker
|
||||
|
||||
Version 0.5.2
|
||||
-------------
|
||||
|
|
5
Makefile
5
Makefile
|
@ -1,4 +1,4 @@
|
|||
.PHONY: clean-pyc test upload-docs
|
||||
.PHONY: clean-pyc test upload-docs docs
|
||||
|
||||
all: clean-pyc test
|
||||
|
||||
|
@ -20,3 +20,6 @@ upload-docs:
|
|||
scp -r docs/_build/dirhtml/* pocoo.org:/var/www/flask.pocoo.org/docs/
|
||||
scp -r docs/_build/latex/Flask.pdf pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.pdf
|
||||
scp -r docs/_build/flask-docs.zip pocoo.org:/var/www/flask.pocoo.org/docs/
|
||||
|
||||
docs:
|
||||
$(MAKE) -C docs html
|
||||
|
|
51
docs/api.rst
51
docs/api.rst
|
@ -351,3 +351,54 @@ Useful Internals
|
|||
information from the context local around for a little longer. Make
|
||||
sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
|
||||
that situation, otherwise your unittests will leak memory.
|
||||
|
||||
Signals
|
||||
-------
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
.. data:: signals_available
|
||||
|
||||
`True` if the signalling system is available. This is the case
|
||||
when `blinker`_ is installed.
|
||||
|
||||
.. data:: template_rendered
|
||||
|
||||
This signal is sent when a template was successfully rendered. The
|
||||
signal is invoked with the instance of the template as `template`
|
||||
and the context as dictionary (named `context`).
|
||||
|
||||
.. data:: request_started
|
||||
|
||||
This signal is sent before any request processing started but when the
|
||||
request context was set up. Because the request context is already
|
||||
bound, the subscriber can access the request with the standard global
|
||||
proxies such as :class:`~flask.request`.
|
||||
|
||||
.. data:: request_finished
|
||||
|
||||
This signal is sent right before the response is sent to the client.
|
||||
It is passed the response to be sent named `response`.
|
||||
|
||||
.. data:: got_request_exception
|
||||
|
||||
This signal is sent when an exception happens during request processing.
|
||||
It is sent *before* the standard exception handling kicks in and even
|
||||
in debug mode, where no exception handling happens. The exception
|
||||
itself is passed to the subscriber as `exception`.
|
||||
|
||||
.. class:: flask.signals.Namespace
|
||||
|
||||
An alias for :class:`blinker.base.Namespace` if blinker is available,
|
||||
otherwise a dummy class that creates fake signals. This class is
|
||||
available for Flask extensions that want to provide the same fallback
|
||||
system as Flask itself.
|
||||
|
||||
.. method:: signal(name, doc=None)
|
||||
|
||||
Creates a new signal for this namespace if blinker is available,
|
||||
otherwise returns a fake signal that has a send method that will
|
||||
do nothing but will fail with a :exc:`RuntimeError` for all other
|
||||
operations, including connecting.
|
||||
|
||||
.. _blinker: http://pypi.python.org/pypi/blinker
|
||||
|
|
|
@ -245,7 +245,8 @@ intersphinx_mapping = {
|
|||
'http://docs.python.org/dev': None,
|
||||
'http://werkzeug.pocoo.org/documentation/dev/': None,
|
||||
'http://www.sqlalchemy.org/docs/': None,
|
||||
'http://wtforms.simplecodes.com/docs/0.5/': None
|
||||
'http://wtforms.simplecodes.com/docs/0.5/': None,
|
||||
'http://discorporate.us/projects/Blinker/docs/1.0/': None
|
||||
}
|
||||
|
||||
pygments_style = 'flask_theme_support.FlaskyStyle'
|
||||
|
|
|
@ -24,6 +24,10 @@ from .globals import current_app, g, request, session, _request_ctx_stack
|
|||
from .module import Module
|
||||
from .templating import render_template, render_template_string
|
||||
|
||||
# the signals
|
||||
from .signals import signals_available, template_rendered, request_started, \
|
||||
request_finished, got_request_exception
|
||||
|
||||
# only import json if it's available
|
||||
if json_available:
|
||||
from .helpers import json
|
||||
|
|
|
@ -32,6 +32,7 @@ from .session import Session, _NullSession
|
|||
from .module import _ModuleSetupState
|
||||
from .templating import _DispatchingJinjaLoader, \
|
||||
_default_template_ctx_processor
|
||||
from .signals import request_started, request_finished, got_request_exception
|
||||
|
||||
# a lock used for logger initialization
|
||||
_logger_lock = Lock()
|
||||
|
@ -657,6 +658,7 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
.. versionadded: 0.3
|
||||
"""
|
||||
got_request_exception.send(self, exception=e)
|
||||
handler = self.error_handlers.get(500)
|
||||
if self.debug:
|
||||
raise
|
||||
|
@ -791,6 +793,7 @@ class Flask(_PackageBoundObject):
|
|||
"""
|
||||
with self.request_context(environ):
|
||||
try:
|
||||
request_started.send(self)
|
||||
rv = self.preprocess_request()
|
||||
if rv is None:
|
||||
rv = self.dispatch_request()
|
||||
|
@ -801,6 +804,7 @@ class Flask(_PackageBoundObject):
|
|||
response = self.process_response(response)
|
||||
except Exception, e:
|
||||
response = self.make_response(self.handle_exception(e))
|
||||
request_finished.send(self, response=response)
|
||||
return response(environ, start_response)
|
||||
|
||||
def request_context(self, environ):
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.signals
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Implements signals based on blinker if available, otherwise
|
||||
falls silently back to a noop
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
signals_available = False
|
||||
try:
|
||||
from blinker import Namespace
|
||||
signals_available = True
|
||||
_signals = Namespace()
|
||||
except ImportError:
|
||||
class Namespace(object):
|
||||
def signal(self, name, doc=None):
|
||||
return _FakeSignal(name, doc)
|
||||
class _FakeSignal(object):
|
||||
"""If blinker is unavailable, create a fake class with the same
|
||||
interface that allows sending of signals but will fail with an
|
||||
error on anything else. Instead of doing anything on send, it
|
||||
will just ignore the arguments and do nothing instead.
|
||||
"""
|
||||
|
||||
def __init__(self, name, doc=None):
|
||||
self.name = name
|
||||
self.__doc__ = doc
|
||||
def _fail(self, *args, **kwargs):
|
||||
raise RuntimeError('signalling support is unavailable '
|
||||
'because the blinker library is '
|
||||
'not installed.')
|
||||
send = lambda *a, **kw: None
|
||||
connect = disconnect = has_receivers_for = receivers_for = \
|
||||
temporarily_connected_to = _fail
|
||||
del _fail
|
||||
|
||||
# the namespace for code signals. If you are not flask code, do
|
||||
# not put signals in here. Create your own namespace instead.
|
||||
_signals = Namespace()
|
||||
|
||||
|
||||
# core signals. For usage examples grep the sourcecode or consult
|
||||
# the API documentation in docs/api.rst as well as docs/signals.rst
|
||||
template_rendered = _signals.signal('template-rendered')
|
||||
request_started = _signals.signal('request-started')
|
||||
request_finished = _signals.signal('request-finished')
|
||||
got_request_exception = _signals.signal('got-request-exception')
|
|
@ -11,6 +11,7 @@
|
|||
from jinja2 import BaseLoader, FileSystemLoader, TemplateNotFound
|
||||
|
||||
from .globals import _request_ctx_stack
|
||||
from .signals import template_rendered
|
||||
|
||||
|
||||
def _default_template_ctx_processor():
|
||||
|
@ -59,6 +60,13 @@ class _DispatchingJinjaLoader(BaseLoader):
|
|||
return result
|
||||
|
||||
|
||||
def _render(template, context, app):
|
||||
"""Renders the template and fires the signal"""
|
||||
rv = template.render(context)
|
||||
template_rendered.send(app, template=template, context=context)
|
||||
return rv
|
||||
|
||||
|
||||
def render_template(template_name, **context):
|
||||
"""Renders a template from the template folder with the given
|
||||
context.
|
||||
|
@ -69,7 +77,8 @@ def render_template(template_name, **context):
|
|||
"""
|
||||
ctx = _request_ctx_stack.top
|
||||
ctx.app.update_template_context(context)
|
||||
return ctx.app.jinja_env.get_template(template_name).render(context)
|
||||
return _render(ctx.app.jinja_env.get_template(template_name),
|
||||
context, ctx.app)
|
||||
|
||||
|
||||
def render_template_string(source, **context):
|
||||
|
@ -83,4 +92,5 @@ def render_template_string(source, **context):
|
|||
"""
|
||||
ctx = _request_ctx_stack.top
|
||||
ctx.app.update_template_context(context)
|
||||
return ctx.app.jinja_env.from_string(source).render(context)
|
||||
return _render(ctx.app.jinja_env.from_string(source),
|
||||
context, ctx.app)
|
||||
|
|
|
@ -1024,6 +1024,83 @@ class SubdomainTestCase(unittest.TestCase):
|
|||
assert rv.data == 'index for mitsuhiko'
|
||||
|
||||
|
||||
class TestSignals(unittest.TestCase):
|
||||
|
||||
def test_template_rendered(self):
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return flask.render_template('simple_template.html', whiskey=42)
|
||||
|
||||
recorded = []
|
||||
def record(sender, template, context):
|
||||
recorded.append((template, context))
|
||||
|
||||
with flask.template_rendered.temporarily_connected_to(record, app):
|
||||
rv = app.test_client().get('/')
|
||||
assert len(recorded) == 1
|
||||
template, context = recorded[0]
|
||||
assert template.name == 'simple_template.html'
|
||||
assert context['whiskey'] == 42
|
||||
|
||||
def test_request_signals(self):
|
||||
app = flask.Flask(__name__)
|
||||
calls = []
|
||||
|
||||
def before_request_signal(sender):
|
||||
calls.append('before-signal')
|
||||
|
||||
def after_request_signal(sender, response):
|
||||
assert response.data == 'stuff'
|
||||
calls.append('after-signal')
|
||||
|
||||
@app.before_request
|
||||
def before_request_handler():
|
||||
calls.append('before-handler')
|
||||
|
||||
@app.after_request
|
||||
def after_request_handler(response):
|
||||
calls.append('after-handler')
|
||||
response.data = 'stuff'
|
||||
return response
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
calls.append('handler')
|
||||
return 'ignored anyway'
|
||||
|
||||
flask.request_started.connect(before_request_signal, app)
|
||||
flask.request_finished.connect(after_request_signal, app)
|
||||
|
||||
try:
|
||||
rv = app.test_client().get('/')
|
||||
assert rv.data == 'stuff'
|
||||
|
||||
assert calls == ['before-signal', 'before-handler',
|
||||
'handler', 'after-handler',
|
||||
'after-signal']
|
||||
finally:
|
||||
flask.request_started.disconnect(before_request_signal, app)
|
||||
flask.request_finished.disconnect(after_request_signal, app)
|
||||
|
||||
def test_request_exception_signal(self):
|
||||
app = flask.Flask(__name__)
|
||||
recorded = []
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
1/0
|
||||
|
||||
def record(sender, exception):
|
||||
recorded.append(exception)
|
||||
|
||||
with flask.got_request_exception.temporarily_connected_to(record):
|
||||
assert app.test_client().get('/').status_code == 500
|
||||
assert len(recorded) == 1
|
||||
assert isinstance(recorded[0], ZeroDivisionError)
|
||||
|
||||
|
||||
def suite():
|
||||
from minitwit_tests import MiniTwitTestCase
|
||||
from flaskr_tests import FlaskrTestCase
|
||||
|
@ -1038,6 +1115,8 @@ def suite():
|
|||
suite.addTest(unittest.makeSuite(SubdomainTestCase))
|
||||
if flask.json_available:
|
||||
suite.addTest(unittest.makeSuite(JSONTestCase))
|
||||
if flask.signals_available:
|
||||
suite.addTest(unittest.makeSuite(TestSignals))
|
||||
suite.addTest(unittest.makeSuite(MiniTwitTestCase))
|
||||
suite.addTest(unittest.makeSuite(FlaskrTestCase))
|
||||
return suite
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<h1>{{ whiskey }}</h1>
|
Loading…
Reference in New Issue