bpo-29692: contextlib.contextmanager may incorrectly unchain RuntimeError (GH-949)
contextlib._GeneratorContextManager.__exit__ includes a special case to deal with
PEP 479 RuntimeErrors created when `StopIteration` is thrown into the context
manager body.
Previously this check was too permissive, and undid one level of chaining on *all*
RuntimeError instances, not just those that wrapped a StopIteration instance.
diff --git a/Lib/contextlib.py b/Lib/contextlib.py
index e91cf46..5e47054 100644
--- a/Lib/contextlib.py
+++ b/Lib/contextlib.py
@@ -88,7 +88,7 @@
try:
next(self.gen)
except StopIteration:
- return
+ return False
else:
raise RuntimeError("generator didn't stop")
else:
@@ -110,7 +110,7 @@
# Likewise, avoid suppressing if a StopIteration exception
# was passed to throw() and later wrapped into a RuntimeError
# (see PEP 479).
- if exc.__cause__ is value:
+ if type is StopIteration and exc.__cause__ is value:
return False
raise
except:
@@ -121,10 +121,10 @@
# fixes the impedance mismatch between the throw() protocol
# and the __exit__() protocol.
#
- if sys.exc_info()[1] is not value:
- raise
- else:
- raise RuntimeError("generator didn't stop after throw()")
+ if sys.exc_info()[1] is value:
+ return False
+ raise
+ raise RuntimeError("generator didn't stop after throw()")
def contextmanager(func):
diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py
index c04c804..b1a467d 100644
--- a/Lib/test/test_contextlib.py
+++ b/Lib/test/test_contextlib.py
@@ -152,6 +152,29 @@
else:
self.fail('StopIteration was suppressed')
+ def test_contextmanager_do_not_unchain_non_stopiteration_exceptions(self):
+ @contextmanager
+ def test_issue29692():
+ try:
+ yield
+ except Exception as exc:
+ raise RuntimeError('issue29692:Chained') from exc
+ try:
+ with test_issue29692():
+ raise ZeroDivisionError
+ except Exception as ex:
+ self.assertIs(type(ex), RuntimeError)
+ self.assertEqual(ex.args[0], 'issue29692:Chained')
+ self.assertIsInstance(ex.__cause__, ZeroDivisionError)
+
+ try:
+ with test_issue29692():
+ raise StopIteration('issue29692:Unchained')
+ except Exception as ex:
+ self.assertIs(type(ex), StopIteration)
+ self.assertEqual(ex.args[0], 'issue29692:Unchained')
+ self.assertIsNone(ex.__cause__)
+
def _create_contextmanager_attribs(self):
def attribs(**kw):
def decorate(func):
diff --git a/Misc/NEWS b/Misc/NEWS
index cc0a4d7..d8b1ccb 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -306,6 +306,9 @@
Library
-------
+- bpo-29692: Fixed arbitrary unchaining of RuntimeError exceptions in
+ contextlib.contextmanager.
+ Patch by Siddharth Velankar.
- bpo-26187: Test that sqlite3 trace callback is not called multiple
times when schema is changing. Indirectly fixed by switching to