Skip to content

Commit

Permalink
bpo-29692: contextlib.contextmanager may incorrectly unchain RuntimeE…
Browse files Browse the repository at this point in the history
…rror (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.
  • Loading branch information
soolabettu authored and ncoghlan committed Apr 11, 2017
1 parent 6fab78e commit 00c75e9
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 6 deletions.
12 changes: 6 additions & 6 deletions Lib/contextlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def __exit__(self, type, value, traceback):
try:
next(self.gen)
except StopIteration:
return
return False
else:
raise RuntimeError("generator didn't stop")
else:
Expand All @@ -110,7 +110,7 @@ def __exit__(self, type, value, traceback):
# 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:
Expand All @@ -121,10 +121,10 @@ def __exit__(self, type, value, traceback):
# 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):
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_contextlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,29 @@ def woohoo():
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):
Expand Down
3 changes: 3 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,9 @@ Extension Modules

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
Expand Down

0 comments on commit 00c75e9

Please sign in to comment.