Commit 53d6420e authored by Nick Coghlan's avatar Nick Coghlan

Close #19092: ExitStack now reraises exceptions from __exit__

Report and patch by Hrvoje Nikšić
parent 816a28ff
...@@ -225,6 +225,8 @@ class ExitStack(object): ...@@ -225,6 +225,8 @@ class ExitStack(object):
return self return self
def __exit__(self, *exc_details): def __exit__(self, *exc_details):
received_exc = exc_details[0] is not None
# We manipulate the exception state so it behaves as though # We manipulate the exception state so it behaves as though
# we were actually nesting multiple with statements # we were actually nesting multiple with statements
frame_exc = sys.exc_info()[1] frame_exc = sys.exc_info()[1]
...@@ -239,17 +241,27 @@ class ExitStack(object): ...@@ -239,17 +241,27 @@ class ExitStack(object):
# Callbacks are invoked in LIFO order to match the behaviour of # Callbacks are invoked in LIFO order to match the behaviour of
# nested context managers # nested context managers
suppressed_exc = False suppressed_exc = False
pending_raise = False
while self._exit_callbacks: while self._exit_callbacks:
cb = self._exit_callbacks.pop() cb = self._exit_callbacks.pop()
try: try:
if cb(*exc_details): if cb(*exc_details):
suppressed_exc = True suppressed_exc = True
pending_raise = False
exc_details = (None, None, None) exc_details = (None, None, None)
except: except:
new_exc_details = sys.exc_info() new_exc_details = sys.exc_info()
# simulate the stack of exceptions by setting the context # simulate the stack of exceptions by setting the context
_fix_exception_context(new_exc_details[1], exc_details[1]) _fix_exception_context(new_exc_details[1], exc_details[1])
if not self._exit_callbacks: pending_raise = True
raise
exc_details = new_exc_details exc_details = new_exc_details
return suppressed_exc if pending_raise:
try:
# bare "raise exc_details[1]" replaces our carefully
# set-up context
fixed_ctx = exc_details[1].__context__
raise exc_details[1]
except BaseException:
exc_details[1].__context__ = fixed_ctx
raise
return received_exc and suppressed_exc
...@@ -573,6 +573,43 @@ class TestExitStack(unittest.TestCase): ...@@ -573,6 +573,43 @@ class TestExitStack(unittest.TestCase):
self.assertIsInstance(inner_exc, ValueError) self.assertIsInstance(inner_exc, ValueError)
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError) self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
def test_exit_exception_non_suppressing(self):
# http://bugs.python.org/issue19092
def raise_exc(exc):
raise exc
def suppress_exc(*exc_details):
return True
try:
with ExitStack() as stack:
stack.callback(lambda: None)
stack.callback(raise_exc, IndexError)
except Exception as exc:
self.assertIsInstance(exc, IndexError)
else:
self.fail("Expected IndexError, but no exception was raised")
try:
with ExitStack() as stack:
stack.callback(raise_exc, KeyError)
stack.push(suppress_exc)
stack.callback(raise_exc, IndexError)
except Exception as exc:
self.assertIsInstance(exc, KeyError)
else:
self.fail("Expected KeyError, but no exception was raised")
def test_body_exception_suppress(self):
def suppress_exc(*exc_details):
return True
try:
with ExitStack() as stack:
stack.push(suppress_exc)
1/0
except IndexError as exc:
self.fail("Expected no exception, got IndexError")
def test_exit_exception_chaining_suppress(self): def test_exit_exception_chaining_suppress(self):
with ExitStack() as stack: with ExitStack() as stack:
stack.push(lambda *exc: True) stack.push(lambda *exc: True)
......
...@@ -882,7 +882,7 @@ Samuel Nicolary ...@@ -882,7 +882,7 @@ Samuel Nicolary
Jonathan Niehof Jonathan Niehof
Gustavo Niemeyer Gustavo Niemeyer
Oscar Nierstrasz Oscar Nierstrasz
Hrvoje Niksic Hrvoje Nikšić
Gregory Nofi Gregory Nofi
Jesse Noller Jesse Noller
Bill Noon Bill Noon
......
...@@ -71,6 +71,10 @@ Core and Builtins ...@@ -71,6 +71,10 @@ Core and Builtins
Library Library
------- -------
- Issue #19092: contextlib.ExitStack now correctly reraises exceptions
from the __exit__ callbacks of inner context managers (Patch by Hrvoje
Nikšić)
- Issue #12641: Avoid passing "-mno-cygwin" to the mingw32 compiler, except - Issue #12641: Avoid passing "-mno-cygwin" to the mingw32 compiler, except
when necessary. Patch by Oscar Benjamin. when necessary. Patch by Oscar Benjamin.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment