From 5cc1d2c840d9a99aeeb442ea6bf71d7ce2dee523 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Thu, 16 Sep 2021 15:52:34 +0200 Subject: [PATCH] Drop Python 2 support for typing_extensions (#893) Update README accordingly --- .flake8 | 1 - .github/workflows/ci.yml | 24 +- typing_extensions/MANIFEST.in | 2 - typing_extensions/README.rst | 54 +-- typing_extensions/setup.py | 16 +- .../src_py2/test_typing_extensions.py | 456 ------------------ .../src_py2/typing_extensions.py | 283 ----------- typing_extensions/tox.ini | 5 +- 8 files changed, 22 insertions(+), 819 deletions(-) delete mode 100644 typing_extensions/src_py2/test_typing_extensions.py delete mode 100644 typing_extensions/src_py2/typing_extensions.py diff --git a/.flake8 b/.flake8 index b61006a00..a40ac7fff 100644 --- a/.flake8 +++ b/.flake8 @@ -14,5 +14,4 @@ ignore = exclude = # tests have more relaxed formatting rules # and its own specific config in .flake8-tests - typing_extensions/src_py2/test_typing_extensions.py, typing_extensions/src_py3/test_typing_extensions.py, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e9d09ce6..7ccde29a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,28 +8,6 @@ permissions: contents: read jobs: - tests-27: - name: Run tests (2.7) - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 2.7 - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r test-requirements.txt - - - name: Test typing_extensions - run: | - pip install typing - pytest typing_extensions/src_py2 - tests: name: Run tests @@ -94,4 +72,4 @@ jobs: run: flake8 - name: Lint tests - run: flake8 --config=.flake8-tests typing_extensions/src_py2/test_typing_extensions.py typing_extensions/src_py3/test_typing_extensions.py + run: flake8 --config=.flake8-tests typing_extensions/src_py3/test_typing_extensions.py diff --git a/typing_extensions/MANIFEST.in b/typing_extensions/MANIFEST.in index feda4cf1e..a727bf4b7 100644 --- a/typing_extensions/MANIFEST.in +++ b/typing_extensions/MANIFEST.in @@ -1,5 +1,3 @@ include LICENSE README.rst include src_py3/typing_extensions.py include src_py3/test_typing_extensions.py -include src_py2/typing_extensions.py -include src_py2/test_typing_extensions.py diff --git a/typing_extensions/README.rst b/typing_extensions/README.rst index 4166510a7..879596e4c 100644 --- a/typing_extensions/README.rst +++ b/typing_extensions/README.rst @@ -9,13 +9,7 @@ Typing Extensions Overview ======== -The ``typing`` module was added to the standard library in Python 3.5 on -a provisional basis and will no longer be provisional in Python 3.7. However, -this means users of Python 3.5 - 3.6 who are unable to upgrade will not be -able to take advantage of new types added to the ``typing`` module, such as -``typing.Text`` or ``typing.Coroutine``. - -The ``typing_extensions`` module contains both backports of these changes +The ``typing_extensions`` module contains both backports of ``typing`` features as well as experimental types that will eventually be added to the ``typing`` module, such as ``Protocol`` (see PEP 544 for details about protocols and static duck typing) or ``TypedDict`` (see PEP 589). @@ -30,11 +24,17 @@ Included items This module currently contains the following: -All Python versions: --------------------- - +- ``Annotated`` +- ``AsyncContextManager`` +- ``AsyncGenerator`` +- ``AsyncIterable`` +- ``AsyncIterator`` +- ``Awaitable`` +- ``ChainMap`` - ``ClassVar`` +- ``Concatenate`` - ``ContextManager`` +- ``Coroutine`` - ``Counter`` - ``DefaultDict`` - ``Deque`` @@ -45,38 +45,17 @@ All Python versions: - ``NoReturn`` - ``overload`` (note that older versions of ``typing`` only let you use ``overload`` in stubs) - ``OrderedDict`` -- ``Protocol`` (except on Python 3.5.0) -- ``runtime_checkable`` (except on Python 3.5.0) +- ``ParamSpec`` +- ``ParamSpecArgs`` +- ``ParamSpecKwargs`` +- ``Protocol`` +- ``runtime_checkable`` - ``Text`` - ``Type`` - ``TypedDict`` - ``TypeAlias`` -- ``TYPE_CHECKING`` - -Python 3.4+ only: ------------------ - -- ``ChainMap`` -- ``ParamSpec`` -- ``Concatenate`` -- ``ParamSpecArgs`` -- ``ParamSpecKwargs`` - ``TypeGuard`` - -Python 3.5+ only: ------------------ - -- ``Annotated`` (except on Python 3.5.0-3.5.2) -- ``AsyncIterable`` -- ``AsyncIterator`` -- ``AsyncContextManager`` -- ``Awaitable`` -- ``Coroutine`` - -Python 3.6+ only: ------------------ - -- ``AsyncGenerator`` +- ``TYPE_CHECKING`` Other Notes and Limitations =========================== @@ -101,4 +80,3 @@ To run tests, navigate into the appropriate source directory and run ``test_typing_extensions.py``. You will also need to install the latest version of ``typing`` if you are using a version of Python that does not include ``typing`` as a part of the standard library. - diff --git a/typing_extensions/setup.py b/typing_extensions/setup.py index 806a8bf92..46a4227c1 100644 --- a/typing_extensions/setup.py +++ b/typing_extensions/setup.py @@ -4,8 +4,8 @@ import sys from setuptools import setup -if sys.version_info < (2, 7, 0) or (3, 0, 0) <= sys.version_info < (3, 6, 0): - sys.stderr.write('ERROR: You need Python 2.7 or 3.6+ ' +if sys.version_info < (3, 6, 0): + sys.stderr.write('ERROR: You need Python 3.6+ ' 'to install typing_extensions.\n') exit(1) @@ -31,7 +31,6 @@ 'Intended Audience :: Developers', 'License :: OSI Approved :: Python Software Foundation License', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', @@ -40,13 +39,6 @@ 'Topic :: Software Development', ] -if sys.version_info.major == 2: - package_dir = 'src_py2' -elif sys.version_info.major == 3: - package_dir = 'src_py3' -else: - raise AssertionError() - setup(name='typing_extensions', version=version, description=description, @@ -57,7 +49,7 @@ license='PSF', keywords='typing function annotations type hints hinting checking ' 'checker typehints typehinting typechecking backport', - package_dir={'': package_dir}, + package_dir={'': 'src_py3'}, py_modules=['typing_extensions'], classifiers=classifiers, - install_requires=["typing >= 3.7.4; python_version < '3.5'"]) + ) diff --git a/typing_extensions/src_py2/test_typing_extensions.py b/typing_extensions/src_py2/test_typing_extensions.py deleted file mode 100644 index 5c21a6df8..000000000 --- a/typing_extensions/src_py2/test_typing_extensions.py +++ /dev/null @@ -1,456 +0,0 @@ -import sys -import os -import contextlib -import collections -import subprocess -from unittest import TestCase, main - -from typing_extensions import Annotated, NoReturn, ClassVar, IntVar -from typing_extensions import ContextManager, Counter, Deque, DefaultDict -from typing_extensions import NewType, TypeAlias, overload -from typing import Dict, List -import typing -import typing_extensions - - -T = typing.TypeVar('T') -KT = typing.TypeVar('KT') -VT = typing.TypeVar('VT') - - -class BaseTestCase(TestCase): - - def assertIsSubclass(self, cls, class_or_tuple, msg=None): - if not issubclass(cls, class_or_tuple): - message = '%r is not a subclass of %r' % (cls, class_or_tuple) - if msg is not None: - message += ' : %s' % msg - raise self.failureException(message) - - def assertNotIsSubclass(self, cls, class_or_tuple, msg=None): - if issubclass(cls, class_or_tuple): - message = '%r is a subclass of %r' % (cls, class_or_tuple) - if msg is not None: - message += ' : %s' % msg - raise self.failureException(message) - - -class Employee(object): - pass - - -class NoReturnTests(BaseTestCase): - - def test_noreturn_instance_type_error(self): - with self.assertRaises(TypeError): - isinstance(42, NoReturn) - - def test_noreturn_subclass_type_error(self): - with self.assertRaises(TypeError): - issubclass(Employee, NoReturn) - with self.assertRaises(TypeError): - issubclass(NoReturn, Employee) - - def test_repr(self): - if hasattr(typing, 'NoReturn'): - self.assertEqual(repr(NoReturn), 'typing.NoReturn') - else: - self.assertEqual(repr(NoReturn), 'typing_extensions.NoReturn') - - def test_not_generic(self): - with self.assertRaises(TypeError): - NoReturn[int] - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class A(NoReturn): - pass - with self.assertRaises(TypeError): - class A(type(NoReturn)): - pass - - def test_cannot_instantiate(self): - with self.assertRaises(TypeError): - NoReturn() - with self.assertRaises(TypeError): - type(NoReturn)() - - -class ClassVarTests(BaseTestCase): - - def test_basics(self): - with self.assertRaises(TypeError): - ClassVar[1] - with self.assertRaises(TypeError): - ClassVar[int, str] - with self.assertRaises(TypeError): - ClassVar[int][str] - - def test_repr(self): - self.assertEqual(repr(ClassVar), 'typing.ClassVar') - cv = ClassVar[int] - self.assertEqual(repr(cv), 'typing.ClassVar[int]') - cv = ClassVar[Employee] - self.assertEqual(repr(cv), 'typing.ClassVar[%s.Employee]' % __name__) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(ClassVar)): - pass - with self.assertRaises(TypeError): - class C(type(ClassVar[int])): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - ClassVar() - with self.assertRaises(TypeError): - type(ClassVar)() - with self.assertRaises(TypeError): - type(ClassVar[typing.Optional[int]])() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, ClassVar[int]) - with self.assertRaises(TypeError): - issubclass(int, ClassVar) - - -class IntVarTests(BaseTestCase): - def test_valid(self): - T_ints = IntVar("T_ints") # noqa - - def test_invalid(self): - with self.assertRaises(TypeError): - T_ints = IntVar("T_ints", int) - with self.assertRaises(TypeError): - T_ints = IntVar("T_ints", bound=int) - with self.assertRaises(TypeError): - T_ints = IntVar("T_ints", covariant=True) # noqa - - -class CollectionsAbcTests(BaseTestCase): - - def test_isinstance_collections(self): - self.assertNotIsInstance(1, collections.Mapping) - self.assertNotIsInstance(1, collections.Iterable) - self.assertNotIsInstance(1, collections.Container) - self.assertNotIsInstance(1, collections.Sized) - with self.assertRaises(TypeError): - isinstance(collections.deque(), typing_extensions.Deque[int]) - with self.assertRaises(TypeError): - issubclass(collections.Counter, typing_extensions.Counter[str]) - - def test_contextmanager(self): - @contextlib.contextmanager - def manager(): - yield 42 - - cm = manager() - self.assertIsInstance(cm, ContextManager) - self.assertNotIsInstance(42, ContextManager) - - with self.assertRaises(TypeError): - isinstance(42, ContextManager[int]) - with self.assertRaises(TypeError): - isinstance(cm, ContextManager[int]) - with self.assertRaises(TypeError): - issubclass(type(cm), ContextManager[int]) - - def test_counter(self): - self.assertIsSubclass(collections.Counter, Counter) - self.assertIs(type(Counter()), collections.Counter) - self.assertIs(type(Counter[T]()), collections.Counter) - self.assertIs(type(Counter[int]()), collections.Counter) - - class A(Counter[int]): pass - class B(Counter[T]): pass - - self.assertIsInstance(A(), collections.Counter) - self.assertIs(type(B[int]()), B) - self.assertEqual(B.__bases__, (typing_extensions.Counter,)) - - def test_deque(self): - self.assertIsSubclass(collections.deque, Deque) - self.assertIs(type(Deque()), collections.deque) - self.assertIs(type(Deque[T]()), collections.deque) - self.assertIs(type(Deque[int]()), collections.deque) - - class A(Deque[int]): pass - class B(Deque[T]): pass - - self.assertIsInstance(A(), collections.deque) - self.assertIs(type(B[int]()), B) - - def test_defaultdict_instantiation(self): - self.assertIsSubclass(collections.defaultdict, DefaultDict) - self.assertIs(type(DefaultDict()), collections.defaultdict) - self.assertIs(type(DefaultDict[KT, VT]()), collections.defaultdict) - self.assertIs(type(DefaultDict[str, int]()), collections.defaultdict) - - class A(DefaultDict[str, int]): pass - class B(DefaultDict[KT, VT]): pass - - self.assertIsInstance(A(), collections.defaultdict) - self.assertIs(type(B[str, int]()), B) - - -class NewTypeTests(BaseTestCase): - - def test_basic(self): - UserId = NewType('UserId', int) - UserName = NewType('UserName', str) - self.assertIsInstance(UserId(5), int) - self.assertIsInstance(UserName('Joe'), type('Joe')) - self.assertEqual(UserId(5) + 1, 6) - - def test_errors(self): - UserId = NewType('UserId', int) - UserName = NewType('UserName', str) - with self.assertRaises(TypeError): - issubclass(UserId, int) - with self.assertRaises(TypeError): - class D(UserName): - pass - - -class OverloadTests(BaseTestCase): - - def test_overload_fails(self): - with self.assertRaises(RuntimeError): - @overload - def blah(): - pass - - blah() - - def test_overload_succeeds(self): - @overload - def blah(): - pass - - def blah(): - pass - - blah() - - -class AnnotatedTests(BaseTestCase): - - def test_repr(self): - self.assertEqual( - repr(Annotated[int, 4, 5]), - "typing_extensions.Annotated[int, 4, 5]" - ) - self.assertEqual( - repr(Annotated[List[int], 4, 5]), - "typing_extensions.Annotated[typing.List[int], 4, 5]" - ) - self.assertEqual(repr(Annotated), "typing_extensions.Annotated") - - def test_flatten(self): - A = Annotated[Annotated[int, 4], 5] - self.assertEqual(A, Annotated[int, 4, 5]) - self.assertEqual(A.__metadata__, (4, 5)) - - 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",)) - with self.assertRaises(TypeError): - LI[int] - with self.assertRaises(TypeError): - L[int, float] - - def test_hash_eq(self): - self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1) - self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4]) - self.assertNotEqual(Annotated[int, 4, 5], Annotated[str, 4, 5]) - self.assertNotEqual(Annotated[int, 4], Annotated[int, 4, 4]) - self.assertEqual( - {Annotated[int, 4, 5], Annotated[int, 4, 5], Annotated[T, 4, 5]}, - {Annotated[int, 4, 5], Annotated[T, 4, 5]} - ) - - def test_instantiate(self): - class C: - classvar = 4 - - def __init__(self, x): - self.x = x - - def __eq__(self, other): - if not isinstance(other, C): - return NotImplemented - return other.x == self.x - - A = Annotated[C, "a decoration"] - a = A(5) - c = C(5) - self.assertEqual(a, c) - self.assertEqual(a.x, c.x) - self.assertEqual(a.classvar, c.classvar) - - def test_instantiate_generic(self): - MyCount = Annotated[typing_extensions.Counter[T], "my decoration"] - self.assertEqual(MyCount([4, 4, 5]), {4: 2, 5: 1}) - self.assertEqual(MyCount[int]([4, 4, 5]), {4: 2, 5: 1}) - - def test_cannot_instantiate_forward(self): - A = Annotated["int", (5, 6)] - with self.assertRaises(TypeError): - A(5) - - def test_cannot_instantiate_type_var(self): - A = Annotated[T, (5, 6)] - with self.assertRaises(TypeError): - A(5) - - def test_cannot_getattr_typevar(self): - with self.assertRaises(AttributeError): - Annotated[T, (5, 7)].x - - def test_attr_passthrough(self): - class C: - classvar = 4 - - A = Annotated[C, "a decoration"] - self.assertEqual(A.classvar, 4) - A.x = 5 - self.assertEqual(C.x, 5) - - def test_hash_eq(self): - self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1) - self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4]) - self.assertNotEqual(Annotated[int, 4, 5], Annotated[str, 4, 5]) - self.assertNotEqual(Annotated[int, 4], Annotated[int, 4, 4]) - self.assertEqual( - {Annotated[int, 4, 5], Annotated[int, 4, 5], Annotated[T, 4, 5]}, - {Annotated[int, 4, 5], Annotated[T, 4, 5]} - ) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(Annotated): - pass - - def test_cannot_check_instance(self): - with self.assertRaises(TypeError): - isinstance(5, Annotated[int, "positive"]) - - def test_cannot_check_subclass(self): - with self.assertRaises(TypeError): - issubclass(int, Annotated[int, "positive"]) - - def test_subst(self): - dec = "a decoration" - dec2 = "another decoration" - - S = Annotated[T, dec2] - self.assertEqual(S[int], Annotated[int, dec2]) - - self.assertEqual(S[Annotated[int, dec]], Annotated[int, dec, dec2]) - L = Annotated[List[T], dec] - - self.assertEqual(L[int], Annotated[List[int], dec]) - with self.assertRaises(TypeError): - L[int, int] - - self.assertEqual(S[L[int]], Annotated[List[int], dec, dec2]) - - D = Annotated[Dict[KT, VT], dec] - self.assertEqual(D[str, int], Annotated[Dict[str, int], dec]) - with self.assertRaises(TypeError): - D[int] - - It = Annotated[int, dec] - with self.assertRaises(TypeError): - It[None] - - LI = L[int] - with self.assertRaises(TypeError): - LI[None] - - def test_annotated_in_other_types(self): - X = List[Annotated[T, 5]] - self.assertEqual(X[int], List[Annotated[int, 5]]) - - -class TypeAliasTests(BaseTestCase): - def test_canonical_usage(self): - Alias = Employee # type: TypeAlias - - def test_cannot_instantiate(self): - with self.assertRaises(TypeError): - TypeAlias() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(42, TypeAlias) - - def test_no_issubclass(self): - with self.assertRaises(TypeError): - issubclass(Employee, TypeAlias) - - with self.assertRaises(TypeError): - issubclass(TypeAlias, Employee) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(TypeAlias): - pass - - with self.assertRaises(TypeError): - class C(type(TypeAlias)): - pass - - def test_repr(self): - if hasattr(typing, 'TypeAlias'): - self.assertEqual(repr(TypeAlias), 'typing.TypeAlias') - self.assertEqual(repr(type(TypeAlias)), 'typing.TypeAlias') - else: - self.assertEqual(repr(TypeAlias), 'typing_extensions.TypeAlias') - self.assertEqual(repr(type(TypeAlias)), 'typing_extensions.TypeAlias') - - def test_cannot_subscript(self): - with self.assertRaises(TypeError): - TypeAlias[int] - - -class AllTests(BaseTestCase): - - def test_typing_extensions_includes_standard(self): - a = typing_extensions.__all__ - self.assertIn('ClassVar', a) - self.assertIn('Type', a) - self.assertIn('Counter', a) - self.assertIn('DefaultDict', a) - self.assertIn('Deque', a) - self.assertIn('NewType', a) - self.assertIn('overload', a) - self.assertIn('Text', a) - self.assertIn('TYPE_CHECKING', a) - - def test_typing_extensions_defers_when_possible(self): - exclude = {'overload', 'Text', 'TYPE_CHECKING', 'Final'} - for item in typing_extensions.__all__: - if item not in exclude and hasattr(typing, item): - self.assertIs( - getattr(typing_extensions, item), - getattr(typing, item)) - - def test_typing_extensions_compiles_with_opt(self): - file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), - 'typing_extensions.py') - try: - subprocess.check_output('{} -OO {}'.format(sys.executable, - file_path), - stderr=subprocess.STDOUT, - shell=True) - except subprocess.CalledProcessError: - self.fail('Module does not compile with optimize=2 (-OO flag).') - - -if __name__ == '__main__': - main() diff --git a/typing_extensions/src_py2/typing_extensions.py b/typing_extensions/src_py2/typing_extensions.py deleted file mode 100644 index 62d1e46c7..000000000 --- a/typing_extensions/src_py2/typing_extensions.py +++ /dev/null @@ -1,283 +0,0 @@ -import abc -import typing -from typing import ( # noqa - # These are imported for re-export. - ClassVar, Type, Generic, Callable, GenericMeta, TypingMeta, - Counter, DefaultDict, Deque, TypeVar, Tuple, Final, final, - NewType, overload, Text, TYPE_CHECKING, Literal, TypedDict, Protocol, - SupportsIndex, - runtime_checkable, - # We use internal typing helpers here, but this significantly reduces - # code duplication. (Also this is only until Protocol is in typing.) - _type_vars, _tp_cache, _type_check, -) - -# Please keep __all__ alphabetized within each category. -__all__ = [ - # Super-special typing primitives. - 'ClassVar', - 'Final', - 'Protocol', - 'Type', - 'TypedDict', - - # Concrete collection types. - 'ContextManager', - 'Counter', - 'Deque', - 'DefaultDict', - - # Structural checks, a.k.a. protocols. - 'SupportsIndex', - - # One-off things. - 'final', - 'IntVar', - 'Literal', - 'NewType', - 'overload', - 'runtime_checkable', - 'Text', - 'TYPE_CHECKING', -] - - -if hasattr(typing, 'NoReturn'): - NoReturn = typing.NoReturn -else: - # TODO: Remove once typing.py has been updated - class _NoReturnMeta(typing.TypingMeta): - """Metaclass for NoReturn.""" - - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - self = super(_NoReturnMeta, cls).__new__(cls, name, bases, namespace) - return self - - class _NoReturn(typing._FinalTypingBase): - """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. - """ - __metaclass__ = _NoReturnMeta - __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) - - -T_co = typing.TypeVar('T_co', covariant=True) - -if hasattr(typing, 'ContextManager'): - ContextManager = typing.ContextManager -else: - # TODO: Remove once typing.py has been updated - class ContextManager(typing.Generic[T_co]): - __slots__ = () - - def __enter__(self): - return self - - @abc.abstractmethod - def __exit__(self, exc_type, exc_value, traceback): - return None - - @classmethod - def __subclasshook__(cls, C): - if cls is ContextManager: - # In Python 3.6+, it is possible to set a method to None to - # explicitly indicate that the class does not implement an ABC - # (https://bugs.python.org/issue25958), but we do not support - # that pattern here because this fallback class is only used - # in Python 3.5 and earlier. - if (any("__enter__" in B.__dict__ for B in C.__mro__) and - any("__exit__" in B.__dict__ for B in C.__mro__)): - return True - return NotImplemented - - -def IntVar(name): - return TypeVar(name) - - -def _is_dunder(name): - """Returns True if name is a __dunder_variable_name__.""" - return len(name) > 4 and name.startswith('__') and name.endswith('__') - - -class AnnotatedMeta(GenericMeta): - """Metaclass for Annotated""" - - def __new__(cls, name, bases, namespace, **kwargs): - if any(b is not object for b in bases): - raise TypeError("Cannot subclass %s" % Annotated) - return super(AnnotatedMeta, cls).__new__(cls, name, bases, namespace, **kwargs) - - @property - def __metadata__(self): - return self._subs_tree()[2] - - 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 '%s[%s, %s]' % (cls, tp_repr, metadata_reprs) - - def _subs_tree(self, tvars=None, args=None): - if self is Annotated: - return Annotated - res = super(AnnotatedMeta, self)._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 - - 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 - - @_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(AnnotatedMeta, self).__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: - 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__, - dict(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(AnnotatedMeta, self).__setattr__(attr, value) - elif self.__origin__ is None: - raise AttributeError(attr) - else: - setattr(self._get_cons(), attr, value) - - -class Annotated(object): - """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[int, Ann1, Ann2], Ann3] == Annotated[int, 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()] - """ - __metaclass__ = AnnotatedMeta - __slots__ = () - - -class _TypeAliasMeta(typing.TypingMeta): - """Metaclass for TypeAlias""" - - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - self = super(_TypeAliasMeta, cls).__new__(cls, name, bases, namespace) - return self - - def __repr__(self): - return 'typing_extensions.TypeAlias' - - -class _TypeAliasBase(typing._FinalTypingBase): - """Special marker indicating that an assignment should - be recognized as a proper type alias definition by type - checkers. - - For example:: - - Predicate = Callable[..., bool] # type: TypeAlias - - It's invalid when used anywhere except as in the example above. - """ - __metaclass__ = _TypeAliasMeta - __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) - -# This alias exists for backwards compatibility. -runtime = runtime_checkable diff --git a/typing_extensions/tox.ini b/typing_extensions/tox.ini index 7bb6156d7..892280f98 100644 --- a/typing_extensions/tox.ini +++ b/typing_extensions/tox.ini @@ -1,9 +1,6 @@ [tox] -envlist = py27, py34, py35, py36, py37, py38, py39 +envlist = py36, py37, py38, py39 [testenv] changedir = src_py3 commands = python -m unittest discover - -[testenv:py27] -changedir = src_py2