Fixed request context preservation and teardown handler interaction.
This commit is contained in:
parent
6dfe933260
commit
1b40b3b573
4 changed files with 59 additions and 1 deletions
3
CHANGES
3
CHANGES
|
|
@ -62,6 +62,9 @@ Release date to be decided.
|
|||
- Changed how the teardown system is informed about exceptions. This is now
|
||||
more reliable in case something handles an exception halfway through
|
||||
the error handling process.
|
||||
- Request context preservation in debug mode now keeps the exception
|
||||
information around which means that teardown handlers are able to
|
||||
distinguish error from success cases.
|
||||
- Added the ``JSONIFY_PRETTYPRINT_REGULAR`` configuration variable.
|
||||
- Flask now orders JSON keys by default to not trash HTTP caches due to
|
||||
different hash seeds between different workers.
|
||||
|
|
|
|||
|
|
@ -1707,6 +1707,13 @@ class Flask(_PackageBoundObject):
|
|||
rv = func(exc)
|
||||
request_tearing_down.send(self, exc=exc)
|
||||
|
||||
# If this interpreter supports clearing the exception information
|
||||
# we do that now. This will only go into effect on Python 2.x,
|
||||
# on 3.x it disappears automatically at the end of the exception
|
||||
# stack.
|
||||
if hasattr(sys, 'exc_clear'):
|
||||
sys.exc_clear()
|
||||
|
||||
def do_teardown_appcontext(self, exc=None):
|
||||
"""Called when an application context is popped. This works pretty
|
||||
much the same as :meth:`do_teardown_request` but for the application
|
||||
|
|
|
|||
16
flask/ctx.py
16
flask/ctx.py
|
|
@ -235,6 +235,10 @@ class RequestContext(object):
|
|||
# is pushed the preserved context is popped.
|
||||
self.preserved = False
|
||||
|
||||
# remembers the exception for pop if there is one in case the context
|
||||
# preservation kicks in.
|
||||
self._preserved_exc = None
|
||||
|
||||
# Functions that should be executed after the request on the response
|
||||
# object. These will be called before the regular "after_request"
|
||||
# functions.
|
||||
|
|
@ -296,7 +300,7 @@ class RequestContext(object):
|
|||
# functionality is not active in production environments.
|
||||
top = _request_ctx_stack.top
|
||||
if top is not None and top.preserved:
|
||||
top.pop()
|
||||
top.pop(top._preserved_exc)
|
||||
|
||||
# Before we push the request context we have to ensure that there
|
||||
# is an application context.
|
||||
|
|
@ -331,9 +335,18 @@ class RequestContext(object):
|
|||
clear_request = False
|
||||
if not self._implicit_app_ctx_stack:
|
||||
self.preserved = False
|
||||
self._preserved_exc = None
|
||||
if exc is None:
|
||||
exc = sys.exc_info()[1]
|
||||
self.app.do_teardown_request(exc)
|
||||
|
||||
# If this interpreter supports clearing the exception information
|
||||
# we do that now. This will only go into effect on Python 2.x,
|
||||
# on 3.x it disappears automatically at the end of the exception
|
||||
# stack.
|
||||
if hasattr(sys, 'exc_clear'):
|
||||
sys.exc_clear()
|
||||
|
||||
request_close = getattr(self.request, 'close', None)
|
||||
if request_close is not None:
|
||||
request_close()
|
||||
|
|
@ -356,6 +369,7 @@ class RequestContext(object):
|
|||
if self.request.environ.get('flask._preserve_context') or \
|
||||
(exc is not None and self.app.preserve_context_on_exception):
|
||||
self.preserved = True
|
||||
self._preserved_exc = exc
|
||||
else:
|
||||
self.pop(exc)
|
||||
|
||||
|
|
|
|||
|
|
@ -1070,6 +1070,40 @@ class BasicFunctionalityTestCase(FlaskTestCase):
|
|||
self.assert_true(flask._request_ctx_stack.top is None)
|
||||
self.assert_true(flask._app_ctx_stack.top is None)
|
||||
|
||||
def test_preserve_remembers_exception(self):
|
||||
app = flask.Flask(__name__)
|
||||
app.debug = True
|
||||
errors = []
|
||||
|
||||
@app.route('/fail')
|
||||
def fail_func():
|
||||
1 // 0
|
||||
|
||||
@app.route('/success')
|
||||
def success_func():
|
||||
return 'Okay'
|
||||
|
||||
@app.teardown_request
|
||||
def teardown_handler(exc):
|
||||
errors.append(exc)
|
||||
|
||||
c = app.test_client()
|
||||
|
||||
# After this failure we did not yet call the teardown handler
|
||||
with self.assert_raises(ZeroDivisionError):
|
||||
c.get('/fail')
|
||||
self.assert_equal(errors, [])
|
||||
|
||||
# But this request triggers it, and it's an error
|
||||
c.get('/success')
|
||||
self.assert_equal(len(errors), 2)
|
||||
self.assert_true(isinstance(errors[0], ZeroDivisionError))
|
||||
|
||||
# At this point another request does nothing.
|
||||
c.get('/success')
|
||||
self.assert_equal(len(errors), 3)
|
||||
self.assert_equal(errors[1], None)
|
||||
|
||||
|
||||
class SubdomainTestCase(FlaskTestCase):
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue