]> granicus.if.org Git - python/commitdiff
[3.5] bpo-29692: contextlib.contextmanager may incorrectly unchain RuntimeError ...
authorMariatta <Mariatta@users.noreply.github.com>
Thu, 13 Apr 2017 10:14:53 +0000 (03:14 -0700)
committerGitHub <noreply@github.com>
Thu, 13 Apr 2017 10:14:53 +0000 (03:14 -0700)
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.
(cherry picked from commit 00c75e9a45ff0366c185e9e8a2e23af5a35481b0)

Lib/contextlib.py
Lib/test/test_contextlib.py
Misc/NEWS

index c0188952ad701ec6e286dd6c07c3de6df30e1f9e..5371a9f331922188c0578c22252bb10d27858a8c 100644 (file)
@@ -65,7 +65,7 @@ class _GeneratorContextManager(ContextDecorator):
             try:
                 next(self.gen)
             except StopIteration:
-                return
+                return False
             else:
                 raise RuntimeError("generator didn't stop")
         else:
@@ -87,7 +87,7 @@ class _GeneratorContextManager(ContextDecorator):
                 # 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:
@@ -98,10 +98,10 @@ class _GeneratorContextManager(ContextDecorator):
                 # 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):
index 516403ef655b9ed0c61f23b9963793e70ef000ae..ad1b6cd787f88a30e3a587bb09637e5bb6034d8f 100644 (file)
@@ -119,6 +119,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):
index fd3814ca1fa543df9ec7e3b1e35da14e91597185..0caeefdfff45a8423fb0d875d748fc04d22ed752 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -48,6 +48,9 @@ Extension Modules
 
 Library
 -------
+- bpo-29692: Fixed arbitrary unchaining of RuntimeError exceptions in 
+  contextlib.contextmanager.
+  Patch by Siddharth Velankar.
 
 - bpo-29998: Pickling and copying ImportError now preserves name and path
   attributes.