bpo-30241: implement contextlib.AbstractAsyncContextManager (#1412)

This commit is contained in:
Jelle Zijlstra 2017-12-13 17:19:17 -08:00 committed by Yury Selivanov
parent bfbf04ef18
commit 176baa326b
5 changed files with 88 additions and 5 deletions

View File

@ -29,6 +29,17 @@ Functions and classes provided:
.. versionadded:: 3.6 .. versionadded:: 3.6
.. class:: AbstractAsyncContextManager
An :term:`abstract base class` for classes that implement
:meth:`object.__aenter__` and :meth:`object.__aexit__`. A default
implementation for :meth:`object.__aenter__` is provided which returns
``self`` while :meth:`object.__aexit__` is an abstract method which by default
returns ``None``. See also the definition of
:ref:`async-context-managers`.
.. versionadded:: 3.7
.. decorator:: contextmanager .. decorator:: contextmanager

View File

@ -306,8 +306,9 @@ is a list of strings, not bytes.
contextlib contextlib
---------- ----------
:func:`contextlib.asynccontextmanager` has been added. (Contributed by :func:`~contextlib.asynccontextmanager` and
Jelle Zijlstra in :issue:`29679`.) :class:`~contextlib.AbstractAsyncContextManager` have been added. (Contributed
by Jelle Zijlstra in :issue:`29679` and :issue:`30241`.)
cProfile cProfile
-------- --------

View File

@ -6,7 +6,8 @@ from collections import deque
from functools import wraps from functools import wraps
__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext", __all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
"AbstractContextManager", "ContextDecorator", "ExitStack", "AbstractContextManager", "AbstractAsyncContextManager",
"ContextDecorator", "ExitStack",
"redirect_stdout", "redirect_stderr", "suppress"] "redirect_stdout", "redirect_stderr", "suppress"]
@ -30,6 +31,27 @@ class AbstractContextManager(abc.ABC):
return NotImplemented return NotImplemented
class AbstractAsyncContextManager(abc.ABC):
"""An abstract base class for asynchronous context managers."""
async def __aenter__(self):
"""Return `self` upon entering the runtime context."""
return self
@abc.abstractmethod
async def __aexit__(self, exc_type, exc_value, traceback):
"""Raise any exception triggered within the runtime context."""
return None
@classmethod
def __subclasshook__(cls, C):
if cls is AbstractAsyncContextManager:
return _collections_abc._check_methods(C, "__aenter__",
"__aexit__")
return NotImplemented
class ContextDecorator(object): class ContextDecorator(object):
"A base class or mixin that enables context managers to work as decorators." "A base class or mixin that enables context managers to work as decorators."
@ -136,7 +158,8 @@ class _GeneratorContextManager(_GeneratorContextManagerBase,
raise RuntimeError("generator didn't stop after throw()") raise RuntimeError("generator didn't stop after throw()")
class _AsyncGeneratorContextManager(_GeneratorContextManagerBase): class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
AbstractAsyncContextManager):
"""Helper for @asynccontextmanager.""" """Helper for @asynccontextmanager."""
async def __aenter__(self): async def __aenter__(self):

View File

@ -1,5 +1,5 @@
import asyncio import asyncio
from contextlib import asynccontextmanager from contextlib import asynccontextmanager, AbstractAsyncContextManager
import functools import functools
from test import support from test import support
import unittest import unittest
@ -20,6 +20,53 @@ def _async_test(func):
return wrapper return wrapper
class TestAbstractAsyncContextManager(unittest.TestCase):
@_async_test
async def test_enter(self):
class DefaultEnter(AbstractAsyncContextManager):
async def __aexit__(self, *args):
await super().__aexit__(*args)
manager = DefaultEnter()
self.assertIs(await manager.__aenter__(), manager)
async with manager as context:
self.assertIs(manager, context)
def test_exit_is_abstract(self):
class MissingAexit(AbstractAsyncContextManager):
pass
with self.assertRaises(TypeError):
MissingAexit()
def test_structural_subclassing(self):
class ManagerFromScratch:
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc_value, traceback):
return None
self.assertTrue(issubclass(ManagerFromScratch, AbstractAsyncContextManager))
class DefaultEnter(AbstractAsyncContextManager):
async def __aexit__(self, *args):
await super().__aexit__(*args)
self.assertTrue(issubclass(DefaultEnter, AbstractAsyncContextManager))
class NoneAenter(ManagerFromScratch):
__aenter__ = None
self.assertFalse(issubclass(NoneAenter, AbstractAsyncContextManager))
class NoneAexit(ManagerFromScratch):
__aexit__ = None
self.assertFalse(issubclass(NoneAexit, AbstractAsyncContextManager))
class AsyncContextManagerTestCase(unittest.TestCase): class AsyncContextManagerTestCase(unittest.TestCase):
@_async_test @_async_test

View File

@ -0,0 +1 @@
Add contextlib.AbstractAsyncContextManager. Patch by Jelle Zijlstra.