Added support for deferred context cleanup. test_client users can now access the context locals after the actual request if the client is used with a with-block. This fixes #59.
This commit is contained in:
parent
937360877b
commit
bc00fd1e83
5 changed files with 111 additions and 3 deletions
3
CHANGES
3
CHANGES
|
|
@ -13,6 +13,9 @@ Release date to be announced, codename to be selected.
|
|||
- :meth:`~flask.Flask.after_request` handlers are now also invoked
|
||||
if the request dies with an exception and an error handling page
|
||||
kicks in.
|
||||
- test client has not the ability to preserve the request context
|
||||
for a little longer. This can also be used to trigger custom
|
||||
requests that do not pop the request stack for testing.
|
||||
|
||||
Version 0.3.1
|
||||
-------------
|
||||
|
|
|
|||
17
docs/api.rst
17
docs/api.rst
|
|
@ -300,3 +300,20 @@ Useful Internals
|
|||
all the context local objects used in Flask. This is a documented
|
||||
instance and can be used by extensions and application code but the
|
||||
use is discouraged in general.
|
||||
|
||||
.. versionchanged:: 0.4
|
||||
|
||||
The request context is automatically popped at the end of the request
|
||||
for you. In debug mode the request context is kept around if
|
||||
exceptions happen so that interactive debuggers have a chance to
|
||||
introspect the data. With 0.4 this can also be forced for requests
|
||||
that did not fail and outside of `DEBUG` mode. By setting
|
||||
``'flask._preserve_context'`` to `True` on the WSGI environment the
|
||||
context will not pop itself at the end of the request. This is used by
|
||||
the :meth:`~flask.Flask.test_client` for example to implement the
|
||||
deferred cleanup functionality.
|
||||
|
||||
You might find this helpful for unittests where you need the
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -218,3 +218,27 @@ All the other objects that are context bound can be used the same.
|
|||
If you want to test your application with different configurations and
|
||||
there does not seem to be a good way to do that, consider switching to
|
||||
application factories (see :ref:`app-factories`).
|
||||
|
||||
|
||||
Keeping the Context Around
|
||||
--------------------------
|
||||
|
||||
.. versionadded:: 0.4
|
||||
|
||||
Sometimes it can be helpful to trigger a regular request but keep the
|
||||
context around for a little longer so that additional introspection can
|
||||
happen. With Flask 0.4 this is possible by using the
|
||||
:meth:`~flask.Flask.test_client` with a `with` block::
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
with app.test_client() as c:
|
||||
rv = c.get('/?foo=42')
|
||||
assert request.args['foo'] == '42'
|
||||
|
||||
If you would just be using the :meth:`~flask.Flask.test_client` without
|
||||
the `with` block, the `assert` would fail with an error because `request`
|
||||
is no longer available (because used outside of an actual request).
|
||||
Keep in mind however that :meth:`~flask.Flask.after_request` functions
|
||||
are already called at that point so your database connection and
|
||||
everything involved is probably already closed down.
|
||||
|
|
|
|||
39
flask.py
39
flask.py
|
|
@ -163,8 +163,10 @@ class _RequestContext(object):
|
|||
def __exit__(self, exc_type, exc_value, tb):
|
||||
# do not pop the request stack if we are in debug mode and an
|
||||
# exception happened. This will allow the debugger to still
|
||||
# access the request object in the interactive shell.
|
||||
if tb is None or not self.app.debug:
|
||||
# access the request object in the interactive shell. Furthermore
|
||||
# the context can be force kept alive for the test client.
|
||||
if not self.request.environ.get('flask._preserve_context') and \
|
||||
(tb is None or not self.app.debug):
|
||||
self.pop()
|
||||
|
||||
|
||||
|
|
@ -1021,9 +1023,40 @@ class Flask(_PackageBoundObject):
|
|||
def test_client(self):
|
||||
"""Creates a test client for this application. For information
|
||||
about unit testing head over to :ref:`testing`.
|
||||
|
||||
The test client can be used in a `with` block to defer the closing down
|
||||
of the context until the end of the `with` block. This is useful if
|
||||
you want to access the context locals for testing::
|
||||
|
||||
with app.test_client() as c:
|
||||
rv = c.get('/?foo=42')
|
||||
assert request.args['foo'] == '42'
|
||||
|
||||
.. versionchanged:: 0.4
|
||||
added support for `with` block usage for the client.
|
||||
"""
|
||||
from werkzeug import Client
|
||||
return Client(self, self.response_class, use_cookies=True)
|
||||
class FlaskClient(Client):
|
||||
preserve_context = context_preserved = False
|
||||
def open(self, *args, **kwargs):
|
||||
if self.context_preserved:
|
||||
_request_ctx_stack.pop()
|
||||
self.context_preserved = False
|
||||
kwargs.setdefault('environ_overrides', {}) \
|
||||
['flask._preserve_context'] = self.preserve_context
|
||||
old = _request_ctx_stack.top
|
||||
try:
|
||||
return Client.open(self, *args, **kwargs)
|
||||
finally:
|
||||
self.context_preserved = _request_ctx_stack.top is not old
|
||||
def __enter__(self):
|
||||
self.preserve_context = True
|
||||
return self
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.preserve_context = False
|
||||
if self.context_preserved:
|
||||
_request_ctx_stack.pop()
|
||||
return FlaskClient(self, self.response_class, use_cookies=True)
|
||||
|
||||
def open_session(self, request):
|
||||
"""Creates or opens a new session. Default implementation stores all
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ class ContextTestCase(unittest.TestCase):
|
|||
assert index() == 'Hello World!'
|
||||
with app.test_request_context('/meh'):
|
||||
assert meh() == 'http://localhost/meh'
|
||||
assert flask._request_ctx_stack.top is None
|
||||
|
||||
def test_manual_context_binding(self):
|
||||
app = flask.Flask(__name__)
|
||||
|
|
@ -76,6 +77,36 @@ class ContextTestCase(unittest.TestCase):
|
|||
else:
|
||||
assert 0, 'expected runtime error'
|
||||
|
||||
def test_test_client_context_binding(self):
|
||||
app = flask.Flask(__name__)
|
||||
@app.route('/')
|
||||
def index():
|
||||
flask.g.value = 42
|
||||
return 'Hello World!'
|
||||
|
||||
@app.route('/other')
|
||||
def other():
|
||||
1/0
|
||||
|
||||
with app.test_client() as c:
|
||||
resp = c.get('/')
|
||||
assert flask.g.value == 42
|
||||
assert resp.data == 'Hello World!'
|
||||
assert resp.status_code == 200
|
||||
|
||||
resp = c.get('/other')
|
||||
assert not hasattr(flask.g, 'value')
|
||||
assert 'Internal Server Error' in resp.data
|
||||
assert resp.status_code == 500
|
||||
flask.g.value = 23
|
||||
|
||||
try:
|
||||
flask.g.value
|
||||
except (AttributeError, RuntimeError):
|
||||
pass
|
||||
else:
|
||||
raise AssertionError('some kind of exception expected')
|
||||
|
||||
|
||||
class BasicFunctionalityTestCase(unittest.TestCase):
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue