forked from orbit-oss/flask
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
|
- :meth:`~flask.Flask.after_request` handlers are now also invoked
|
||||||
if the request dies with an exception and an error handling page
|
if the request dies with an exception and an error handling page
|
||||||
kicks in.
|
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
|
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
|
all the context local objects used in Flask. This is a documented
|
||||||
instance and can be used by extensions and application code but the
|
instance and can be used by extensions and application code but the
|
||||||
use is discouraged in general.
|
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
|
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
|
there does not seem to be a good way to do that, consider switching to
|
||||||
application factories (see :ref:`app-factories`).
|
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):
|
def __exit__(self, exc_type, exc_value, tb):
|
||||||
# do not pop the request stack if we are in debug mode and an
|
# do not pop the request stack if we are in debug mode and an
|
||||||
# exception happened. This will allow the debugger to still
|
# exception happened. This will allow the debugger to still
|
||||||
# access the request object in the interactive shell.
|
# access the request object in the interactive shell. Furthermore
|
||||||
if tb is None or not self.app.debug:
|
# 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()
|
self.pop()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1021,9 +1023,40 @@ class Flask(_PackageBoundObject):
|
||||||
def test_client(self):
|
def test_client(self):
|
||||||
"""Creates a test client for this application. For information
|
"""Creates a test client for this application. For information
|
||||||
about unit testing head over to :ref:`testing`.
|
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
|
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):
|
def open_session(self, request):
|
||||||
"""Creates or opens a new session. Default implementation stores all
|
"""Creates or opens a new session. Default implementation stores all
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ class ContextTestCase(unittest.TestCase):
|
||||||
assert index() == 'Hello World!'
|
assert index() == 'Hello World!'
|
||||||
with app.test_request_context('/meh'):
|
with app.test_request_context('/meh'):
|
||||||
assert meh() == 'http://localhost/meh'
|
assert meh() == 'http://localhost/meh'
|
||||||
|
assert flask._request_ctx_stack.top is None
|
||||||
|
|
||||||
def test_manual_context_binding(self):
|
def test_manual_context_binding(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
@ -76,6 +77,36 @@ class ContextTestCase(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
assert 0, 'expected runtime error'
|
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):
|
class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue