Skip to content

Commit

Permalink
Switch away from using None as default value for the exception when t…
Browse files Browse the repository at this point in the history
…earing 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.
  • Loading branch information
mjpieters committed Mar 23, 2015
1 parent ec5811d commit ec0d208
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 8 deletions.
11 changes: 7 additions & 4 deletions flask/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
# 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):
Expand Down Expand Up @@ -1774,7 +1777,7 @@ def process_response(self, response):
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
Expand All @@ -1785,7 +1788,7 @@ def do_teardown_request(self, exc=None):
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
Expand All @@ -1795,14 +1798,14 @@ def do_teardown_request(self, exc=None):
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)
Expand Down
12 changes: 8 additions & 4 deletions flask/ctx.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
from ._compat import BROKEN_PYPY_CTXMGR_EXIT, reraise


# a singleton sentinel value for parameter defaults
_sentinel = object()


class _AppCtxGlobals(object):
"""A plain object."""

Expand Down Expand Up @@ -168,11 +172,11 @@ def push(self):
_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()
Expand Down Expand Up @@ -320,7 +324,7 @@ def push(self):
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.
Expand All @@ -334,7 +338,7 @@ def pop(self, exc=None):
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)

Expand Down
15 changes: 15 additions & 0 deletions tests/test_appctx.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,21 @@ def cleanup(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):
Expand Down
15 changes: 15 additions & 0 deletions tests/test_reqctx.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ def end_of_request(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(
Expand Down

0 comments on commit ec0d208

Please sign in to comment.