Browse Source

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.

pull/124/head
Armin Ronacher 15 years ago
parent
commit
bc00fd1e83
  1. 3
      CHANGES
  2. 17
      docs/api.rst
  3. 24
      docs/testing.rst
  4. 39
      flask.py
  5. 31
      tests/flask_tests.py

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

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

24
docs/testing.rst

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

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

31
tests/flask_tests.py

@ -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…
Cancel
Save