mirror of
https://github.com/python/cpython.git
synced 2025-07-07 19:35:27 +00:00
bpo-44566: resolve differences between asynccontextmanager and contextmanager (#27024)
This commit is contained in:
parent
85fa3b6b7c
commit
7f1c330da3
4 changed files with 116 additions and 87 deletions
|
@ -113,18 +113,20 @@ class _GeneratorContextManagerBase:
|
|||
# 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
|
||||
|
@ -134,8 +136,8 @@ class _GeneratorContextManager(_GeneratorContextManagerBase,
|
|||
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:
|
||||
|
@ -146,9 +148,9 @@ class _GeneratorContextManager(_GeneratorContextManagerBase,
|
|||
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
|
||||
|
@ -156,68 +158,6 @@ class _GeneratorContextManager(_GeneratorContextManagerBase,
|
|||
return exc is not value
|
||||
except RuntimeError as exc:
|
||||
# Don't re-raise the passed in exception. (issue27122)
|
||||
if exc is value:
|
||||
return False
|
||||
# Likewise, 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:
|
||||
return False
|
||||
raise
|
||||
except:
|
||||
# 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
|
||||
raise RuntimeError("generator didn't stop after throw()")
|
||||
|
||||
|
||||
class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
|
||||
AbstractAsyncContextManager,
|
||||
AsyncContextDecorator):
|
||||
"""Helper for @asynccontextmanager."""
|
||||
|
||||
def _recreate_cm(self):
|
||||
# _AGCM instances are one-shot context managers, so the
|
||||
# ACM must be recreated each time a decorated function is
|
||||
# called
|
||||
return self.__class__(self.func, self.args, self.kwds)
|
||||
|
||||
async def __aenter__(self):
|
||||
try:
|
||||
return await self.gen.__anext__()
|
||||
except StopAsyncIteration:
|
||||
raise RuntimeError("generator didn't yield") from None
|
||||
|
||||
async def __aexit__(self, typ, value, traceback):
|
||||
if typ is None:
|
||||
try:
|
||||
await self.gen.__anext__()
|
||||
except StopAsyncIteration:
|
||||
return
|
||||
else:
|
||||
raise RuntimeError("generator didn't stop")
|
||||
else:
|
||||
if value is None:
|
||||
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:
|
||||
return exc is not value
|
||||
except RuntimeError as exc:
|
||||
if exc is value:
|
||||
return False
|
||||
# Avoid suppressing if a StopIteration exception
|
||||
|
@ -226,13 +166,87 @@ class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
|
|||
# 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, StopAsyncIteration)):
|
||||
if exc.__cause__ is value:
|
||||
return False
|
||||
if (
|
||||
isinstance(value, StopIteration)
|
||||
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 throw()")
|
||||
|
||||
class _AsyncGeneratorContextManager(
|
||||
_GeneratorContextManagerBase,
|
||||
AbstractAsyncContextManager,
|
||||
AsyncContextDecorator,
|
||||
):
|
||||
"""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 anext(self.gen)
|
||||
except StopAsyncIteration:
|
||||
raise RuntimeError("generator didn't yield") from None
|
||||
|
||||
async def __aexit__(self, typ, value, traceback):
|
||||
if typ is None:
|
||||
try:
|
||||
await anext(self.gen)
|
||||
except StopAsyncIteration:
|
||||
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()
|
||||
try:
|
||||
await self.gen.athrow(typ, value, traceback)
|
||||
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 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 actually Stop(Async)Iteration (see
|
||||
# issue29692).
|
||||
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