Browse Source

Added support for signals

pull/112/head
Armin Ronacher 14 years ago
parent
commit
e0712b47c6
  1. 5
      CHANGES
  2. 5
      Makefile
  3. 51
      docs/api.rst
  4. 3
      docs/conf.py
  5. 4
      flask/__init__.py
  6. 4
      flask/app.py
  7. 50
      flask/signals.py
  8. 14
      flask/templating.py
  9. 79
      tests/flask_tests.py
  10. 1
      tests/templates/simple_template.html

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

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

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

3
docs/conf.py

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

4
flask/__init__.py

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

4
flask/app.py

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

50
flask/signals.py

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

14
flask/templating.py

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

79
tests/flask_tests.py

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

1
tests/templates/simple_template.html

@ -0,0 +1 @@
<h1>{{ whiskey }}</h1>
Loading…
Cancel
Save