From 722e7ead1907758fb29ebf2eb53e0545c2a79ec4 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 3 Mar 2022 06:32:01 +0100 Subject: [PATCH 01/13] Improve distribution package (#1097) --- MANIFEST.in | 3 --- pyproject.toml | 23 +++++++++-------------- 2 files changed, 9 insertions(+), 17 deletions(-) delete mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index e3e1b70c..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include CHANGELOG LICENSE README.rst -include src/typing_extensions.py -include src/test_typing_extensions.py diff --git a/pyproject.toml b/pyproject.toml index 354c2068..fd6e1859 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,20 +8,7 @@ build-backend = "flit_core.buildapi" name = "typing_extensions" version = "4.1.1" description = "Backported and Experimental Type Hints for Python 3.6+" -readme.text = """\ -Typing Extensions -- Backported and Experimental Type Hints for Python - -The ``typing`` module was added to the standard library in Python 3.5, but -many new features have been added to the module since then. -This means users of older Python versions who are unable to upgrade will not be -able to take advantage of new types added to the ``typing`` module, such as -``typing.Protocol`` or ``typing.TypedDict``. - -The ``typing_extensions`` module contains backports of these changes. -Experimental types that may eventually be added to the ``typing`` -module are also included in ``typing_extensions``. -""" -readme.content-type = "text/x-rst" +readme = "README.rst" requires-python = ">=3.6" urls.Home = "https://github.com/python/typing/blob/master/typing_extensions/README.rst" license.file = "LICENSE" @@ -61,3 +48,11 @@ classifiers = [ [[project.authors]] name = "Guido van Rossum, Jukka Lehtosalo, Ɓukasz Langa, Michael Lee" email = "levkivskyi@gmail.com" + +[tool.flit.sdist] +include = [ + "CHANGELOG", + "README.rst", + "*/test*.py" +] +exclude = [] From aba47e2491229963596351aec568497c1495e1f4 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 17 Mar 2022 01:09:58 -0700 Subject: [PATCH 02/13] typing-extensions: Drop Python 3.6 (#1104) --- .github/workflows/ci.yml | 2 +- CHANGELOG | 4 + README.rst | 9 +- pyproject.toml | 2 +- src/test_typing_extensions.py | 90 +-- src/typing_extensions.py | 1285 +++------------------------------ 6 files changed, 147 insertions(+), 1245 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0e6d67b..302b2cae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: # Python version, because typing sometimes changed between bugfix releases. # For available versions, see: # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json - python-version: ["3.6", "3.6.7", "3.7", "3.7.1", "3.8", "3.8.0", "3.9", "3.9.0", "3.10", "3.10.0", "3.11-dev"] + python-version: ["3.7", "3.7.1", "3.8", "3.8.0", "3.9", "3.9.0", "3.10", "3.10.0", "3.11-dev"] runs-on: ubuntu-latest diff --git a/CHANGELOG b/CHANGELOG index d27af291..f098464e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +# Unreleased + +- Drop support for Python 3.6. Original patch by Adam Turner (@AA-Turner). + # Release 4.1.1 (February 13, 2022) - Fix importing `typing_extensions` on Python 3.7.0 and 3.7.1. Original diff --git a/README.rst b/README.rst index 5db69d15..bb890689 100644 --- a/README.rst +++ b/README.rst @@ -30,6 +30,10 @@ Therefore, it's safe to depend on ``typing_extensions`` like this: ``typing_extensions >=x.y, <(x+1)``, where ``x.y`` is the first version that includes all features you need. +``typing_extensions`` supports Python versions 3.7 and higher. In the future, +support for older Python versions will be dropped some time after that version +reaches end of life. + Included items ============== @@ -101,7 +105,7 @@ This module currently contains the following: - ``Text`` - ``Type`` - ``TYPE_CHECKING`` - - ``get_type_hints`` (``typing_extensions`` provides this function only in Python 3.7+) + - ``get_type_hints`` Other Notes and Limitations =========================== @@ -131,9 +135,6 @@ versions of the typing module: - ``ParamSpec`` and ``Concatenate`` will not work with ``get_args`` and ``get_origin``. Certain PEP 612 special cases in user-defined ``Generic``\ s are also not available. -- ``Unpack`` from PEP 646 does not work properly with user-defined - ``Generic``\ s in Python 3.6: ``class X(Generic[Unpack[Ts]]):`` does - not work. These types are only guaranteed to work for static type checking. diff --git a/pyproject.toml b/pyproject.toml index fd6e1859..fbd01801 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ name = "typing_extensions" version = "4.1.1" description = "Backported and Experimental Type Hints for Python 3.6+" readme = "README.rst" -requires-python = ">=3.6" +requires-python = ">=3.7" urls.Home = "https://github.com/python/typing/blob/master/typing_extensions/README.rst" license.file = "LICENSE" keywords = [ diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index a66a2f29..20e35f43 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -23,32 +23,14 @@ from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict from typing_extensions import TypeVarTuple, Unpack, dataclass_transform, reveal_type, Never, assert_never, LiteralString -try: - from typing_extensions import get_type_hints -except ImportError: - from typing import get_type_hints - -PEP_560 = sys.version_info[:3] >= (3, 7, 0) - -OLD_GENERICS = False -try: - from typing import _type_vars, _next_in_mro, _type_check # noqa -except ImportError: - OLD_GENERICS = True +from typing_extensions import get_type_hints, get_origin, get_args # Flags used to mark tests that only apply after a specific # version of the typing module. -TYPING_3_6_1 = sys.version_info[:3] >= (3, 6, 1) TYPING_3_8_0 = sys.version_info[:3] >= (3, 8, 0) TYPING_3_10_0 = sys.version_info[:3] >= (3, 10, 0) TYPING_3_11_0 = sys.version_info[:3] >= (3, 11, 0) -# For typing versions where instantiating collection -# types are allowed. -# -# See https://github.com/python/typing/issues/367 -CAN_INSTANTIATE_COLLECTIONS = TYPING_3_6_1 - class BaseTestCase(TestCase): def assertIsSubclass(self, cls, class_or_tuple, msg=None): @@ -78,9 +60,7 @@ def test_equality(self): self.assertIs(self.bottom_type, self.bottom_type) self.assertNotEqual(self.bottom_type, None) - @skipUnless(PEP_560, "Python 3.7+ required") def test_get_origin(self): - from typing_extensions import get_origin self.assertIs(get_origin(self.bottom_type), None) def test_instance_type_error(self): @@ -621,11 +601,8 @@ def test_final_forward_ref(self): self.assertNotEqual(gth(Loop, globals())['attr'], Final) -@skipUnless(PEP_560, "Python 3.7+ required") class GetUtilitiesTestCase(TestCase): def test_get_origin(self): - from typing_extensions import get_origin - T = TypeVar('T') P = ParamSpec('P') Ts = TypeVarTuple('Ts') @@ -655,8 +632,6 @@ class C(Generic[T]): pass self.assertIs(get_origin(Unpack), None) def test_get_args(self): - from typing_extensions import get_args - T = TypeVar('T') Ts = TypeVarTuple('Ts') class C(Generic[T]): pass @@ -767,7 +742,6 @@ class MyDeque(typing_extensions.Deque[int]): ... def test_counter(self): self.assertIsSubclass(collections.Counter, typing_extensions.Counter) - @skipUnless(CAN_INSTANTIATE_COLLECTIONS, "Behavior added in typing 3.6.1") def test_defaultdict_instantiation(self): self.assertIs( type(typing_extensions.DefaultDict()), @@ -790,7 +764,6 @@ class MyDefDict(typing_extensions.DefaultDict[str, int]): self.assertIsSubclass(MyDefDict, collections.defaultdict) self.assertNotIsSubclass(collections.defaultdict, MyDefDict) - @skipUnless(CAN_INSTANTIATE_COLLECTIONS, "Behavior added in typing 3.6.1") def test_ordereddict_instantiation(self): self.assertIs( type(typing_extensions.OrderedDict()), @@ -844,10 +817,7 @@ def test_counter_instantiation(self): self.assertIs(type(typing_extensions.Counter[int]()), collections.Counter) class C(typing_extensions.Counter[T]): ... self.assertIs(type(C[int]()), C) - if not PEP_560: - self.assertEqual(C.__bases__, (typing_extensions.Counter,)) - else: - self.assertEqual(C.__bases__, (collections.Counter, typing.Generic)) + self.assertEqual(C.__bases__, (collections.Counter, typing.Generic)) def test_counter_subclass_instantiation(self): @@ -922,9 +892,8 @@ def manager(): cm = manager() self.assertNotIsInstance(cm, typing_extensions.AsyncContextManager) self.assertEqual(typing_extensions.AsyncContextManager[int].__args__, (int,)) - if TYPING_3_6_1: - with self.assertRaises(TypeError): - isinstance(42, typing_extensions.AsyncContextManager[int]) + with self.assertRaises(TypeError): + isinstance(42, typing_extensions.AsyncContextManager[int]) with self.assertRaises(TypeError): typing_extensions.AsyncContextManager[int, str] @@ -1189,10 +1158,6 @@ def x(self): ... self.assertIsSubclass(C, P) self.assertIsSubclass(C, PG) self.assertIsSubclass(BadP, PG) - if not PEP_560: - self.assertIsSubclass(PG[int], PG) - self.assertIsSubclass(BadPG[int], P) - self.assertIsSubclass(BadPG[T], PG) with self.assertRaises(TypeError): issubclass(C, PG[T]) with self.assertRaises(TypeError): @@ -1383,7 +1348,6 @@ class C: pass with self.assertRaises(TypeError): issubclass(C(), P) - @skipUnless(not OLD_GENERICS, "New style generics required") def test_defining_generic_protocols(self): T = TypeVar('T') S = TypeVar('S') @@ -1392,16 +1356,19 @@ class PR(Protocol[T, S]): def meth(self): pass class P(PR[int, T], Protocol[T]): y = 1 - self.assertIsSubclass(PR[int, T], PR) - self.assertIsSubclass(P[str], PR) with self.assertRaises(TypeError): - PR[int] + issubclass(PR[int, T], PR) with self.assertRaises(TypeError): - P[int, str] + issubclass(P[str], PR) with self.assertRaises(TypeError): - PR[int, 1] + PR[int] with self.assertRaises(TypeError): - PR[int, ClassVar] + P[int, str] + if not TYPING_3_10_0: + with self.assertRaises(TypeError): + PR[int, 1] + with self.assertRaises(TypeError): + PR[int, ClassVar] class C(PR[int, T]): pass self.assertIsInstance(C[str](), C) @@ -1413,11 +1380,8 @@ class PR(Protocol, Generic[T, S]): def meth(self): pass class P(PR[int, str], Protocol): y = 1 - if not PEP_560: + with self.assertRaises(TypeError): self.assertIsSubclass(PR[int, str], PR) - else: - with self.assertRaises(TypeError): - self.assertIsSubclass(PR[int, str], PR) self.assertIsSubclass(P, PR) with self.assertRaises(TypeError): PR[int] @@ -1448,7 +1412,6 @@ def __init__(self): self.test = 'OK' self.assertEqual(C[int]().test, 'OK') - @skipUnless(not OLD_GENERICS, "New style generics required") def test_protocols_bad_subscripts(self): T = TypeVar('T') S = TypeVar('S') @@ -1465,9 +1428,6 @@ def test_generic_protocols_repr(self): T = TypeVar('T') S = TypeVar('S') class P(Protocol[T, S]): pass - # After PEP 560 unsubscripted generics have a standard repr. - if not PEP_560: - self.assertTrue(repr(P).endswith('P')) self.assertTrue(repr(P[T, S]).endswith('P[~T, ~S]')) self.assertTrue(repr(P[int, str]).endswith('P[int, str]')) @@ -1480,13 +1440,10 @@ class P(Protocol[T, S]): pass self.assertEqual(P[T, T][Tuple[T, S]][int, str], P[Tuple[int, str], Tuple[int, str]]) - @skipUnless(not OLD_GENERICS, "New style generics required") def test_generic_protocols_special_from_generic(self): T = TypeVar('T') class P(Protocol[T]): pass self.assertEqual(P.__parameters__, (T,)) - self.assertIs(P.__args__, None) - self.assertIs(P.__origin__, None) self.assertEqual(P[int].__parameters__, ()) self.assertEqual(P[int].__args__, (int,)) self.assertIs(P[int].__origin__, P) @@ -1517,9 +1474,6 @@ def meth(self): self.assertEqual(typing_extensions._get_protocol_attrs(PR), {'x'}) self.assertEqual(frozenset(typing_extensions._get_protocol_attrs(PG)), frozenset({'x', 'meth'})) - if not PEP_560: - self.assertEqual(frozenset(typing_extensions._get_protocol_attrs(PG[int])), - frozenset({'x', 'meth'})) def test_no_runtime_deco_on_nominal(self): with self.assertRaises(TypeError): @@ -1747,7 +1701,6 @@ def test_optional_keys(self): assert Point2Dor3D.__required_keys__ == frozenset(['x', 'y']) assert Point2Dor3D.__optional_keys__ == frozenset(['z']) - @skipUnless(PEP_560, "runtime support for Required and NotRequired requires PEP 560") def test_required_notrequired_keys(self): assert NontotalMovie.__required_keys__ == frozenset({'title'}) assert NontotalMovie.__optional_keys__ == frozenset({'year'}) @@ -1821,16 +1774,14 @@ def test_flatten(self): A = Annotated[Annotated[int, 4], 5] self.assertEqual(A, Annotated[int, 4, 5]) self.assertEqual(A.__metadata__, (4, 5)) - if PEP_560: - self.assertEqual(A.__origin__, int) + self.assertEqual(A.__origin__, int) def test_specialize(self): L = Annotated[List[T], "my decoration"] LI = Annotated[List[int], "my decoration"] self.assertEqual(L[int], Annotated[List[int], "my decoration"]) self.assertEqual(L[int].__metadata__, ("my decoration",)) - if PEP_560: - self.assertEqual(L[int].__origin__, List[int]) + self.assertEqual(L[int].__origin__, List[int]) with self.assertRaises(TypeError): LI[int] with self.assertRaises(TypeError): @@ -1934,7 +1885,6 @@ def test_cannot_check_subclass(self): with self.assertRaises(TypeError): issubclass(int, Annotated[int, "positive"]) - @skipUnless(PEP_560, "pickle support was added with PEP 560") def test_pickle(self): samples = [typing.Any, typing.Union[int, str], typing.Optional[str], Tuple[int, ...], @@ -2000,7 +1950,6 @@ def test_annotated_in_other_types(self): self.assertEqual(X[int], List[Annotated[int, 5]]) -@skipUnless(PEP_560, "Python 3.7 required") class GetTypeHintsTests(BaseTestCase): def test_get_type_hints(self): def foobar(x: List['X']): ... @@ -2355,9 +2304,7 @@ def baz(self) -> "LiteralString": ... self.assertEqual(gth(Foo.bar), {'return': LiteralString}) self.assertEqual(gth(Foo.baz), {'return': LiteralString}) - @skipUnless(PEP_560, "Python 3.7+ required") def test_get_origin(self): - from typing_extensions import get_origin self.assertIsNone(get_origin(LiteralString)) def test_repr(self): @@ -2510,7 +2457,6 @@ def test_union(self): Union ) - @skipUnless(PEP_560, "Unimplemented for 3.6") def test_concatenation(self): Xs = TypeVarTuple('Xs') self.assertEqual(Tuple[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) @@ -2523,7 +2469,6 @@ class C(Generic[Unpack[Xs]]): pass self.assertEqual(C[int, Unpack[Xs], str].__args__, (int, Unpack[Xs], str)) - @skipUnless(PEP_560, "Unimplemented for 3.6") def test_class(self): Ts = TypeVarTuple('Ts') @@ -2766,8 +2711,7 @@ def test_typing_extensions_includes_standard(self): self.assertIn("Concatenate", a) self.assertIn('Annotated', a) - if PEP_560: - self.assertIn('get_type_hints', a) + self.assertIn('get_type_hints', a) self.assertIn('Awaitable', a) self.assertIn('AsyncIterator', a) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 194731cd..5c43354e 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -6,17 +6,6 @@ import types as _types import typing -# After PEP 560, internal typing API was substantially reworked. -# This is especially important for Protocol class which uses internal APIs -# quite extensively. -PEP_560 = sys.version_info[:3] >= (3, 7, 0) - -if PEP_560: - GenericMeta = type -else: - # 3.6 - from typing import GenericMeta, _type_vars # noqa - # Please keep __all__ alphabetized within each category. __all__ = [ @@ -56,6 +45,9 @@ 'assert_never', 'dataclass_transform', 'final', + 'get_args', + 'get_origin', + 'get_type_hints', 'IntVar', 'is_typeddict', 'Literal', @@ -75,21 +67,13 @@ 'NotRequired', ] -if PEP_560: - __all__.extend(["get_args", "get_origin", "get_type_hints"]) +# for backward compatibility +PEP_560 = True +GenericMeta = type # The functions below are modified copies of typing internal helpers. # They are needed by _ProtocolMeta and they provide support for PEP 646. - -def _no_slots_copy(dct): - dict_copy = dict(dct) - if '__slots__' in dict_copy: - for slot in dict_copy['__slots__']: - dict_copy.pop(slot, None) - return dict_copy - - _marker = object() @@ -148,32 +132,7 @@ def _collect_type_vars(types, typevar_types=None): return tuple(tvars) -# 3.6.2+ -if hasattr(typing, 'NoReturn'): - NoReturn = typing.NoReturn -# 3.6.0-3.6.1 -else: - class _NoReturn(typing._FinalTypingBase, _root=True): - """Special type indicating functions that never return. - Example:: - - from typing import NoReturn - - def stop() -> NoReturn: - raise Exception('no way') - - This type is invalid in other positions, e.g., ``List[NoReturn]`` - will fail in static type checkers. - """ - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("NoReturn cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("NoReturn cannot be used with issubclass().") - - NoReturn = _NoReturn(_root=True) +NoReturn = typing.NoReturn # Some unconstrained type variables. These are used by the container types. # (These are not for export.) @@ -190,7 +149,7 @@ def __subclasscheck__(self, cls): if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): Final = typing.Final # 3.7 -elif sys.version_info[:2] >= (3, 7): +else: class _FinalForm(typing._SpecialForm, _root=True): def __repr__(self): @@ -215,61 +174,6 @@ class FastConnector(Connection): TIMEOUT = 1 # Error reported by type checker There is no runtime checking of these properties.""") -# 3.6 -else: - class _Final(typing._FinalTypingBase, _root=True): - """A special typing construct to indicate that a name - cannot be re-assigned or overridden in a subclass. - For example: - - MAX_SIZE: Final = 9000 - MAX_SIZE += 1 # Error reported by type checker - - class Connection: - TIMEOUT: Final[int] = 10 - class FastConnector(Connection): - TIMEOUT = 1 # Error reported by type checker - - There is no runtime checking of these properties. - """ - - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(typing._type_check(item, - f'{cls.__name__[1:]} accepts only single type.'), - _root=True) - raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += f'[{typing._type_repr(self.__type__)}]' - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _Final): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - Final = _Final(_root=True) - if sys.version_info >= (3, 11): final = typing.final @@ -317,7 +221,7 @@ def IntVar(name): if hasattr(typing, 'Literal'): Literal = typing.Literal # 3.7: -elif sys.version_info[:2] >= (3, 7): +else: class _LiteralForm(typing._SpecialForm, _root=True): def __repr__(self): @@ -339,55 +243,6 @@ def __getitem__(self, parameters): Literal[...] cannot be subclassed. There is no runtime checking verifying that the parameter is actually a value instead of a type.""") -# 3.6: -else: - class _Literal(typing._FinalTypingBase, _root=True): - """A type that can be used to indicate to type checkers that the - corresponding value has a value literally equivalent to the - provided parameter. For example: - - var: Literal[4] = 4 - - The type checker understands that 'var' is literally equal to the - value 4 and no other value. - - Literal[...] cannot be subclassed. There is no runtime checking - verifying that the parameter is actually a value instead of a type. - """ - - __slots__ = ('__values__',) - - def __init__(self, values=None, **kwds): - self.__values__ = values - - def __getitem__(self, values): - cls = type(self) - if self.__values__ is None: - if not isinstance(values, tuple): - values = (values,) - return cls(values, _root=True) - raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') - - def _eval_type(self, globalns, localns): - return self - - def __repr__(self): - r = super().__repr__() - if self.__values__ is not None: - r += f'[{", ".join(map(typing._type_repr, self.__values__))}]' - return r - - def __hash__(self): - return hash((type(self).__name__, self.__values__)) - - def __eq__(self, other): - if not isinstance(other, _Literal): - return NotImplemented - if self.__values__ is not None: - return self.__values__ == other.__values__ - return self is other - - Literal = _Literal(_root=True) _overload_dummy = typing._overload_dummy # noqa @@ -401,154 +256,30 @@ def __eq__(self, other): # A few are simply re-exported for completeness. -class _ExtensionsGenericMeta(GenericMeta): - def __subclasscheck__(self, subclass): - """This mimics a more modern GenericMeta.__subclasscheck__() logic - (that does not have problems with recursion) to work around interactions - between collections, typing, and typing_extensions on older - versions of Python, see https://github.com/python/typing/issues/501. - """ - if self.__origin__ is not None: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: - raise TypeError("Parameterized generics cannot be used with class " - "or instance checks") - return False - if not self.__extra__: - return super().__subclasscheck__(subclass) - res = self.__extra__.__subclasshook__(subclass) - if res is not NotImplemented: - return res - if self.__extra__ in subclass.__mro__: - return True - for scls in self.__extra__.__subclasses__(): - if isinstance(scls, GenericMeta): - continue - if issubclass(subclass, scls): - return True - return False - - Awaitable = typing.Awaitable Coroutine = typing.Coroutine AsyncIterable = typing.AsyncIterable AsyncIterator = typing.AsyncIterator - -# 3.6.1+ -if hasattr(typing, 'Deque'): - Deque = typing.Deque -# 3.6.0 -else: - class Deque(collections.deque, typing.MutableSequence[T], - metaclass=_ExtensionsGenericMeta, - extra=collections.deque): - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Deque: - return collections.deque(*args, **kwds) - return typing._generic_new(collections.deque, cls, *args, **kwds) - +Deque = typing.Deque ContextManager = typing.ContextManager -# 3.6.2+ -if hasattr(typing, 'AsyncContextManager'): - AsyncContextManager = typing.AsyncContextManager -# 3.6.0-3.6.1 -else: - from _collections_abc import _check_methods as _check_methods_in_mro # noqa - - class AsyncContextManager(typing.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: - return _check_methods_in_mro(C, "__aenter__", "__aexit__") - return NotImplemented - +AsyncContextManager = typing.AsyncContextManager DefaultDict = typing.DefaultDict # 3.7.2+ if hasattr(typing, 'OrderedDict'): OrderedDict = typing.OrderedDict # 3.7.0-3.7.2 -elif (3, 7, 0) <= sys.version_info[:3] < (3, 7, 2): - OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) -# 3.6 -else: - class OrderedDict(collections.OrderedDict, typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.OrderedDict): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is OrderedDict: - return collections.OrderedDict(*args, **kwds) - return typing._generic_new(collections.OrderedDict, cls, *args, **kwds) - -# 3.6.2+ -if hasattr(typing, 'Counter'): - Counter = typing.Counter -# 3.6.0-3.6.1 -else: - class Counter(collections.Counter, - typing.Dict[T, int], - metaclass=_ExtensionsGenericMeta, extra=collections.Counter): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Counter: - return collections.Counter(*args, **kwds) - return typing._generic_new(collections.Counter, cls, *args, **kwds) - -# 3.6.1+ -if hasattr(typing, 'ChainMap'): - ChainMap = typing.ChainMap -elif hasattr(collections, 'ChainMap'): - class ChainMap(collections.ChainMap, typing.MutableMapping[KT, VT], - metaclass=_ExtensionsGenericMeta, - extra=collections.ChainMap): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is ChainMap: - return collections.ChainMap(*args, **kwds) - return typing._generic_new(collections.ChainMap, cls, *args, **kwds) - -# 3.6.1+ -if hasattr(typing, 'AsyncGenerator'): - AsyncGenerator = typing.AsyncGenerator -# 3.6.0 else: - class AsyncGenerator(AsyncIterator[T_co], typing.Generic[T_co, T_contra], - metaclass=_ExtensionsGenericMeta, - extra=collections.abc.AsyncGenerator): - __slots__ = () + OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) +Counter = typing.Counter +ChainMap = typing.ChainMap +AsyncGenerator = typing.AsyncGenerator NewType = typing.NewType Text = typing.Text TYPE_CHECKING = typing.TYPE_CHECKING -def _gorg(cls): - """This function exists for compatibility with old typing versions.""" - assert isinstance(cls, GenericMeta) - if hasattr(cls, '_gorg'): - return cls._gorg - while cls.__origin__ is not None: - cls = cls.__origin__ - return cls - - _PROTO_WHITELIST = ['Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable', 'AsyncIterator', 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', @@ -582,7 +313,7 @@ def _is_callable_members_only(cls): if hasattr(typing, 'Protocol'): Protocol = typing.Protocol # 3.7 -elif PEP_560: +else: def _no_init(self, *args, **kwargs): if type(self)._is_protocol: @@ -764,250 +495,12 @@ def _proto_hook(other): raise TypeError('Protocols can only inherit from other' f' protocols, got {repr(base)}') cls.__init__ = _no_init -# 3.6 -else: - from typing import _next_in_mro, _type_check # noqa - - def _no_init(self, *args, **kwargs): - if type(self)._is_protocol: - raise TypeError('Protocols cannot be instantiated') - - class _ProtocolMeta(GenericMeta): - """Internal metaclass for Protocol. - - This exists so Protocol classes can be generic without deriving - from Generic. - """ - def __new__(cls, name, bases, namespace, - tvars=None, args=None, origin=None, extra=None, orig_bases=None): - # This is just a version copied from GenericMeta.__new__ that - # includes "Protocol" special treatment. (Comments removed for brevity.) - assert extra is None # Protocols should not have extra - if tvars is not None: - assert origin is not None - assert all(isinstance(t, typing.TypeVar) for t in tvars), tvars - else: - tvars = _type_vars(bases) - gvars = None - for base in bases: - if base is typing.Generic: - raise TypeError("Cannot inherit from plain Generic") - if (isinstance(base, GenericMeta) and - base.__origin__ in (typing.Generic, Protocol)): - if gvars is not None: - raise TypeError( - "Cannot inherit from Generic[...] or" - " Protocol[...] multiple times.") - gvars = base.__parameters__ - if gvars is None: - gvars = tvars - else: - tvarset = set(tvars) - gvarset = set(gvars) - if not tvarset <= gvarset: - s_vars = ", ".join(str(t) for t in tvars if t not in gvarset) - s_args = ", ".join(str(g) for g in gvars) - cls_name = "Generic" if any(b.__origin__ is typing.Generic - for b in bases) else "Protocol" - raise TypeError(f"Some type variables ({s_vars}) are" - f" not listed in {cls_name}[{s_args}]") - tvars = gvars - - initial_bases = bases - if (extra is not None and type(extra) is abc.ABCMeta and - extra not in bases): - bases = (extra,) + bases - bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b - for b in bases) - if any(isinstance(b, GenericMeta) and b is not typing.Generic for b in bases): - bases = tuple(b for b in bases if b is not typing.Generic) - namespace.update({'__origin__': origin, '__extra__': extra}) - self = super(GenericMeta, cls).__new__(cls, name, bases, namespace, - _root=True) - super(GenericMeta, self).__setattr__('_gorg', - self if not origin else - _gorg(origin)) - self.__parameters__ = tvars - self.__args__ = tuple(... if a is typing._TypingEllipsis else - () if a is typing._TypingEmpty else - a for a in args) if args else None - self.__next_in_mro__ = _next_in_mro(self) - if orig_bases is None: - self.__orig_bases__ = initial_bases - elif origin is not None: - self._abc_registry = origin._abc_registry - self._abc_cache = origin._abc_cache - if hasattr(self, '_subs_tree'): - self.__tree_hash__ = (hash(self._subs_tree()) if origin else - super(GenericMeta, self).__hash__()) - return self - - def __init__(cls, *args, **kwargs): - super().__init__(*args, **kwargs) - if not cls.__dict__.get('_is_protocol', None): - cls._is_protocol = any(b is Protocol or - isinstance(b, _ProtocolMeta) and - b.__origin__ is Protocol - for b in cls.__bases__) - if cls._is_protocol: - for base in cls.__mro__[1:]: - if not (base in (object, typing.Generic) or - base.__module__ == 'collections.abc' and - base.__name__ in _PROTO_WHITELIST or - isinstance(base, typing.TypingMeta) and base._is_protocol or - isinstance(base, GenericMeta) and - base.__origin__ is typing.Generic): - raise TypeError(f'Protocols can only inherit from other' - f' protocols, got {repr(base)}') - - cls.__init__ = _no_init - - def _proto_hook(other): - if not cls.__dict__.get('_is_protocol', None): - return NotImplemented - if not isinstance(other, type): - # Same error as for issubclass(1, int) - raise TypeError('issubclass() arg 1 must be a class') - for attr in _get_protocol_attrs(cls): - for base in other.__mro__: - if attr in base.__dict__: - if base.__dict__[attr] is None: - return NotImplemented - break - annotations = getattr(base, '__annotations__', {}) - if (isinstance(annotations, typing.Mapping) and - attr in annotations and - isinstance(other, _ProtocolMeta) and - other._is_protocol): - break - else: - return NotImplemented - return True - if '__subclasshook__' not in cls.__dict__: - cls.__subclasshook__ = _proto_hook - - def __instancecheck__(self, instance): - # We need this method for situations where attributes are - # assigned in __init__. - if ((not getattr(self, '_is_protocol', False) or - _is_callable_members_only(self)) and - issubclass(instance.__class__, self)): - return True - if self._is_protocol: - if all(hasattr(instance, attr) and - (not callable(getattr(self, attr, None)) or - getattr(instance, attr) is not None) - for attr in _get_protocol_attrs(self)): - return True - return super(GenericMeta, self).__instancecheck__(instance) - - def __subclasscheck__(self, cls): - if self.__origin__ is not None: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: - raise TypeError("Parameterized generics cannot be used with class " - "or instance checks") - return False - if (self.__dict__.get('_is_protocol', None) and - not self.__dict__.get('_is_runtime_protocol', None)): - if sys._getframe(1).f_globals['__name__'] in ['abc', - 'functools', - 'typing']: - return False - raise TypeError("Instance and class checks can only be used with" - " @runtime protocols") - if (self.__dict__.get('_is_runtime_protocol', None) and - not _is_callable_members_only(self)): - if sys._getframe(1).f_globals['__name__'] in ['abc', - 'functools', - 'typing']: - return super(GenericMeta, self).__subclasscheck__(cls) - raise TypeError("Protocols with non-method members" - " don't support issubclass()") - return super(GenericMeta, self).__subclasscheck__(cls) - - @typing._tp_cache - def __getitem__(self, params): - # We also need to copy this from GenericMeta.__getitem__ to get - # special treatment of "Protocol". (Comments removed for brevity.) - if not isinstance(params, tuple): - params = (params,) - if not params and _gorg(self) is not typing.Tuple: - raise TypeError( - f"Parameter list to {self.__qualname__}[...] cannot be empty") - msg = "Parameters to generic types must be types." - params = tuple(_type_check(p, msg) for p in params) - if self in (typing.Generic, Protocol): - if not all(isinstance(p, typing.TypeVar) for p in params): - raise TypeError( - f"Parameters to {repr(self)}[...] must all be type variables") - if len(set(params)) != len(params): - raise TypeError( - f"Parameters to {repr(self)}[...] must all be unique") - tvars = params - args = params - elif self in (typing.Tuple, typing.Callable): - tvars = _type_vars(params) - args = params - elif self.__origin__ in (typing.Generic, Protocol): - raise TypeError(f"Cannot subscript already-subscripted {repr(self)}") - else: - _check_generic(self, params, len(self.__parameters__)) - tvars = _type_vars(params) - args = params - - prepend = (self,) if self.__origin__ is None else () - return self.__class__(self.__name__, - prepend + self.__bases__, - _no_slots_copy(self.__dict__), - tvars=tvars, - args=args, - origin=self, - extra=self.__extra__, - orig_bases=self.__orig_bases__) - - class Protocol(metaclass=_ProtocolMeta): - """Base class for protocol classes. Protocol classes are defined as:: - - class Proto(Protocol): - def meth(self) -> int: - ... - - Such classes are primarily used with static type checkers that recognize - structural subtyping (static duck-typing), for example:: - - class C: - def meth(self) -> int: - return 0 - - def func(x: Proto) -> int: - return x.meth() - - func(C()) # Passes static type check - - See PEP 544 for details. Protocol classes decorated with - @typing_extensions.runtime act as simple-minded runtime protocol that checks - only the presence of given attributes, ignoring their type signatures. - - Protocol classes can be generic, they are defined as:: - - class GenProto(Protocol[T]): - def meth(self) -> T: - ... - """ - __slots__ = () - _is_protocol = True - - def __new__(cls, *args, **kwds): - if _gorg(cls) is Protocol: - raise TypeError("Type Protocol cannot be instantiated; " - "it can be used only as a base class") - return typing._generic_new(cls.__next_in_mro__, cls, *args, **kwds) # 3.8+ if hasattr(typing, 'runtime_checkable'): runtime_checkable = typing.runtime_checkable -# 3.6-3.7 +# 3.7 else: def runtime_checkable(cls): """Mark a protocol class as a runtime protocol, so that it @@ -1031,7 +524,7 @@ def runtime_checkable(cls): # 3.8+ if hasattr(typing, 'SupportsIndex'): SupportsIndex = typing.SupportsIndex -# 3.6-3.7 +# 3.7 else: @runtime_checkable class SupportsIndex(Protocol): @@ -1148,29 +641,22 @@ def __new__(cls, name, bases, ns, total=True): optional_keys.update(base.__dict__.get('__optional_keys__', ())) annotations.update(own_annotations) - if PEP_560: - for annotation_key, annotation_type in own_annotations.items(): - annotation_origin = get_origin(annotation_type) - if annotation_origin is Annotated: - annotation_args = get_args(annotation_type) - if annotation_args: - annotation_type = annotation_args[0] - annotation_origin = get_origin(annotation_type) - - if annotation_origin is Required: - required_keys.add(annotation_key) - elif annotation_origin is NotRequired: - optional_keys.add(annotation_key) - elif total: - required_keys.add(annotation_key) - else: - optional_keys.add(annotation_key) - else: - own_annotation_keys = set(own_annotations.keys()) - if total: - required_keys.update(own_annotation_keys) + for annotation_key, annotation_type in own_annotations.items(): + annotation_origin = get_origin(annotation_type) + if annotation_origin is Annotated: + annotation_args = get_args(annotation_type) + if annotation_args: + annotation_type = annotation_args[0] + annotation_origin = get_origin(annotation_type) + + if annotation_origin is Required: + required_keys.add(annotation_key) + elif annotation_origin is NotRequired: + optional_keys.add(annotation_key) + elif total: + required_keys.add(annotation_key) else: - optional_keys.update(own_annotation_keys) + optional_keys.add(annotation_key) tp_dict.__annotations__ = annotations tp_dict.__required_keys__ = frozenset(required_keys) @@ -1233,7 +719,7 @@ class Film(TypedDict): if hasattr(typing, "Required"): get_type_hints = typing.get_type_hints -elif PEP_560: +else: import functools import types @@ -1312,7 +798,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): # to work. _AnnotatedAlias = typing._AnnotatedAlias # 3.7-3.8 -elif PEP_560: +else: class _AnnotatedAlias(typing._GenericAlias, _root=True): """Runtime representation of an annotated type. @@ -1409,198 +895,52 @@ def __init_subclass__(cls, *args, **kwargs): raise TypeError( f"Cannot subclass {cls.__module__}.Annotated" ) -# 3.6 -else: - - def _is_dunder(name): - """Returns True if name is a __dunder_variable_name__.""" - return len(name) > 4 and name.startswith('__') and name.endswith('__') - # Prior to Python 3.7 types did not have `copy_with`. A lot of the equality - # checks, argument expansion etc. are done on the _subs_tre. As a result we - # can't provide a get_type_hints function that strips out annotations. - - class AnnotatedMeta(typing.GenericMeta): - """Metaclass for Annotated""" +# Python 3.8 has get_origin() and get_args() but those implementations aren't +# Annotated-aware, so we can't use those. Python 3.9's versions don't support +# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do. +if sys.version_info[:2] >= (3, 10): + get_origin = typing.get_origin + get_args = typing.get_args +# 3.7-3.9 +else: + try: + # 3.9+ + from typing import _BaseGenericAlias + except ImportError: + _BaseGenericAlias = typing._GenericAlias + try: + # 3.9+ + from typing import GenericAlias + except ImportError: + GenericAlias = typing._GenericAlias - def __new__(cls, name, bases, namespace, **kwargs): - if any(b is not object for b in bases): - raise TypeError("Cannot subclass " + str(Annotated)) - return super().__new__(cls, name, bases, namespace, **kwargs) + def get_origin(tp): + """Get the unsubscripted version of a type. - @property - def __metadata__(self): - return self._subs_tree()[2] + This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar + and Annotated. Return None for unsupported types. Examples:: - def _tree_repr(self, tree): - cls, origin, metadata = tree - if not isinstance(origin, tuple): - tp_repr = typing._type_repr(origin) - else: - tp_repr = origin[0]._tree_repr(origin) - metadata_reprs = ", ".join(repr(arg) for arg in metadata) - return f'{cls}[{tp_repr}, {metadata_reprs}]' - - def _subs_tree(self, tvars=None, args=None): # noqa - if self is Annotated: - return Annotated - res = super()._subs_tree(tvars=tvars, args=args) - # Flatten nested Annotated - if isinstance(res[1], tuple) and res[1][0] is Annotated: - sub_tp = res[1][1] - sub_annot = res[1][2] - return (Annotated, sub_tp, sub_annot + res[2]) - return res + get_origin(Literal[42]) is Literal + get_origin(int) is None + get_origin(ClassVar[int]) is ClassVar + get_origin(Generic) is Generic + get_origin(Generic[T]) is Generic + get_origin(Union[T, int]) is Union + get_origin(List[Tuple[T, T]][int]) == list + get_origin(P.args) is P + """ + if isinstance(tp, _AnnotatedAlias): + return Annotated + if isinstance(tp, (typing._GenericAlias, GenericAlias, _BaseGenericAlias, + ParamSpecArgs, ParamSpecKwargs)): + return tp.__origin__ + if tp is typing.Generic: + return typing.Generic + return None - def _get_cons(self): - """Return the class used to create instance of this type.""" - if self.__origin__ is None: - raise TypeError("Cannot get the underlying type of a " - "non-specialized Annotated type.") - tree = self._subs_tree() - while isinstance(tree, tuple) and tree[0] is Annotated: - tree = tree[1] - if isinstance(tree, tuple): - return tree[0] - else: - return tree - - @typing._tp_cache - def __getitem__(self, params): - if not isinstance(params, tuple): - params = (params,) - if self.__origin__ is not None: # specializing an instantiated type - return super().__getitem__(params) - elif not isinstance(params, tuple) or len(params) < 2: - raise TypeError("Annotated[...] should be instantiated " - "with at least two arguments (a type and an " - "annotation).") - else: - if ( - isinstance(params[0], typing._TypingBase) and - type(params[0]).__name__ == "_ClassVar" - ): - tp = params[0] - else: - msg = "Annotated[t, ...]: t must be a type." - tp = typing._type_check(params[0], msg) - metadata = tuple(params[1:]) - return self.__class__( - self.__name__, - self.__bases__, - _no_slots_copy(self.__dict__), - tvars=_type_vars((tp,)), - # Metadata is a tuple so it won't be touched by _replace_args et al. - args=(tp, metadata), - origin=self, - ) - - def __call__(self, *args, **kwargs): - cons = self._get_cons() - result = cons(*args, **kwargs) - try: - result.__orig_class__ = self - except AttributeError: - pass - return result - - def __getattr__(self, attr): - # For simplicity we just don't relay all dunder names - if self.__origin__ is not None and not _is_dunder(attr): - return getattr(self._get_cons(), attr) - raise AttributeError(attr) - - def __setattr__(self, attr, value): - if _is_dunder(attr) or attr.startswith('_abc_'): - super().__setattr__(attr, value) - elif self.__origin__ is None: - raise AttributeError(attr) - else: - setattr(self._get_cons(), attr, value) - - def __instancecheck__(self, obj): - raise TypeError("Annotated cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Annotated cannot be used with issubclass().") - - class Annotated(metaclass=AnnotatedMeta): - """Add context specific metadata to a type. - - Example: Annotated[int, runtime_check.Unsigned] indicates to the - hypothetical runtime_check module that this type is an unsigned int. - Every other consumer of this type can ignore this metadata and treat - this type as int. - - The first argument to Annotated must be a valid type, the remaining - arguments are kept as a tuple in the __metadata__ field. - - Details: - - - It's an error to call `Annotated` with less than two arguments. - - Nested Annotated are flattened:: - - Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3] - - - Instantiating an annotated type is equivalent to instantiating the - underlying type:: - - Annotated[C, Ann1](5) == C(5) - - - Annotated can be used as a generic type alias:: - - Optimized = Annotated[T, runtime.Optimize()] - Optimized[int] == Annotated[int, runtime.Optimize()] - - OptimizedList = Annotated[List[T], runtime.Optimize()] - OptimizedList[int] == Annotated[List[int], runtime.Optimize()] - """ - -# Python 3.8 has get_origin() and get_args() but those implementations aren't -# Annotated-aware, so we can't use those. Python 3.9's versions don't support -# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do. -if sys.version_info[:2] >= (3, 10): - get_origin = typing.get_origin - get_args = typing.get_args -# 3.7-3.9 -elif PEP_560: - try: - # 3.9+ - from typing import _BaseGenericAlias - except ImportError: - _BaseGenericAlias = typing._GenericAlias - try: - # 3.9+ - from typing import GenericAlias - except ImportError: - GenericAlias = typing._GenericAlias - - def get_origin(tp): - """Get the unsubscripted version of a type. - - This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar - and Annotated. Return None for unsupported types. Examples:: - - get_origin(Literal[42]) is Literal - get_origin(int) is None - get_origin(ClassVar[int]) is ClassVar - get_origin(Generic) is Generic - get_origin(Generic[T]) is Generic - get_origin(Union[T, int]) is Union - get_origin(List[Tuple[T, T]][int]) == list - get_origin(P.args) is P - """ - if isinstance(tp, _AnnotatedAlias): - return Annotated - if isinstance(tp, (typing._GenericAlias, GenericAlias, _BaseGenericAlias, - ParamSpecArgs, ParamSpecKwargs)): - return tp.__origin__ - if tp is typing.Generic: - return typing.Generic - return None - - def get_args(tp): - """Get type arguments with all substitutions performed. + def get_args(tp): + """Get type arguments with all substitutions performed. For unions, basic simplifications used by Union constructor are performed. Examples:: @@ -1645,7 +985,7 @@ def TypeAlias(self, parameters): """ raise TypeError(f"{self} is not subscriptable") # 3.7-3.8 -elif sys.version_info[:2] >= (3, 7): +else: class _TypeAliasForm(typing._SpecialForm, _root=True): def __repr__(self): return 'typing_extensions.' + self._name @@ -1661,44 +1001,13 @@ def __repr__(self): It's invalid when used anywhere except as in the example above.""") -# 3.6 -else: - class _TypeAliasMeta(typing.TypingMeta): - """Metaclass for TypeAlias""" - - def __repr__(self): - return 'typing_extensions.TypeAlias' - - class _TypeAliasBase(typing._FinalTypingBase, metaclass=_TypeAliasMeta, _root=True): - """Special marker indicating that an assignment should - be recognized as a proper type alias definition by type - checkers. - - For example:: - - Predicate: TypeAlias = Callable[..., bool] - - It's invalid when used anywhere except as in the example above. - """ - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("TypeAlias cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("TypeAlias cannot be used with issubclass().") - - def __repr__(self): - return 'typing_extensions.TypeAlias' - - TypeAlias = _TypeAliasBase(_root=True) # Python 3.10+ has PEP 612 if hasattr(typing, 'ParamSpecArgs'): ParamSpecArgs = typing.ParamSpecArgs ParamSpecKwargs = typing.ParamSpecKwargs -# 3.6-3.9 +# 3.7-3.9 else: class _Immutable: """Mixin to indicate that object should not be copied.""" @@ -1759,7 +1068,7 @@ def __eq__(self, other): # 3.10+ if hasattr(typing, 'ParamSpec'): ParamSpec = typing.ParamSpec -# 3.6-3.9 +# 3.7-3.9 else: # Inherits from list as a workaround for Callable checks in Python < 3.9.2. @@ -1861,28 +1170,17 @@ def __reduce__(self): def __call__(self, *args, **kwargs): pass - if not PEP_560: - # Only needed in 3.6. - def _get_type_vars(self, tvars): - if self not in tvars: - tvars.append(self) - -# 3.6-3.9 +# 3.7-3.9 if not hasattr(typing, 'Concatenate'): # Inherits from list as a workaround for Callable checks in Python < 3.9.2. class _ConcatenateGenericAlias(list): # Trick Generic into looking into this for __parameters__. - if PEP_560: - __class__ = typing._GenericAlias - else: - __class__ = typing._TypingBase + __class__ = typing._GenericAlias # Flag in 3.8. _special = False - # Attribute in 3.6 and earlier. - _gorg = typing.Generic def __init__(self, origin, args): super().__init__(args) @@ -1907,14 +1205,8 @@ def __parameters__(self): tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec)) ) - if not PEP_560: - # Only required in 3.6. - def _get_type_vars(self, tvars): - if self.__origin__ and self.__parameters__: - typing._get_type_vars(self.__parameters__, tvars) - -# 3.6-3.9 +# 3.7-3.9 @typing._tp_cache def _concatenate_getitem(self, parameters): if parameters == (): @@ -1949,7 +1241,7 @@ def Concatenate(self, parameters): """ return _concatenate_getitem(self, parameters) # 3.7-8 -elif sys.version_info[:2] >= (3, 7): +else: class _ConcatenateForm(typing._SpecialForm, _root=True): def __repr__(self): return 'typing_extensions.' + self._name @@ -1969,42 +1261,6 @@ def __getitem__(self, parameters): See PEP 612 for detailed information. """) -# 3.6 -else: - class _ConcatenateAliasMeta(typing.TypingMeta): - """Metaclass for Concatenate.""" - - def __repr__(self): - return 'typing_extensions.Concatenate' - - class _ConcatenateAliasBase(typing._FinalTypingBase, - metaclass=_ConcatenateAliasMeta, - _root=True): - """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a - higher order function which adds, removes or transforms parameters of a - callable. - - For example:: - - Callable[Concatenate[int, P], int] - - See PEP 612 for detailed information. - """ - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("Concatenate cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Concatenate cannot be used with issubclass().") - - def __repr__(self): - return 'typing_extensions.Concatenate' - - def __getitem__(self, parameters): - return _concatenate_getitem(self, parameters) - - Concatenate = _ConcatenateAliasBase(_root=True) # 3.10+ if hasattr(typing, 'TypeGuard'): @@ -2062,7 +1318,7 @@ def is_str(val: Union[str, float]): item = typing._type_check(parameters, f'{self} accepts only single type.') return typing._GenericAlias(self, (item,)) # 3.7-3.8 -elif sys.version_info[:2] >= (3, 7): +else: class _TypeGuardForm(typing._SpecialForm, _root=True): def __repr__(self): @@ -2117,138 +1373,55 @@ def is_str(val: Union[str, float]): ``TypeGuard`` also works with type variables. For more information, see PEP 647 (User-Defined Type Guards). """) -# 3.6 -else: - class _TypeGuard(typing._FinalTypingBase, _root=True): - """Special typing form used to annotate the return type of a user-defined - type guard function. ``TypeGuard`` only accepts a single type argument. - At runtime, functions marked this way should return a boolean. - - ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static - type checkers to determine a more precise type of an expression within a - program's code flow. Usually type narrowing is done by analyzing - conditional code flow and applying the narrowing to a block of code. The - conditional expression here is sometimes referred to as a "type guard". - - Sometimes it would be convenient to use a user-defined boolean function - as a type guard. Such a function should use ``TypeGuard[...]`` as its - return type to alert static type checkers to this intention. - - Using ``-> TypeGuard`` tells the static type checker that for a given - function: - - 1. The return value is a boolean. - 2. If the return value is ``True``, the type of its argument - is the type inside ``TypeGuard``. - - For example:: - - def is_str(val: Union[str, float]): - # "isinstance" type guard - if isinstance(val, str): - # Type of ``val`` is narrowed to ``str`` - ... - else: - # Else, type of ``val`` is narrowed to ``float``. - ... - - Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower - form of ``TypeA`` (it can even be a wider form) and this may lead to - type-unsafe results. The main reason is to allow for things like - narrowing ``List[object]`` to ``List[str]`` even though the latter is not - a subtype of the former, since ``List`` is invariant. The responsibility of - writing type-safe type guards is left to the user. - - ``TypeGuard`` also works with type variables. For more information, see - PEP 647 (User-Defined Type Guards). - """ - - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(typing._type_check(item, - f'{cls.__name__[1:]} accepts only a single type.'), - _root=True) - raise TypeError(f'{cls.__name__[1:]} cannot be further subscripted') - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += f'[{typing._type_repr(self.__type__)}]' - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _TypeGuard): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - TypeGuard = _TypeGuard(_root=True) +# Vendored from cpython typing._SpecialFrom +class _SpecialForm(typing._Final, _root=True): + __slots__ = ('_name', '__doc__', '_getitem') -if sys.version_info[:2] >= (3, 7): - # Vendored from cpython typing._SpecialFrom - class _SpecialForm(typing._Final, _root=True): - __slots__ = ('_name', '__doc__', '_getitem') + def __init__(self, getitem): + self._getitem = getitem + self._name = getitem.__name__ + self.__doc__ = getitem.__doc__ - def __init__(self, getitem): - self._getitem = getitem - self._name = getitem.__name__ - self.__doc__ = getitem.__doc__ - - def __getattr__(self, item): - if item in {'__name__', '__qualname__'}: - return self._name + def __getattr__(self, item): + if item in {'__name__', '__qualname__'}: + return self._name - raise AttributeError(item) + raise AttributeError(item) - def __mro_entries__(self, bases): - raise TypeError(f"Cannot subclass {self!r}") + def __mro_entries__(self, bases): + raise TypeError(f"Cannot subclass {self!r}") - def __repr__(self): - return f'typing_extensions.{self._name}' + def __repr__(self): + return f'typing_extensions.{self._name}' - def __reduce__(self): - return self._name + def __reduce__(self): + return self._name - def __call__(self, *args, **kwds): - raise TypeError(f"Cannot instantiate {self!r}") + def __call__(self, *args, **kwds): + raise TypeError(f"Cannot instantiate {self!r}") - def __or__(self, other): - return typing.Union[self, other] + def __or__(self, other): + return typing.Union[self, other] - def __ror__(self, other): - return typing.Union[other, self] + def __ror__(self, other): + return typing.Union[other, self] - def __instancecheck__(self, obj): - raise TypeError(f"{self} cannot be used with isinstance()") + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance()") - def __subclasscheck__(self, cls): - raise TypeError(f"{self} cannot be used with issubclass()") + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass()") - @typing._tp_cache - def __getitem__(self, parameters): - return self._getitem(self, parameters) + @typing._tp_cache + def __getitem__(self, parameters): + return self._getitem(self, parameters) if hasattr(typing, "LiteralString"): LiteralString = typing.LiteralString -elif sys.version_info[:2] >= (3, 7): +else: @_SpecialForm def LiteralString(self, params): """Represents an arbitrary literal string. @@ -2267,38 +1440,11 @@ def query(sql: LiteralString) -> ...: """ raise TypeError(f"{self} is not subscriptable") -else: - class _LiteralString(typing._FinalTypingBase, _root=True): - """Represents an arbitrary literal string. - - Example:: - - from typing_extensions import LiteralString - - def query(sql: LiteralString) -> ...: - ... - - query("SELECT * FROM table") # ok - query(f"SELECT * FROM {input()}") # not ok - - See PEP 675 for details. - - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError(f"{self} cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError(f"{self} cannot be used with issubclass().") - - LiteralString = _LiteralString(_root=True) if hasattr(typing, "Self"): Self = typing.Self -elif sys.version_info[:2] >= (3, 7): +else: @_SpecialForm def Self(self, params): """Used to spell the type of "self" in classes. @@ -2315,35 +1461,11 @@ def parse(self, data: bytes) -> Self: """ raise TypeError(f"{self} is not subscriptable") -else: - class _Self(typing._FinalTypingBase, _root=True): - """Used to spell the type of "self" in classes. - - Example:: - - from typing import Self - - class ReturnsSelf: - def parse(self, data: bytes) -> Self: - ... - return self - - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError(f"{self} cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError(f"{self} cannot be used with issubclass().") - - Self = _Self(_root=True) if hasattr(typing, "Never"): Never = typing.Never -elif sys.version_info[:2] >= (3, 7): +else: @_SpecialForm def Never(self, params): """The bottom type, a type that has no members. @@ -2369,39 +1491,6 @@ def int_or_str(arg: int | str) -> None: """ raise TypeError(f"{self} is not subscriptable") -else: - class _Never(typing._FinalTypingBase, _root=True): - """The bottom type, a type that has no members. - - This can be used to define a function that should never be - called, or a function that never returns:: - - from typing_extensions import Never - - def never_call_me(arg: Never) -> None: - pass - - def int_or_str(arg: int | str) -> None: - never_call_me(arg) # type checker error - match arg: - case int(): - print("It's an int") - case str(): - print("It's a str") - case _: - never_call_me(arg) # ok, arg is of type Never - - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError(f"{self} cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError(f"{self} cannot be used with issubclass().") - - Never = _Never(_root=True) if hasattr(typing, 'Required'): @@ -2449,7 +1538,7 @@ class Movie(TypedDict): item = typing._type_check(parameters, f'{self._name} accepts only single type') return typing._GenericAlias(self, (item,)) -elif sys.version_info[:2] >= (3, 7): +else: class _RequiredForm(typing._SpecialForm, _root=True): def __repr__(self): return 'typing_extensions.' + self._name @@ -2490,78 +1579,6 @@ class Movie(TypedDict): year=1999, ) """) -else: - # NOTE: Modeled after _Final's implementation when _FinalTypingBase available - class _MaybeRequired(typing._FinalTypingBase, _root=True): - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(typing._type_check(item, - '{} accepts only single type.'.format(cls.__name__[1:])), - _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, type(self)): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - class _Required(_MaybeRequired, _root=True): - """A special typing construct to mark a key of a total=False TypedDict - as required. For example: - - class Movie(TypedDict, total=False): - title: Required[str] - year: int - - m = Movie( - title='The Matrix', # typechecker error if key is omitted - year=1999, - ) - - There is no runtime checking that a required key is actually provided - when instantiating a related TypedDict. - """ - - class _NotRequired(_MaybeRequired, _root=True): - """A special typing construct to mark a key of a TypedDict as - potentially missing. For example: - - class Movie(TypedDict): - title: str - year: NotRequired[int] - - m = Movie( - title='The Matrix', # typechecker error if key is omitted - year=1999, - ) - """ - - Required = _Required(_root=True) - NotRequired = _NotRequired(_root=True) if sys.version_info[:2] >= (3, 9): @@ -2590,7 +1607,7 @@ def add_batch_axis( def _is_unpack(obj): return isinstance(obj, _UnpackAlias) -elif sys.version_info[:2] >= (3, 7): +else: class _UnpackAlias(typing._GenericAlias, _root=True): __class__ = typing.TypeVar @@ -2619,64 +1636,6 @@ def add_batch_axis( def _is_unpack(obj): return isinstance(obj, _UnpackAlias) -else: - # NOTE: Modeled after _Final's implementation when _FinalTypingBase available - class _Unpack(typing._FinalTypingBase, _root=True): - """A special typing construct to unpack a variadic type. For example: - - Shape = TypeVarTuple('Shape') - Batch = NewType('Batch', int) - - def add_batch_axis( - x: Array[Unpack[Shape]] - ) -> Array[Batch, Unpack[Shape]]: ... - - """ - __slots__ = ('__type__',) - __class__ = typing.TypeVar - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(typing._type_check(item, - 'Unpack accepts only single type.'), - _root=True) - raise TypeError('Unpack cannot be further subscripted') - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _Unpack): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - # For 3.6 only - def _get_type_vars(self, tvars): - self.__type__._get_type_vars(tvars) - - Unpack = _Unpack(_root=True) - - def _is_unpack(obj): - return isinstance(obj, _Unpack) - class TypeVarTuple: """Type variable tuple. @@ -2757,12 +1716,6 @@ def __init_subclass__(self, *args, **kwds): if '_root' not in kwds: raise TypeError("Cannot subclass special typing classes") - if not PEP_560: - # Only needed in 3.6. - def _get_type_vars(self, tvars): - if self not in tvars: - tvars.append(self) - if hasattr(typing, "reveal_type"): reveal_type = typing.reveal_type From 00a403095e4ec816414107155921d5c5ccb5877f Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Tue, 22 Mar 2022 14:45:53 -0700 Subject: [PATCH 03/13] test_typing_extensions: fix lint (#1111) --- src/test_typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 20e35f43..53a8343f 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -2591,7 +2591,7 @@ def stmethod(): ... def prop(self): ... @final - @lru_cache() + @lru_cache() # noqa: B019 def cached(self): ... # Use getattr_static because the descriptor returns the From 73d560881e62aeb1af0f3aff9683024a3fc7b0b7 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 22 Mar 2022 14:46:02 -0700 Subject: [PATCH 04/13] LiteralString, NotRequired, Required will be in 3.11 (#1110) --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index bb890689..d29d9f53 100644 --- a/README.rst +++ b/README.rst @@ -41,16 +41,16 @@ This module currently contains the following: - Experimental features - - ``LiteralString`` (see PEP 675) - ``@dataclass_transform()`` (see PEP 681) - - ``NotRequired`` (see PEP 655) - - ``Required`` (see PEP 655) - In ``typing`` since Python 3.11 - ``assert_never`` + - ``LiteralString`` (see PEP 675) - ``Never`` + - ``NotRequired`` (see PEP 655) - ``reveal_type`` + - ``Required`` (see PEP 655) - ``Self`` (see PEP 673) - ``TypeVarTuple`` (see PEP 646) - ``Unpack`` (see PEP 646) From f520b8a839727061583a11280586cb05dd072a75 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 22 Mar 2022 15:29:58 -0700 Subject: [PATCH 05/13] Add assert_type (#1103) --- CHANGELOG | 1 + README.rst | 1 + src/test_typing_extensions.py | 19 +++++++++++++++++-- src/typing_extensions.py | 21 +++++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f098464e..a9a59804 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # Unreleased +- Add `typing.assert_type`. Backport from bpo-46480. - Drop support for Python 3.6. Original patch by Adam Turner (@AA-Turner). # Release 4.1.1 (February 13, 2022) diff --git a/README.rst b/README.rst index d29d9f53..9abed044 100644 --- a/README.rst +++ b/README.rst @@ -46,6 +46,7 @@ This module currently contains the following: - In ``typing`` since Python 3.11 - ``assert_never`` + - ``assert_type`` - ``LiteralString`` (see PEP 675) - ``Never`` - ``NotRequired`` (see PEP 655) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 53a8343f..b8fe5e35 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -12,7 +12,7 @@ from unittest import TestCase, main, skipUnless, skipIf from test import ann_module, ann_module2, ann_module3 import typing -from typing import TypeVar, Optional, Union, Any +from typing import TypeVar, Optional, Union, Any, AnyStr from typing import T, KT, VT # Not in __all__. from typing import Tuple, List, Dict, Iterable, Iterator, Callable from typing import Generic, NamedTuple @@ -23,7 +23,7 @@ from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict from typing_extensions import TypeVarTuple, Unpack, dataclass_transform, reveal_type, Never, assert_never, LiteralString -from typing_extensions import get_type_hints, get_origin, get_args +from typing_extensions import assert_type, get_type_hints, get_origin, get_args # Flags used to mark tests that only apply after a specific # version of the typing module. @@ -425,6 +425,21 @@ def blah(): blah() +class AssertTypeTests(BaseTestCase): + + def test_basics(self): + arg = 42 + self.assertIs(assert_type(arg, int), arg) + self.assertIs(assert_type(arg, Union[str, float]), arg) + self.assertIs(assert_type(arg, AnyStr), arg) + self.assertIs(assert_type(arg, None), arg) + + def test_errors(self): + # Bogus calls are not expected to fail. + arg = 42 + self.assertIs(assert_type(arg, 42), arg) + self.assertIs(assert_type(arg, 'hello'), arg) + T_a = TypeVar('T_a') diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 5c43354e..5698a76c 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -717,6 +717,27 @@ class Film(TypedDict): """ return isinstance(tp, tuple(_TYPEDDICT_TYPES)) + +if hasattr(typing, "assert_type"): + assert_type = typing.assert_type + +else: + def assert_type(__val, __typ): + """Assert (to the type checker) that the value is of the given type. + + When the type checker encounters a call to assert_type(), it + emits an error if the value is not of the specified type:: + + def greet(name: str) -> None: + assert_type(name, str) # ok + assert_type(name, int) # type checker error + + At runtime this returns the first argument unchanged and otherwise + does nothing. + """ + return __val + + if hasattr(typing, "Required"): get_type_hints = typing.get_type_hints else: From 759c89476ce6cd2ebec4ccd1f46b168a976b717a Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 11 Apr 2022 10:37:18 -0700 Subject: [PATCH 06/13] Fix "accepts only single type" errors (#1130) - Add "a" to make the message grammatical - Add a trailing period because _type_check adds another sentence after it. For example, `Unpack[3]` on 3.10 currently fails with `TypeError: Unpack accepts only single type Got 3.`. - Use an f-string --- src/typing_extensions.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 5698a76c..bd2ab337 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -157,7 +157,7 @@ def __repr__(self): def __getitem__(self, parameters): item = typing._type_check(parameters, - f'{self._name} accepts only single type') + f'{self._name} accepts only a single type.') return typing._GenericAlias(self, (item,)) Final = _FinalForm('Final', @@ -1336,7 +1336,7 @@ def is_str(val: Union[str, float]): ``TypeGuard`` also works with type variables. For more information, see PEP 647 (User-Defined Type Guards). """ - item = typing._type_check(parameters, f'{self} accepts only single type.') + item = typing._type_check(parameters, f'{self} accepts only a single type.') return typing._GenericAlias(self, (item,)) # 3.7-3.8 else: @@ -1539,7 +1539,7 @@ class Movie(TypedDict, total=False): There is no runtime checking that a required key is actually provided when instantiating a related TypedDict. """ - item = typing._type_check(parameters, f'{self._name} accepts only single type') + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') return typing._GenericAlias(self, (item,)) @_ExtensionsSpecialForm @@ -1556,7 +1556,7 @@ class Movie(TypedDict): year=1999, ) """ - item = typing._type_check(parameters, f'{self._name} accepts only single type') + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') return typing._GenericAlias(self, (item,)) else: @@ -1566,7 +1566,7 @@ def __repr__(self): def __getitem__(self, parameters): item = typing._type_check(parameters, - '{} accepts only single type'.format(self._name)) + f'{self._name} accepts only a single type.') return typing._GenericAlias(self, (item,)) Required = _RequiredForm( @@ -1622,7 +1622,7 @@ def add_batch_axis( ) -> Array[Batch, Unpack[Shape]]: ... """ - item = typing._type_check(parameters, f'{self._name} accepts only single type') + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') return _UnpackAlias(self, (item,)) def _is_unpack(obj): @@ -1638,7 +1638,7 @@ def __repr__(self): def __getitem__(self, parameters): item = typing._type_check(parameters, - f'{self._name} accepts only single type') + f'{self._name} accepts only a single type.') return _UnpackAlias(self, (item,)) Unpack = _UnpackForm( From 4d21e24ee7a37680503646312b8912044e4798dc Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 15 Apr 2022 23:16:04 +0100 Subject: [PATCH 07/13] Add `assert_type` to `__all__` (#1136) Looks like this is in `typing.__all__` but was missed out of `typing_extensions.__all__` --- src/typing_extensions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index bd2ab337..c959adbf 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -43,6 +43,7 @@ # One-off things. 'Annotated', 'assert_never', + 'assert_type', 'dataclass_transform', 'final', 'get_args', From d00b3457babd7ebc868b638e992784beeedf81da Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 15 Apr 2022 17:10:25 -0700 Subject: [PATCH 08/13] test that all names are present in __all__ (#1138) --- src/test_typing_extensions.py | 21 +++++++++++++++++++++ src/typing_extensions.py | 10 ++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index b8fe5e35..1439e517 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -2743,6 +2743,27 @@ def test_typing_extensions_includes_standard(self): for name in a: self.assertTrue(hasattr(typing_extensions, name)) + def test_all_names_in___all__(self): + exclude = { + 'GenericMeta', + 'KT', + 'PEP_560', + 'T', + 'T_co', + 'T_contra', + 'VT', + } + actual_names = { + name for name in dir(typing_extensions) + if not name.startswith("_") + and not isinstance(getattr(typing_extensions, name), types.ModuleType) + } + # Make sure all public names are in __all__ + self.assertEqual({*exclude, *typing_extensions.__all__}, + actual_names) + # Make sure all excluded names actually exist + self.assertLessEqual(exclude, actual_names) + def test_typing_extensions_defers_when_possible(self): exclude = { 'overload', diff --git a/src/typing_extensions.py b/src/typing_extensions.py index c959adbf..d5e40497 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -15,6 +15,8 @@ 'Final', 'LiteralString', 'ParamSpec', + 'ParamSpecArgs', + 'ParamSpecKwargs', 'Self', 'Type', 'TypeVarTuple', @@ -933,9 +935,9 @@ def __init_subclass__(cls, *args, **kwargs): _BaseGenericAlias = typing._GenericAlias try: # 3.9+ - from typing import GenericAlias + from typing import GenericAlias as _typing_GenericAlias except ImportError: - GenericAlias = typing._GenericAlias + _typing_GenericAlias = typing._GenericAlias def get_origin(tp): """Get the unsubscripted version of a type. @@ -954,7 +956,7 @@ def get_origin(tp): """ if isinstance(tp, _AnnotatedAlias): return Annotated - if isinstance(tp, (typing._GenericAlias, GenericAlias, _BaseGenericAlias, + if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias, _BaseGenericAlias, ParamSpecArgs, ParamSpecKwargs)): return tp.__origin__ if tp is typing.Generic: @@ -974,7 +976,7 @@ def get_args(tp): """ if isinstance(tp, _AnnotatedAlias): return (tp.__origin__,) + tp.__metadata__ - if isinstance(tp, (typing._GenericAlias, GenericAlias)): + if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias)): if getattr(tp, "_special", False): return () res = tp.__args__ From 92d508d262d84d503b11c3fa63a076e571fbdedd Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Apr 2022 10:11:18 -0700 Subject: [PATCH 09/13] Add get_overloads() (#1140) Co-authored-by: Alex Waygood --- CHANGELOG | 5 ++- README.rst | 6 +++ src/test_typing_extensions.py | 74 ++++++++++++++++++++++++++++++++++- src/typing_extensions.py | 70 ++++++++++++++++++++++++++++++++- 4 files changed, 152 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a9a59804..970bbd4a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ # Unreleased -- Add `typing.assert_type`. Backport from bpo-46480. +- Add `typing_extensions.get_overloads` and + `typing_extensions.clear_overloads`, and add registry support to + `typing_extensions.overload`. Backport from python/cpython#89263. +- Add `typing_extensions.assert_type`. Backport from bpo-46480. - Drop support for Python 3.6. Original patch by Adam Turner (@AA-Turner). # Release 4.1.1 (February 13, 2022) diff --git a/README.rst b/README.rst index 9abed044..3a23b755 100644 --- a/README.rst +++ b/README.rst @@ -47,6 +47,8 @@ This module currently contains the following: - ``assert_never`` - ``assert_type`` + - ``clear_overloads`` + - ``get_overloads`` - ``LiteralString`` (see PEP 675) - ``Never`` - ``NotRequired`` (see PEP 655) @@ -122,6 +124,10 @@ Certain objects were changed after they were added to ``typing``, and Python 3.8 and lack support for ``ParamSpecArgs`` and ``ParamSpecKwargs`` in 3.9. - ``@final`` was changed in Python 3.11 to set the ``.__final__`` attribute. +- ``@overload`` was changed in Python 3.11 to make function overloads + introspectable at runtime. In order to access overloads with + ``typing_extensions.get_overloads()``, you must use + ``@typing_extensions.overload``. There are a few types whose interface was modified between different versions of typing. For example, ``typing.Sequence`` was modified to diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 1439e517..ab03244d 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -3,6 +3,7 @@ import abc import contextlib import collections +from collections import defaultdict import collections.abc from functools import lru_cache import inspect @@ -10,6 +11,7 @@ import subprocess import types from unittest import TestCase, main, skipUnless, skipIf +from unittest.mock import patch from test import ann_module, ann_module2, ann_module3 import typing from typing import TypeVar, Optional, Union, Any, AnyStr @@ -21,9 +23,10 @@ from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, Self from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired -from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload, final, is_typeddict +from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, final, is_typeddict from typing_extensions import TypeVarTuple, Unpack, dataclass_transform, reveal_type, Never, assert_never, LiteralString from typing_extensions import assert_type, get_type_hints, get_origin, get_args +from typing_extensions import clear_overloads, get_overloads, overload # Flags used to mark tests that only apply after a specific # version of the typing module. @@ -403,6 +406,20 @@ def test_no_multiple_subscripts(self): Literal[1][1] +class MethodHolder: + @classmethod + def clsmethod(cls): ... + @staticmethod + def stmethod(): ... + def method(self): ... + + +if TYPING_3_11_0: + registry_holder = typing +else: + registry_holder = typing_extensions + + class OverloadTests(BaseTestCase): def test_overload_fails(self): @@ -424,6 +441,61 @@ def blah(): blah() + def set_up_overloads(self): + def blah(): + pass + + overload1 = blah + overload(blah) + + def blah(): + pass + + overload2 = blah + overload(blah) + + def blah(): + pass + + return blah, [overload1, overload2] + + # Make sure we don't clear the global overload registry + @patch( + f"{registry_holder.__name__}._overload_registry", + defaultdict(lambda: defaultdict(dict)) + ) + def test_overload_registry(self): + registry = registry_holder._overload_registry + # The registry starts out empty + self.assertEqual(registry, {}) + + impl, overloads = self.set_up_overloads() + self.assertNotEqual(registry, {}) + self.assertEqual(list(get_overloads(impl)), overloads) + + def some_other_func(): pass + overload(some_other_func) + other_overload = some_other_func + def some_other_func(): pass + self.assertEqual(list(get_overloads(some_other_func)), [other_overload]) + + # Make sure that after we clear all overloads, the registry is + # completely empty. + clear_overloads() + self.assertEqual(registry, {}) + self.assertEqual(get_overloads(impl), []) + + # Querying a function with no overloads shouldn't change the registry. + def the_only_one(): pass + self.assertEqual(get_overloads(the_only_one), []) + self.assertEqual(registry, {}) + + def test_overload_registry_repeated(self): + for _ in range(2): + impl, overloads = self.set_up_overloads() + + self.assertEqual(list(get_overloads(impl)), overloads) + class AssertTypeTests(BaseTestCase): diff --git a/src/typing_extensions.py b/src/typing_extensions.py index d5e40497..49110999 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -1,6 +1,7 @@ import abc import collections import collections.abc +import functools import operator import sys import types as _types @@ -46,7 +47,9 @@ 'Annotated', 'assert_never', 'assert_type', + 'clear_overloads', 'dataclass_transform', + 'get_overloads', 'final', 'get_args', 'get_origin', @@ -249,7 +252,72 @@ def __getitem__(self, parameters): _overload_dummy = typing._overload_dummy # noqa -overload = typing.overload + + +if hasattr(typing, "get_overloads"): # 3.11+ + overload = typing.overload + get_overloads = typing.get_overloads + clear_overloads = typing.clear_overloads +else: + # {module: {qualname: {firstlineno: func}}} + _overload_registry = collections.defaultdict( + functools.partial(collections.defaultdict, dict) + ) + + def overload(func): + """Decorator for overloaded functions/methods. + + In a stub file, place two or more stub definitions for the same + function in a row, each decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + + In a non-stub file (i.e. a regular .py file), do the same but + follow it with an implementation. The implementation should *not* + be decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + def utf8(value): + # implementation goes here + + The overloads for a function can be retrieved at runtime using the + get_overloads() function. + """ + # classmethod and staticmethod + f = getattr(func, "__func__", func) + try: + _overload_registry[f.__module__][f.__qualname__][ + f.__code__.co_firstlineno + ] = func + except AttributeError: + # Not a normal function; ignore. + pass + return _overload_dummy + + def get_overloads(func): + """Return all defined overloads for *func* as a sequence.""" + # classmethod and staticmethod + f = getattr(func, "__func__", func) + if f.__module__ not in _overload_registry: + return [] + mod_dict = _overload_registry[f.__module__] + if f.__qualname__ not in mod_dict: + return [] + return list(mod_dict[f.__qualname__].values()) + + def clear_overloads(): + """Clear all overloads in the registry.""" + _overload_registry.clear() # This is not a real generic class. Don't use outside annotations. From 7f22dcda00409927d6dc443630467a870afcbc5f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Apr 2022 10:17:25 -0700 Subject: [PATCH 10/13] dataclass_transform: accept **kwargs, rename field_descriptors (#1120) --- CHANGELOG | 3 +++ src/test_typing_extensions.py | 18 +++++++++++++----- src/typing_extensions.py | 10 ++++++---- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 970bbd4a..550a1463 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Unreleased +- Update `typing_extensions.dataclass_transform` to rename the + `field_descriptors` parameter to `field_specifiers` and accept + arbitrary keyword arguments. - Add `typing_extensions.get_overloads` and `typing_extensions.clear_overloads`, and add registry support to `typing_extensions.overload`. Backport from python/cpython#89263. diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index ab03244d..8e11eb67 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -2718,7 +2718,8 @@ class CustomerModel: "eq_default": True, "order_default": False, "kw_only_default": True, - "field_descriptors": (), + "field_specifiers": (), + "kwargs": {}, } ) self.assertIs( @@ -2730,7 +2731,12 @@ def test_base_class(self): class ModelBase: def __init_subclass__(cls, *, frozen: bool = False): ... - Decorated = dataclass_transform(eq_default=True, order_default=True)(ModelBase) + Decorated = dataclass_transform( + eq_default=True, + order_default=True, + # Arbitrary unrecognized kwargs are accepted at runtime. + make_everything_awesome=True, + )(ModelBase) class CustomerModel(Decorated, frozen=True): id: int @@ -2742,7 +2748,8 @@ class CustomerModel(Decorated, frozen=True): "eq_default": True, "order_default": True, "kw_only_default": False, - "field_descriptors": (), + "field_specifiers": (), + "kwargs": {"make_everything_awesome": True}, } ) self.assertIsSubclass(CustomerModel, Decorated) @@ -2757,7 +2764,7 @@ def __new__( return super().__new__(cls, name, bases, namespace) Decorated = dataclass_transform( - order_default=True, field_descriptors=(Field,) + order_default=True, field_specifiers=(Field,) )(ModelMeta) class ModelBase(metaclass=Decorated): ... @@ -2772,7 +2779,8 @@ class CustomerModel(ModelBase, init=False): "eq_default": True, "order_default": True, "kw_only_default": False, - "field_descriptors": (Field,), + "field_specifiers": (Field,), + "kwargs": {}, } ) self.assertIsInstance(CustomerModel, Decorated) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 49110999..1e3e1282 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -1866,10 +1866,11 @@ def dataclass_transform( eq_default: bool = True, order_default: bool = False, kw_only_default: bool = False, - field_descriptors: typing.Tuple[ + field_specifiers: typing.Tuple[ typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]], ... ] = (), + **kwargs: typing.Any, ) -> typing.Callable[[T], T]: """Decorator that marks a function, class, or metaclass as providing dataclass-like behavior. @@ -1921,8 +1922,8 @@ class CustomerModel(ModelBase): assumed to be True or False if it is omitted by the caller. - ``kw_only_default`` indicates whether the ``kw_only`` parameter is assumed to be True or False if it is omitted by the caller. - - ``field_descriptors`` specifies a static list of supported classes - or functions, that describe fields, similar to ``dataclasses.field()``. + - ``field_specifiers`` specifies a static list of supported classes + or functions that describe fields, similar to ``dataclasses.field()``. At runtime, this decorator records its arguments in the ``__dataclass_transform__`` attribute on the decorated object. @@ -1935,7 +1936,8 @@ def decorator(cls_or_fn): "eq_default": eq_default, "order_default": order_default, "kw_only_default": kw_only_default, - "field_descriptors": field_descriptors, + "field_specifiers": field_specifiers, + "kwargs": kwargs, } return cls_or_fn return decorator From af2ebf70a671760460f6cbd1f2d953be7c79f9c4 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Apr 2022 10:35:37 -0700 Subject: [PATCH 11/13] Add to the CHANGELOG (#1141) --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 550a1463..569fe50c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Unreleased +- Add `ParamSpecArgs` and `ParamSpecKwargs` to `__all__`. +- Improve "accepts only single type" error messages. +- Improve the distributed package. Patch by Marc Mueller (@cdce8p). - Update `typing_extensions.dataclass_transform` to rename the `field_descriptors` parameter to `field_specifiers` and accept arbitrary keyword arguments. From 48a5fe07702f4a5068b056b580b6725deb5ad13d Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Apr 2022 10:58:25 -0700 Subject: [PATCH 12/13] Fix tests on Python 3.11 (#1139) - Defer to the PEP 646 implementation in typing.py on 3.11 - Adjust some tests accordingly. Noted a bug in https://github.com/python/cpython/pull/32341#issuecomment-1100466389 - typing._type_check() is more lenient in 3.11 and no longer rejects ints - The representation of the empty tuple type changed Tests pass for me on a 3.11 build from today now. --- CHANGELOG | 1 + src/test_typing_extensions.py | 61 ++++++++++------- src/typing_extensions.py | 119 ++++++++++++++++++---------------- 3 files changed, 102 insertions(+), 79 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 569fe50c..8900c588 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # Unreleased +- Re-export `typing.Unpack` and `typing.TypeVarTuple` on Python 3.11. - Add `ParamSpecArgs` and `ParamSpecKwargs` to `__all__`. - Improve "accepts only single type" error messages. - Improve the distributed package. Patch by Marc Mueller (@cdce8p). diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 8e11eb67..7f14f3f9 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -32,6 +32,8 @@ # version of the typing module. TYPING_3_8_0 = sys.version_info[:3] >= (3, 8, 0) TYPING_3_10_0 = sys.version_info[:3] >= (3, 10, 0) + +# 3.11 makes runtime type checks (_type_check) more lenient. TYPING_3_11_0 = sys.version_info[:3] >= (3, 11, 0) @@ -157,8 +159,9 @@ def test_exception(self): class ClassVarTests(BaseTestCase): def test_basics(self): - with self.assertRaises(TypeError): - ClassVar[1] + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + ClassVar[1] with self.assertRaises(TypeError): ClassVar[int, str] with self.assertRaises(TypeError): @@ -201,8 +204,9 @@ def test_no_isinstance(self): class FinalTests(BaseTestCase): def test_basics(self): - with self.assertRaises(TypeError): - Final[1] + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + Final[1] with self.assertRaises(TypeError): Final[int, str] with self.assertRaises(TypeError): @@ -245,8 +249,9 @@ def test_no_isinstance(self): class RequiredTests(BaseTestCase): def test_basics(self): - with self.assertRaises(TypeError): - Required[1] + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + Required[1] with self.assertRaises(TypeError): Required[int, str] with self.assertRaises(TypeError): @@ -289,8 +294,9 @@ def test_no_isinstance(self): class NotRequiredTests(BaseTestCase): def test_basics(self): - with self.assertRaises(TypeError): - NotRequired[1] + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + NotRequired[1] with self.assertRaises(TypeError): NotRequired[int, str] with self.assertRaises(TypeError): @@ -738,7 +744,10 @@ class C(Generic[T]): pass self.assertEqual(get_args(Union[int, Callable[[Tuple[T, ...]], str]]), (int, Callable[[Tuple[T, ...]], str])) self.assertEqual(get_args(Tuple[int, ...]), (int, ...)) - self.assertEqual(get_args(Tuple[()]), ((),)) + if TYPING_3_11_0: + self.assertEqual(get_args(Tuple[()]), ()) + else: + self.assertEqual(get_args(Tuple[()]), ((),)) self.assertEqual(get_args(Annotated[T, 'one', 2, ['three']]), (T, 'one', 2, ['three'])) self.assertEqual(get_args(List), ()) self.assertEqual(get_args(Tuple), ()) @@ -1731,10 +1740,12 @@ def test_typeddict_errors(self): isinstance(jim, Emp) with self.assertRaises(TypeError): issubclass(dict, Emp) - with self.assertRaises(TypeError): - TypedDict('Hi', x=1) - with self.assertRaises(TypeError): - TypedDict('Hi', [('x', int), ('y', 1)]) + + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + TypedDict('Hi', x=1) + with self.assertRaises(TypeError): + TypedDict('Hi', [('x', int), ('y', 1)]) with self.assertRaises(TypeError): TypedDict('Hi', [('x', int)], y=int) @@ -2313,11 +2324,12 @@ def test_invalid_uses(self): ): Concatenate[P, T] - with self.assertRaisesRegex( - TypeError, - 'each arg must be a type', - ): - Concatenate[1, P] + if not TYPING_3_11_0: + with self.assertRaisesRegex( + TypeError, + 'each arg must be a type', + ): + Concatenate[1, P] def test_basic_introspection(self): P = ParamSpec('P') @@ -2497,7 +2509,10 @@ def test_basic_plain(self): def test_repr(self): Ts = TypeVarTuple('Ts') - self.assertEqual(repr(Unpack[Ts]), 'typing_extensions.Unpack[Ts]') + if TYPING_3_11_0: + self.assertEqual(repr(Unpack[Ts]), '*Ts') + else: + self.assertEqual(repr(Unpack[Ts]), 'typing_extensions.Unpack[Ts]') def test_cannot_subclass_vars(self): with self.assertRaises(TypeError): @@ -2572,8 +2587,10 @@ class C(Generic[T1, T2, Unpack[Ts]]): pass self.assertEqual(C[int, str].__args__, (int, str)) self.assertEqual(C[int, str, float].__args__, (int, str, float)) self.assertEqual(C[int, str, float, bool].__args__, (int, str, float, bool)) - with self.assertRaises(TypeError): - C[int] + # TODO This should probably also fail on 3.11, pending changes to CPython. + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + C[int] class TypeVarTupleTests(BaseTestCase): @@ -2617,7 +2634,7 @@ def test_args_and_parameters(self): Ts = TypeVarTuple('Ts') t = Tuple[tuple(Ts)] - self.assertEqual(t.__args__, (Ts.__unpacked__,)) + self.assertEqual(t.__args__, (Unpack[Ts],)) self.assertEqual(t.__parameters__, (Ts,)) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 1e3e1282..dc038819 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -1673,7 +1673,9 @@ class Movie(TypedDict): """) -if sys.version_info[:2] >= (3, 9): +if hasattr(typing, "Unpack"): # 3.11+ + Unpack = typing.Unpack +elif sys.version_info[:2] >= (3, 9): class _UnpackSpecialForm(typing._SpecialForm, _root=True): def __repr__(self): return 'typing_extensions.' + self._name @@ -1729,84 +1731,87 @@ def _is_unpack(obj): return isinstance(obj, _UnpackAlias) -class TypeVarTuple: - """Type variable tuple. +if hasattr(typing, "TypeVarTuple"): # 3.11+ + TypeVarTuple = typing.TypeVarTuple +else: + class TypeVarTuple: + """Type variable tuple. - Usage:: + Usage:: - Ts = TypeVarTuple('Ts') + Ts = TypeVarTuple('Ts') - In the same way that a normal type variable is a stand-in for a single - type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* type such as - ``Tuple[int, str]``. + In the same way that a normal type variable is a stand-in for a single + type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* + type such as ``Tuple[int, str]``. - Type variable tuples can be used in ``Generic`` declarations. - Consider the following example:: + Type variable tuples can be used in ``Generic`` declarations. + Consider the following example:: - class Array(Generic[*Ts]): ... + class Array(Generic[*Ts]): ... - The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``, - where ``T1`` and ``T2`` are type variables. To use these type variables - as type parameters of ``Array``, we must *unpack* the type variable tuple using - the star operator: ``*Ts``. The signature of ``Array`` then behaves - as if we had simply written ``class Array(Generic[T1, T2]): ...``. - In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows - us to parameterise the class with an *arbitrary* number of type parameters. + The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``, + where ``T1`` and ``T2`` are type variables. To use these type variables + as type parameters of ``Array``, we must *unpack* the type variable tuple using + the star operator: ``*Ts``. The signature of ``Array`` then behaves + as if we had simply written ``class Array(Generic[T1, T2]): ...``. + In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows + us to parameterise the class with an *arbitrary* number of type parameters. - Type variable tuples can be used anywhere a normal ``TypeVar`` can. - This includes class definitions, as shown above, as well as function - signatures and variable annotations:: + Type variable tuples can be used anywhere a normal ``TypeVar`` can. + This includes class definitions, as shown above, as well as function + signatures and variable annotations:: - class Array(Generic[*Ts]): + class Array(Generic[*Ts]): - def __init__(self, shape: Tuple[*Ts]): - self._shape: Tuple[*Ts] = shape + def __init__(self, shape: Tuple[*Ts]): + self._shape: Tuple[*Ts] = shape - def get_shape(self) -> Tuple[*Ts]: - return self._shape + def get_shape(self) -> Tuple[*Ts]: + return self._shape - shape = (Height(480), Width(640)) - x: Array[Height, Width] = Array(shape) - y = abs(x) # Inferred type is Array[Height, Width] - z = x + x # ... is Array[Height, Width] - x.get_shape() # ... is tuple[Height, Width] + shape = (Height(480), Width(640)) + x: Array[Height, Width] = Array(shape) + y = abs(x) # Inferred type is Array[Height, Width] + z = x + x # ... is Array[Height, Width] + x.get_shape() # ... is tuple[Height, Width] - """ + """ - # Trick Generic __parameters__. - __class__ = typing.TypeVar + # Trick Generic __parameters__. + __class__ = typing.TypeVar - def __iter__(self): - yield self.__unpacked__ + def __iter__(self): + yield self.__unpacked__ - def __init__(self, name): - self.__name__ = name + def __init__(self, name): + self.__name__ = name - # for pickling: - try: - def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - def_mod = None - if def_mod != 'typing_extensions': - self.__module__ = def_mod + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod - self.__unpacked__ = Unpack[self] + self.__unpacked__ = Unpack[self] - def __repr__(self): - return self.__name__ + def __repr__(self): + return self.__name__ - def __hash__(self): - return object.__hash__(self) + def __hash__(self): + return object.__hash__(self) - def __eq__(self, other): - return self is other + def __eq__(self, other): + return self is other - def __reduce__(self): - return self.__name__ + def __reduce__(self): + return self.__name__ - def __init_subclass__(self, *args, **kwds): - if '_root' not in kwds: - raise TypeError("Cannot subclass special typing classes") + def __init_subclass__(self, *args, **kwds): + if '_root' not in kwds: + raise TypeError("Cannot subclass special typing classes") if hasattr(typing, "reveal_type"): From 7d76a775b0ddd7632e654adc05ab2968727a2807 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 17 Apr 2022 14:21:53 -0700 Subject: [PATCH 13/13] prepare release 4.2.0 (#1144) --- CHANGELOG | 2 +- pyproject.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8900c588..aa66e55c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -# Unreleased +# Release 4.2.0 (April 17, 2022) - Re-export `typing.Unpack` and `typing.TypeVarTuple` on Python 3.11. - Add `ParamSpecArgs` and `ParamSpecKwargs` to `__all__`. diff --git a/pyproject.toml b/pyproject.toml index fbd01801..217b9499 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,8 +6,8 @@ build-backend = "flit_core.buildapi" # Project metadata [project] name = "typing_extensions" -version = "4.1.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" readme = "README.rst" requires-python = ">=3.7" urls.Home = "https://github.com/python/typing/blob/master/typing_extensions/README.rst" @@ -28,7 +28,7 @@ keywords = [ ] # Classifiers list: https://pypi.org/classifiers/ classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: Python Software Foundation License",