forked from orbit-oss/flask
Add teardown_request decorator. Fixes issue #174
This commit is contained in:
parent
3deae1bd48
commit
04e70bd5c7
4 changed files with 136 additions and 4 deletions
2
CHANGES
2
CHANGES
|
|
@ -37,6 +37,8 @@ Release date to be announced, codename to be selected
|
||||||
was incorrectly introduced in 0.6.
|
was incorrectly introduced in 0.6.
|
||||||
- Added `create_jinja_loader` to override the loader creation process.
|
- Added `create_jinja_loader` to override the loader creation process.
|
||||||
- Implemented a silent flag for `config.from_pyfile`.
|
- Implemented a silent flag for `config.from_pyfile`.
|
||||||
|
- Added `teardown_request` decorator, for functions that should run at the end
|
||||||
|
of a request regardless of whether an exception occurred.
|
||||||
|
|
||||||
Version 0.6.1
|
Version 0.6.1
|
||||||
-------------
|
-------------
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,11 @@ but how can we elegantly do that for requests? We will need the database
|
||||||
connection in all our functions so it makes sense to initialize them
|
connection in all our functions so it makes sense to initialize them
|
||||||
before each request and shut them down afterwards.
|
before each request and shut them down afterwards.
|
||||||
|
|
||||||
Flask allows us to do that with the :meth:`~flask.Flask.before_request` and
|
Flask allows us to do that with the :meth:`~flask.Flask.before_request`,
|
||||||
:meth:`~flask.Flask.after_request` decorators::
|
:meth:`~flask.Flask.after_request` and :meth:`~flask.Flask.teardown_request`
|
||||||
|
decorators. In debug mode, if an error is raised,
|
||||||
|
:meth:`~flask.Flask.after_request` won't be run, and you'll have access to the
|
||||||
|
db connection in the interactive debugger::
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def before_request():
|
def before_request():
|
||||||
|
|
@ -20,13 +23,29 @@ Flask allows us to do that with the :meth:`~flask.Flask.before_request` and
|
||||||
g.db.close()
|
g.db.close()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
If you want to guarantee that the connection is always closed in debug mode, you
|
||||||
|
can close it in a function decorated with :meth:`~flask.Flask.teardown_request`:
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def before_request():
|
||||||
|
g.db = connect_db()
|
||||||
|
|
||||||
|
@app.teardown_request
|
||||||
|
def teardown_request(exception):
|
||||||
|
g.db.close()
|
||||||
|
|
||||||
Functions marked with :meth:`~flask.Flask.before_request` are called before
|
Functions marked with :meth:`~flask.Flask.before_request` are called before
|
||||||
a request and passed no arguments, functions marked with
|
a request and passed no arguments. Functions marked with
|
||||||
:meth:`~flask.Flask.after_request` are called after a request and
|
:meth:`~flask.Flask.after_request` are called after a request and
|
||||||
passed the response that will be sent to the client. They have to return
|
passed the response that will be sent to the client. They have to return
|
||||||
that response object or a different one. In this case we just return it
|
that response object or a different one. In this case we just return it
|
||||||
unchanged.
|
unchanged.
|
||||||
|
|
||||||
|
Functions marked with :meth:`~flask.Flask.teardown_request` get called after the
|
||||||
|
response has been constructed. They are not allowed to modify the request, and
|
||||||
|
their return values are ignored. If an exception occurred while the request was
|
||||||
|
being processed, it is passed to each function; otherwise, None is passed in.
|
||||||
|
|
||||||
We store our current database connection on the special :data:`~flask.g`
|
We store our current database connection on the special :data:`~flask.g`
|
||||||
object that flask provides for us. This object stores information for one
|
object that flask provides for us. This object stores information for one
|
||||||
request only and is available from within each function. Never store such
|
request only and is available from within each function. Never store such
|
||||||
|
|
|
||||||
39
flask/app.py
39
flask/app.py
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import sys
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from datetime import timedelta, datetime
|
from datetime import timedelta, datetime
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
@ -247,6 +248,18 @@ class Flask(_PackageBoundObject):
|
||||||
#: :meth:`after_request` decorator.
|
#: :meth:`after_request` decorator.
|
||||||
self.after_request_funcs = {}
|
self.after_request_funcs = {}
|
||||||
|
|
||||||
|
#: A dictionary with lists of functions that are called after
|
||||||
|
#: each request, even if an exception has occurred. The key of the
|
||||||
|
#: dictionary is the name of the module this function is active for,
|
||||||
|
#: `None` for all requests. These functions are not allowed to modify
|
||||||
|
#: the request, and their return values are ignored. If an exception
|
||||||
|
#: occurred while processing the request, it gets passed to each
|
||||||
|
#: teardown_request function. To register a function here, use the
|
||||||
|
#: :meth:`teardown_request` decorator.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.7
|
||||||
|
self.teardown_request_funcs = {}
|
||||||
|
|
||||||
#: A dictionary with list of functions that are called without argument
|
#: A dictionary with list of functions that are called without argument
|
||||||
#: to populate the template context. The key of the dictionary is the
|
#: to populate the template context. The key of the dictionary is the
|
||||||
#: name of the module this function is active for, `None` for all
|
#: name of the module this function is active for, `None` for all
|
||||||
|
|
@ -704,6 +717,11 @@ class Flask(_PackageBoundObject):
|
||||||
self.after_request_funcs.setdefault(None, []).append(f)
|
self.after_request_funcs.setdefault(None, []).append(f)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
def teardown_request(self, f):
|
||||||
|
"""Register a function to be run at the end of each request, regardless of whether there was an exception or not."""
|
||||||
|
self.teardown_request_funcs.setdefault(None, []).append(f)
|
||||||
|
return f
|
||||||
|
|
||||||
def context_processor(self, f):
|
def context_processor(self, f):
|
||||||
"""Registers a template context processor function."""
|
"""Registers a template context processor function."""
|
||||||
self.template_context_processors[None].append(f)
|
self.template_context_processors[None].append(f)
|
||||||
|
|
@ -869,6 +887,20 @@ class Flask(_PackageBoundObject):
|
||||||
response = handler(response)
|
response = handler(response)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def do_teardown_request(self):
|
||||||
|
"""Called after the actual request dispatching and will
|
||||||
|
call every as :meth:`teardown_request` decorated function.
|
||||||
|
"""
|
||||||
|
funcs = reversed(self.teardown_request_funcs.get(None, ()))
|
||||||
|
mod = request.module
|
||||||
|
if mod and mod in self.teardown_request_funcs:
|
||||||
|
funcs = chain(funcs, reversed(self.teardown_request_funcs[mod]))
|
||||||
|
exc = sys.exc_info()[1]
|
||||||
|
for func in funcs:
|
||||||
|
rv = func(exc)
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
|
||||||
def request_context(self, environ):
|
def request_context(self, environ):
|
||||||
"""Creates a request context from the given environment and binds
|
"""Creates a request context from the given environment and binds
|
||||||
it to the current context. This must be used in combination with
|
it to the current context. This must be used in combination with
|
||||||
|
|
@ -947,6 +979,11 @@ class Flask(_PackageBoundObject):
|
||||||
even if an exception happens database have the chance to
|
even if an exception happens database have the chance to
|
||||||
properly close the connection.
|
properly close the connection.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.7
|
||||||
|
The :meth:`teardown_request` functions get called at the very end of
|
||||||
|
processing the request. If an exception was thrown, it gets passed to
|
||||||
|
each teardown_request function.
|
||||||
|
|
||||||
:param environ: a WSGI environment
|
:param environ: a WSGI environment
|
||||||
:param start_response: a callable accepting a status code,
|
:param start_response: a callable accepting a status code,
|
||||||
a list of headers and an optional
|
a list of headers and an optional
|
||||||
|
|
@ -965,6 +1002,8 @@ class Flask(_PackageBoundObject):
|
||||||
response = self.process_response(response)
|
response = self.process_response(response)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
response = self.make_response(self.handle_exception(e))
|
response = self.make_response(self.handle_exception(e))
|
||||||
|
finally:
|
||||||
|
self.do_teardown_request()
|
||||||
request_finished.send(self, response=response)
|
request_finished.send(self, response=response)
|
||||||
return response(environ, start_response)
|
return response(environ, start_response)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -413,6 +413,72 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
assert 'Internal Server Error' in rv.data
|
assert 'Internal Server Error' in rv.data
|
||||||
assert len(called) == 1
|
assert len(called) == 1
|
||||||
|
|
||||||
|
def test_teardown_request_handler(self):
|
||||||
|
called = []
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
@app.teardown_request
|
||||||
|
def teardown_request(exc):
|
||||||
|
called.append(True)
|
||||||
|
return "Ignored"
|
||||||
|
@app.route('/')
|
||||||
|
def root():
|
||||||
|
return "Response"
|
||||||
|
rv = app.test_client().get('/')
|
||||||
|
assert rv.status_code == 200
|
||||||
|
assert 'Response' in rv.data
|
||||||
|
assert len(called) == 1
|
||||||
|
|
||||||
|
def test_teardown_request_handler_debug_mode(self):
|
||||||
|
called = []
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.debug = True
|
||||||
|
@app.teardown_request
|
||||||
|
def teardown_request(exc):
|
||||||
|
called.append(True)
|
||||||
|
return "Ignored"
|
||||||
|
@app.route('/')
|
||||||
|
def root():
|
||||||
|
return "Response"
|
||||||
|
rv = app.test_client().get('/')
|
||||||
|
assert rv.status_code == 200
|
||||||
|
assert 'Response' in rv.data
|
||||||
|
assert len(called) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_teardown_request_handler_error(self):
|
||||||
|
called = []
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
@app.teardown_request
|
||||||
|
def teardown_request1(exc):
|
||||||
|
assert type(exc) == ZeroDivisionError
|
||||||
|
called.append(True)
|
||||||
|
# This raises a new error and blows away sys.exc_info(), so we can
|
||||||
|
# test that all teardown_requests get passed the same original
|
||||||
|
# exception.
|
||||||
|
try:
|
||||||
|
raise TypeError
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
@app.teardown_request
|
||||||
|
def teardown_request2(exc):
|
||||||
|
assert type(exc) == ZeroDivisionError
|
||||||
|
called.append(True)
|
||||||
|
# This raises a new error and blows away sys.exc_info(), so we can
|
||||||
|
# test that all teardown_requests get passed the same original
|
||||||
|
# exception.
|
||||||
|
try:
|
||||||
|
raise TypeError
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
@app.route('/')
|
||||||
|
def fails():
|
||||||
|
1/0
|
||||||
|
rv = app.test_client().get('/')
|
||||||
|
assert rv.status_code == 500
|
||||||
|
assert 'Internal Server Error' in rv.data
|
||||||
|
assert len(called) == 2
|
||||||
|
|
||||||
|
|
||||||
def test_before_after_request_order(self):
|
def test_before_after_request_order(self):
|
||||||
called = []
|
called = []
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
@ -430,12 +496,18 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
def after2(response):
|
def after2(response):
|
||||||
called.append(3)
|
called.append(3)
|
||||||
return response
|
return response
|
||||||
|
@app.teardown_request
|
||||||
|
def finish1(exc):
|
||||||
|
called.append(6)
|
||||||
|
@app.teardown_request
|
||||||
|
def finish2(exc):
|
||||||
|
called.append(5)
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
return '42'
|
return '42'
|
||||||
rv = app.test_client().get('/')
|
rv = app.test_client().get('/')
|
||||||
assert rv.data == '42'
|
assert rv.data == '42'
|
||||||
assert called == [1, 2, 3, 4]
|
assert called == [1, 2, 3, 4, 5, 6]
|
||||||
|
|
||||||
def test_error_handling(self):
|
def test_error_handling(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue