mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	[3.9] bpo-44566: resolve differences between asynccontextmanager and contextmanager (GH-27024). (#27269)
(cherry picked from commit 7f1c330da3)
Co-authored-by: Thomas Grainger <tagrain@gmail.com>
			
			
This commit is contained in:
		
							parent
							
								
									dae4928dd0
								
							
						
					
					
						commit
						1c5c9c89ff
					
				
					 4 changed files with 78 additions and 44 deletions
				
			
		|  | @ -97,18 +97,20 @@ def __init__(self, func, args, kwds): | |||
|         # for the class instead. | ||||
|         # See http://bugs.python.org/issue19404 for more details. | ||||
| 
 | ||||
| 
 | ||||
| class _GeneratorContextManager(_GeneratorContextManagerBase, | ||||
|                                AbstractContextManager, | ||||
|                                ContextDecorator): | ||||
|     """Helper for @contextmanager decorator.""" | ||||
| 
 | ||||
|     def _recreate_cm(self): | ||||
|         # _GCM instances are one-shot context managers, so the | ||||
|         # _GCMB instances are one-shot context managers, so the | ||||
|         # CM must be recreated each time a decorated function is | ||||
|         # called | ||||
|         return self.__class__(self.func, self.args, self.kwds) | ||||
| 
 | ||||
| 
 | ||||
| class _GeneratorContextManager( | ||||
|     _GeneratorContextManagerBase, | ||||
|     AbstractContextManager, | ||||
|     ContextDecorator, | ||||
| ): | ||||
|     """Helper for @contextmanager decorator.""" | ||||
| 
 | ||||
|     def __enter__(self): | ||||
|         # do not keep args and kwds alive unnecessarily | ||||
|         # they are only needed for recreation, which is not possible anymore | ||||
|  | @ -118,8 +120,8 @@ def __enter__(self): | |||
|         except StopIteration: | ||||
|             raise RuntimeError("generator didn't yield") from None | ||||
| 
 | ||||
|     def __exit__(self, type, value, traceback): | ||||
|         if type is None: | ||||
|     def __exit__(self, typ, value, traceback): | ||||
|         if typ is None: | ||||
|             try: | ||||
|                 next(self.gen) | ||||
|             except StopIteration: | ||||
|  | @ -130,9 +132,9 @@ def __exit__(self, type, value, traceback): | |||
|             if value is None: | ||||
|                 # Need to force instantiation so we can reliably | ||||
|                 # tell if we get the same exception back | ||||
|                 value = type() | ||||
|                 value = typ() | ||||
|             try: | ||||
|                 self.gen.throw(type, value, traceback) | ||||
|                 self.gen.throw(typ, value, traceback) | ||||
|             except StopIteration as exc: | ||||
|                 # Suppress StopIteration *unless* it's the same exception that | ||||
|                 # was passed to throw().  This prevents a StopIteration | ||||
|  | @ -142,35 +144,39 @@ def __exit__(self, type, value, traceback): | |||
|                 # Don't re-raise the passed in exception. (issue27122) | ||||
|                 if exc is value: | ||||
|                     return False | ||||
|                 # Likewise, avoid suppressing if a StopIteration exception | ||||
|                 # Avoid suppressing if a StopIteration exception | ||||
|                 # was passed to throw() and later wrapped into a RuntimeError | ||||
|                 # (see PEP 479). | ||||
|                 if type is StopIteration and exc.__cause__ is value: | ||||
|                 # (see PEP 479 for sync generators; async generators also | ||||
|                 # have this behavior). But do this only if the exception wrapped | ||||
|                 # by the RuntimeError is actually Stop(Async)Iteration (see | ||||
|                 # issue29692). | ||||
|                 if ( | ||||
|                     isinstance(value, StopIteration) | ||||
|                     and exc.__cause__ is value | ||||
|                 ): | ||||
|                     return False | ||||
|                 raise | ||||
|             except: | ||||
|             except BaseException as exc: | ||||
|                 # only re-raise if it's *not* the exception that was | ||||
|                 # passed to throw(), because __exit__() must not raise | ||||
|                 # an exception unless __exit__() itself failed.  But throw() | ||||
|                 # has to raise the exception to signal propagation, so this | ||||
|                 # fixes the impedance mismatch between the throw() protocol | ||||
|                 # and the __exit__() protocol. | ||||
|                 # | ||||
|                 # This cannot use 'except BaseException as exc' (as in the | ||||
|                 # async implementation) to maintain compatibility with | ||||
|                 # Python 2, where old-style class exceptions are not caught | ||||
|                 # by 'except BaseException'. | ||||
|                 if sys.exc_info()[1] is value: | ||||
|                     return False | ||||
|                 raise | ||||
|                 if exc is not value: | ||||
|                     raise | ||||
|                 return False | ||||
|             raise RuntimeError("generator didn't stop after throw()") | ||||
| 
 | ||||
| 
 | ||||
| class _AsyncGeneratorContextManager(_GeneratorContextManagerBase, | ||||
|                                     AbstractAsyncContextManager): | ||||
|     """Helper for @asynccontextmanager.""" | ||||
|     """Helper for @asynccontextmanager decorator.""" | ||||
| 
 | ||||
|     async def __aenter__(self): | ||||
|         # do not keep args and kwds alive unnecessarily | ||||
|         # they are only needed for recreation, which is not possible anymore | ||||
|         del self.args, self.kwds, self.func | ||||
|         try: | ||||
|             return await self.gen.__anext__() | ||||
|         except StopAsyncIteration: | ||||
|  | @ -181,35 +187,48 @@ async def __aexit__(self, typ, value, traceback): | |||
|             try: | ||||
|                 await self.gen.__anext__() | ||||
|             except StopAsyncIteration: | ||||
|                 return | ||||
|                 return False | ||||
|             else: | ||||
|                 raise RuntimeError("generator didn't stop") | ||||
|         else: | ||||
|             if value is None: | ||||
|                 # Need to force instantiation so we can reliably | ||||
|                 # tell if we get the same exception back | ||||
|                 value = typ() | ||||
|             # See _GeneratorContextManager.__exit__ for comments on subtleties | ||||
|             # in this implementation | ||||
|             try: | ||||
|                 await self.gen.athrow(typ, value, traceback) | ||||
|                 raise RuntimeError("generator didn't stop after athrow()") | ||||
|             except StopAsyncIteration as exc: | ||||
|                 # Suppress StopIteration *unless* it's the same exception that | ||||
|                 # was passed to throw().  This prevents a StopIteration | ||||
|                 # raised inside the "with" statement from being suppressed. | ||||
|                 return exc is not value | ||||
|             except RuntimeError as exc: | ||||
|                 # Don't re-raise the passed in exception. (issue27122) | ||||
|                 if exc is value: | ||||
|                     return False | ||||
|                 # Avoid suppressing if a StopIteration exception | ||||
|                 # was passed to throw() and later wrapped into a RuntimeError | ||||
|                 # Avoid suppressing if a Stop(Async)Iteration exception | ||||
|                 # was passed to athrow() and later wrapped into a RuntimeError | ||||
|                 # (see PEP 479 for sync generators; async generators also | ||||
|                 # have this behavior). But do this only if the exception wrapped | ||||
|                 # by the RuntimeError is actully Stop(Async)Iteration (see | ||||
|                 # issue29692). | ||||
|                 if isinstance(value, (StopIteration, StopAsyncIteration)): | ||||
|                     if exc.__cause__ is value: | ||||
|                         return False | ||||
|                 if ( | ||||
|                     isinstance(value, (StopIteration, StopAsyncIteration)) | ||||
|                     and exc.__cause__ is value | ||||
|                 ): | ||||
|                     return False | ||||
|                 raise | ||||
|             except BaseException as exc: | ||||
|                 # only re-raise if it's *not* the exception that was | ||||
|                 # passed to throw(), because __exit__() must not raise | ||||
|                 # an exception unless __exit__() itself failed.  But throw() | ||||
|                 # has to raise the exception to signal propagation, so this | ||||
|                 # fixes the impedance mismatch between the throw() protocol | ||||
|                 # and the __exit__() protocol. | ||||
|                 if exc is not value: | ||||
|                     raise | ||||
|                 return False | ||||
|             raise RuntimeError("generator didn't stop after athrow()") | ||||
| 
 | ||||
| 
 | ||||
| def contextmanager(func): | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Łukasz Langa
						Łukasz Langa