Skip to content

Commit

Permalink
bpo-28556: Updates to typing module (#2076)
Browse files Browse the repository at this point in the history
This PR contains two updates to typing module:

- Support ContextManager on all versions (original PR by Jelle Zijlstra).
- Add generic AsyncContextManager.
  • Loading branch information
ilevkivskyi authored and gvanrossum committed Jun 10, 2017
1 parent ca81615 commit 29fda8d
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 5 deletions.
39 changes: 36 additions & 3 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1552,6 +1552,12 @@ def __anext__(self) -> T_a:
return data
else:
raise StopAsyncIteration
class ACM:
async def __aenter__(self) -> int:
return 42
async def __aexit__(self, etype, eval, tb):
return None
"""

if ASYNCIO:
Expand All @@ -1562,12 +1568,13 @@ def __anext__(self) -> T_a:
else:
# fake names for the sake of static analysis
asyncio = None
AwaitableWrapper = AsyncIteratorWrapper = object
AwaitableWrapper = AsyncIteratorWrapper = ACM = object

PY36 = sys.version_info[:2] >= (3, 6)

PY36_TESTS = """
from test import ann_module, ann_module2, ann_module3
from typing import AsyncContextManager
class A:
y: float
Expand Down Expand Up @@ -1604,6 +1611,16 @@ def __str__(self):
return f'{self.x} -> {self.y}'
def __add__(self, other):
return 0
async def g_with(am: AsyncContextManager[int]):
x: int
async with am as x:
return x
try:
g_with(ACM()).send(None)
except StopIteration as e:
assert e.args[0] == 42
"""

if PY36:
Expand Down Expand Up @@ -2156,8 +2173,6 @@ class B: ...

class OtherABCTests(BaseTestCase):

@skipUnless(hasattr(typing, 'ContextManager'),
'requires typing.ContextManager')
def test_contextmanager(self):
@contextlib.contextmanager
def manager():
Expand All @@ -2167,6 +2182,24 @@ def manager():
self.assertIsInstance(cm, typing.ContextManager)
self.assertNotIsInstance(42, typing.ContextManager)

@skipUnless(ASYNCIO, 'Python 3.5 required')
def test_async_contextmanager(self):
class NotACM:
pass
self.assertIsInstance(ACM(), typing.AsyncContextManager)
self.assertNotIsInstance(NotACM(), typing.AsyncContextManager)
@contextlib.contextmanager
def manager():
yield 42

cm = manager()
self.assertNotIsInstance(cm, typing.AsyncContextManager)
self.assertEqual(typing.AsyncContextManager[int].__args__, (int,))
with self.assertRaises(TypeError):
isinstance(42, typing.AsyncContextManager[int])
with self.assertRaises(TypeError):
typing.AsyncContextManager[int, str]


class TypeTests(BaseTestCase):

Expand Down
61 changes: 59 additions & 2 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import collections.abc as collections_abc
except ImportError:
import collections as collections_abc # Fallback for PY3.2.
if sys.version_info[:2] >= (3, 6):
import _collections_abc # Needed for private function _check_methods # noqa
try:
from types import WrapperDescriptorType, MethodWrapperType, MethodDescriptorType
except ImportError:
Expand Down Expand Up @@ -37,6 +39,7 @@
# for 'Generic' and ABCs below.
'ByteString',
'Container',
'ContextManager',
'Hashable',
'ItemsView',
'Iterable',
Expand All @@ -57,8 +60,8 @@
# AsyncIterable,
# Coroutine,
# Collection,
# ContextManager,
# AsyncGenerator,
# AsyncContextManager

# Structural checks, a.k.a. protocols.
'Reversible',
Expand Down Expand Up @@ -1949,7 +1952,61 @@ class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView):
if hasattr(contextlib, 'AbstractContextManager'):
class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager):
__slots__ = ()
__all__.append('ContextManager')
else:
class ContextManager(Generic[T_co]):
__slots__ = ()

def __enter__(self):
return self

@abc.abstractmethod
def __exit__(self, exc_type, exc_value, traceback):
return None

@classmethod
def __subclasshook__(cls, C):
if cls is ContextManager:
# In Python 3.6+, it is possible to set a method to None to
# explicitly indicate that the class does not implement an ABC
# (https://bugs.python.org/issue25958), but we do not support
# that pattern here because this fallback class is only used
# in Python 3.5 and earlier.
if (any("__enter__" in B.__dict__ for B in C.__mro__) and
any("__exit__" in B.__dict__ for B in C.__mro__)):
return True
return NotImplemented


if hasattr(contextlib, 'AbstractAsyncContextManager'):
class AsyncContextManager(Generic[T_co],
extra=contextlib.AbstractAsyncContextManager):
__slots__ = ()

__all__.append('AsyncContextManager')
elif sys.version_info[:2] >= (3, 5):
exec("""
class AsyncContextManager(Generic[T_co]):
__slots__ = ()
async def __aenter__(self):
return self
@abc.abstractmethod
async def __aexit__(self, exc_type, exc_value, traceback):
return None
@classmethod
def __subclasshook__(cls, C):
if cls is AsyncContextManager:
if sys.version_info[:2] >= (3, 6):
return _collections_abc._check_methods(C, "__aenter__", "__aexit__")
if (any("__aenter__" in B.__dict__ for B in C.__mro__) and
any("__aexit__" in B.__dict__ for B in C.__mro__)):
return True
return NotImplemented
__all__.append('AsyncContextManager')
""")


class Dict(dict, MutableMapping[KT, VT], extra=dict):
Expand Down
4 changes: 4 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,10 @@ Library
non-blocking mode if it succeeded to aquire the lock but the acquire took
longer than the timeout.

- bpo-28556: Updates to typing module: Add generic AsyncContextManager, add
support for ContextManager on all versions. Original PRs by Jelle Zijlstra
and Ivan Levkivskyi

- bpo-30605: re.compile() no longer raises a BytesWarning when compiling a
bytes instance with misplaced inline modifier. Patch by Roy Williams.

Expand Down

0 comments on commit 29fda8d

Please sign in to comment.