Switch away from using None as default value for the exception when tearing down a context.
When an exception has been handled when using the request / app context in a with statement, `sys.exc_info()` will still contain the exception information even though it has been handled already. The `__exit__` methods pass in `None` for the exception value in that case, which needs to be distinguisable from the default value for the `exc` parameter. Use a dedicated singleton sentinel value instead.
This commit is contained in:
parent
ec5811d0a1
commit
ec0d208bc1
4 changed files with 45 additions and 8 deletions
11
flask/app.py
11
flask/app.py
|
|
@ -38,6 +38,9 @@ from ._compat import reraise, string_types, text_type, integer_types
|
|||
# a lock used for logger initialization
|
||||
_logger_lock = Lock()
|
||||
|
||||
# a singleton sentinel value for parameter defaults
|
||||
_sentinel = object()
|
||||
|
||||
|
||||
def _make_timedelta(value):
|
||||
if not isinstance(value, timedelta):
|
||||
|
|
@ -1774,7 +1777,7 @@ class Flask(_PackageBoundObject):
|
|||
self.save_session(ctx.session, response)
|
||||
return response
|
||||
|
||||
def do_teardown_request(self, exc=None):
|
||||
def do_teardown_request(self, exc=_sentinel):
|
||||
"""Called after the actual request dispatching and will
|
||||
call every as :meth:`teardown_request` decorated function. This is
|
||||
not actually called by the :class:`Flask` object itself but is always
|
||||
|
|
@ -1785,7 +1788,7 @@ class Flask(_PackageBoundObject):
|
|||
Added the `exc` argument. Previously this was always using the
|
||||
current exception information.
|
||||
"""
|
||||
if exc is None:
|
||||
if exc is _sentinel:
|
||||
exc = sys.exc_info()[1]
|
||||
funcs = reversed(self.teardown_request_funcs.get(None, ()))
|
||||
bp = _request_ctx_stack.top.request.blueprint
|
||||
|
|
@ -1795,14 +1798,14 @@ class Flask(_PackageBoundObject):
|
|||
func(exc)
|
||||
request_tearing_down.send(self, exc=exc)
|
||||
|
||||
def do_teardown_appcontext(self, exc=None):
|
||||
def do_teardown_appcontext(self, exc=_sentinel):
|
||||
"""Called when an application context is popped. This works pretty
|
||||
much the same as :meth:`do_teardown_request` but for the application
|
||||
context.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
if exc is None:
|
||||
if exc is _sentinel:
|
||||
exc = sys.exc_info()[1]
|
||||
for func in reversed(self.teardown_appcontext_funcs):
|
||||
func(exc)
|
||||
|
|
|
|||
12
flask/ctx.py
12
flask/ctx.py
|
|
@ -21,6 +21,10 @@ from .signals import appcontext_pushed, appcontext_popped
|
|||
from ._compat import BROKEN_PYPY_CTXMGR_EXIT, reraise
|
||||
|
||||
|
||||
# a singleton sentinel value for parameter defaults
|
||||
_sentinel = object()
|
||||
|
||||
|
||||
class _AppCtxGlobals(object):
|
||||
"""A plain object."""
|
||||
|
||||
|
|
@ -168,11 +172,11 @@ class AppContext(object):
|
|||
_app_ctx_stack.push(self)
|
||||
appcontext_pushed.send(self.app)
|
||||
|
||||
def pop(self, exc=None):
|
||||
def pop(self, exc=_sentinel):
|
||||
"""Pops the app context."""
|
||||
self._refcnt -= 1
|
||||
if self._refcnt <= 0:
|
||||
if exc is None:
|
||||
if exc is _sentinel:
|
||||
exc = sys.exc_info()[1]
|
||||
self.app.do_teardown_appcontext(exc)
|
||||
rv = _app_ctx_stack.pop()
|
||||
|
|
@ -320,7 +324,7 @@ class RequestContext(object):
|
|||
if self.session is None:
|
||||
self.session = self.app.make_null_session()
|
||||
|
||||
def pop(self, exc=None):
|
||||
def pop(self, exc=_sentinel):
|
||||
"""Pops the request context and unbinds it by doing that. This will
|
||||
also trigger the execution of functions registered by the
|
||||
:meth:`~flask.Flask.teardown_request` decorator.
|
||||
|
|
@ -334,7 +338,7 @@ class RequestContext(object):
|
|||
if not self._implicit_app_ctx_stack:
|
||||
self.preserved = False
|
||||
self._preserved_exc = None
|
||||
if exc is None:
|
||||
if exc is _sentinel:
|
||||
exc = sys.exc_info()[1]
|
||||
self.app.do_teardown_request(exc)
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,21 @@ def test_app_tearing_down_with_previous_exception():
|
|||
|
||||
assert cleanup_stuff == [None]
|
||||
|
||||
def test_app_tearing_down_with_handled_exception():
|
||||
cleanup_stuff = []
|
||||
app = flask.Flask(__name__)
|
||||
@app.teardown_appcontext
|
||||
def cleanup(exception):
|
||||
cleanup_stuff.append(exception)
|
||||
|
||||
with app.app_context():
|
||||
try:
|
||||
raise Exception('dummy')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
assert cleanup_stuff == [None]
|
||||
|
||||
def test_custom_app_ctx_globals_class():
|
||||
class CustomRequestGlobals(object):
|
||||
def __init__(self):
|
||||
|
|
|
|||
|
|
@ -48,6 +48,21 @@ def test_teardown_with_previous_exception():
|
|||
assert buffer == []
|
||||
assert buffer == [None]
|
||||
|
||||
def test_teardown_with_handled_exception():
|
||||
buffer = []
|
||||
app = flask.Flask(__name__)
|
||||
@app.teardown_request
|
||||
def end_of_request(exception):
|
||||
buffer.append(exception)
|
||||
|
||||
with app.test_request_context():
|
||||
assert buffer == []
|
||||
try:
|
||||
raise Exception('dummy')
|
||||
except Exception:
|
||||
pass
|
||||
assert buffer == [None]
|
||||
|
||||
def test_proper_test_request_context():
|
||||
app = flask.Flask(__name__)
|
||||
app.config.update(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue