diff --git a/CHANGELOG.md b/CHANGELOG.md index ad7e43c9..129c9502 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# Release 4.4.0 (October 6, 2022) + +- Add `typing_extensions.Any` a backport of python 3.11's Any class which is + subclassable at runtime. (backport from python/cpython#31841, by Shantanu + and Jelle Zijlstra). Patch by James Hilton-Balfe (@Gobot1234). +- Add initial support for TypeVarLike `default` parameter, PEP 696. + Patch by Marc Mueller (@cdce8p). +- Runtime support for PEP 698, adding `typing_extensions.override`. Patch by + Jelle Zijlstra. +- Add the `infer_variance` parameter to `TypeVar`, as specified in PEP 695. + Patch by Jelle Zijlstra. + # Release 4.3.0 (July 1, 2022) - Add `typing_extensions.NamedTuple`, allowing for generic `NamedTuple`s on diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a58156bb..a65feb4f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,7 +41,6 @@ backwards-incompatible changes. - Build the source and wheel distributions: - - `cd typing_extensions` - `rm -rf dist/` - `python -m build .` diff --git a/LICENSE b/LICENSE index 583f9f6e..1df6b3b8 100644 --- a/LICENSE +++ b/LICENSE @@ -13,12 +13,11 @@ software. In May 2000, Guido and the Python core development team moved to BeOpen.com to form the BeOpen PythonLabs team. In October of the same -year, the PythonLabs team moved to Digital Creations (now Zope -Corporation, see http://www.zope.com). In 2001, the Python Software -Foundation (PSF, see http://www.python.org/psf/) was formed, a -non-profit organization created specifically to own Python-related -Intellectual Property. Zope Corporation is a sponsoring member of -the PSF. +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. All Python releases are Open Source (see http://www.opensource.org for the Open Source Definition). Historically, most, but not all, Python @@ -74,8 +73,9 @@ analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved" are -retained in Python alone or in any derivative version prepared by Licensee. +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make @@ -180,9 +180,9 @@ version prepared by Licensee. Alternately, in lieu of CNRI's License Agreement, Licensee may substitute the following text (omitting the quotes): "Python 1.6.1 is made available subject to the terms and conditions in CNRI's License Agreement. This Agreement together with -Python 1.6.1 may be located on the Internet using the following +Python 1.6.1 may be located on the internet using the following unique, persistent identifier (known as a handle): 1895.22/1013. This -Agreement may also be obtained from a proxy server on the Internet +Agreement may also be obtained from a proxy server on the internet using the following URL: http://hdl.handle.net/1895.22/1013". 3. In the event Licensee prepares a derivative work that is based on diff --git a/README.md b/README.md index c960e663..bc4a7791 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The `typing_extensions` module serves two related purposes: - Enable use of new type system features on older Python versions. For example, `typing.TypeGuard` is new in Python 3.10, but `typing_extensions` allows - users on Python 3.6 through 3.9 to use it too. + users on previous Python versions to use it too. - Enable experimentation with new type system PEPs before they are accepted and added to the `typing` module. @@ -35,7 +35,9 @@ This module currently contains the following: - Experimental features - - (Currently none) + - `override` (see PEP 698) + - The `default=` argument to `TypeVar`, `ParamSpec`, and `TypeVarTuple` (see PEP 696) + - The `infer_variance=` argument to `TypeVar` (see PEP 695) - In `typing` since Python 3.11 @@ -50,13 +52,13 @@ This module currently contains the following: - `reveal_type` - `Required` (see PEP 655) - `Self` (see PEP 673) - - `TypeVarTuple` (see PEP 646) + - `TypeVarTuple` (see PEP 646; the `typing_extensions` version supports the `default=` argument from PEP 696) - `Unpack` (see PEP 646) - In `typing` since Python 3.10 - `Concatenate` (see PEP 612) - - `ParamSpec` (see PEP 612) + - `ParamSpec` (see PEP 612; the `typing_extensions` version supports the `default=` argument from PEP 696) - `ParamSpecArgs` (see PEP 612) - `ParamSpecKwargs` (see PEP 612) - `TypeAlias` (see PEP 613) @@ -96,7 +98,6 @@ This module currently contains the following: - `Counter` - `DefaultDict` - `Deque` - - `NamedTuple` - `NewType` - `NoReturn` - `overload` @@ -105,6 +106,13 @@ This module currently contains the following: - `TYPE_CHECKING` - `get_type_hints` +- The following have always been present in `typing`, but the `typing_extensions` versions provide + additional features: + + - `Any` (supports inheritance since Python 3.11) + - `NamedTuple` (supports multiple inheritance with `Generic` since Python 3.11) + - `TypeVar` (see PEPs 695 and 696) + # Other Notes and Limitations Certain objects were changed after they were added to `typing`, and @@ -125,6 +133,11 @@ Certain objects were changed after they were added to `typing`, and `@typing_extensions.overload`. - `NamedTuple` was changed in Python 3.11 to allow for multiple inheritance with `typing.Generic`. +- Since Python 3.11, it has been possible to inherit from `Any` at + runtime. `typing_extensions.Any` also provides this capability. +- `TypeVar` gains two additional parameters, `default=` and `infer_variance=`, + in the draft PEPs 695 and 696, which are being considered for inclusion + in Python 3.12. There are a few types whose interface was modified between different versions of typing. For example, `typing.Sequence` was modified to diff --git a/pyproject.toml b/pyproject.toml index 6005d55c..0069a321 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,11 +6,11 @@ build-backend = "flit_core.buildapi" # Project metadata [project] name = "typing_extensions" -version = "4.3.0" +version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" readme = "README.md" requires-python = ">=3.7" -license.file = "LICENSE" +license = { file = "LICENSE" } keywords = [ "annotations", "backport", diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index ee498e56..15b2147b 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -15,13 +15,13 @@ from unittest.mock import patch from test import ann_module, ann_module2, ann_module3 import typing -from typing import TypeVar, Optional, Union, Any, AnyStr +from typing import TypeVar, Optional, Union, AnyStr from typing import T, KT, VT # Not in __all__. from typing import Tuple, List, Dict, Iterable, Iterator, Callable from typing import Generic from typing import no_type_check import typing_extensions -from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, Self +from typing_extensions import NoReturn, Any, 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, final, is_typeddict @@ -29,6 +29,7 @@ from typing_extensions import assert_type, get_type_hints, get_origin, get_args from typing_extensions import clear_overloads, get_overloads, overload from typing_extensions import NamedTuple +from typing_extensions import override from _typed_dict_test_helper import FooGeneric # Flags used to mark tests that only apply after a specific @@ -160,6 +161,63 @@ def test_exception(self): assert_never(None) +class OverrideTests(BaseTestCase): + def test_override(self): + class Base: + def foo(self): ... + + class Derived(Base): + @override + def foo(self): + return 42 + + self.assertIsSubclass(Derived, Base) + self.assertEqual(Derived().foo(), 42) + self.assertEqual(dir(Base.foo), dir(Derived.foo)) + + +class AnyTests(BaseTestCase): + def test_can_subclass(self): + class Mock(Any): pass + self.assertTrue(issubclass(Mock, Any)) + self.assertIsInstance(Mock(), Mock) + + class Something: pass + self.assertFalse(issubclass(Something, Any)) + self.assertNotIsInstance(Something(), Mock) + + class MockSomething(Something, Mock): pass + self.assertTrue(issubclass(MockSomething, Any)) + ms = MockSomething() + self.assertIsInstance(ms, MockSomething) + self.assertIsInstance(ms, Something) + self.assertIsInstance(ms, Mock) + + class SubclassesAny(Any): + ... + + def test_repr(self): + if sys.version_info >= (3, 11): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(Any), f"{mod_name}.Any") + if sys.version_info < (3, 11): # skip for now on 3.11+ see python/cpython#95987 + self.assertEqual(repr(self.SubclassesAny), "") + + def test_instantiation(self): + with self.assertRaises(TypeError): + Any() + + self.SubclassesAny() + + def test_isinstance(self): + with self.assertRaises(TypeError): + isinstance(object(), Any) + + isinstance(object(), self.SubclassesAny) + + class ClassVarTests(BaseTestCase): def test_basics(self): @@ -451,6 +509,23 @@ def blah(): blah() + @patch( + f"{registry_holder.__name__}._overload_registry", + defaultdict(lambda: defaultdict(dict)) + ) + def test_overload_on_compiled_functions(self): + registry = registry_holder._overload_registry + # The registry starts out empty: + self.assertEqual(registry, {}) + + # This should just not fail: + overload(sum) + overload(print) + + # No overloads are recorded: + self.assertEqual(get_overloads(sum), []) + self.assertEqual(get_overloads(print), []) + def set_up_overloads(self): def blah(): pass @@ -488,6 +563,9 @@ def some_other_func(): pass other_overload = some_other_func def some_other_func(): pass self.assertEqual(list(get_overloads(some_other_func)), [other_overload]) + # Unrelated function still has no overloads: + def not_overloaded(): pass + self.assertEqual(list(get_overloads(not_overloaded)), []) # Make sure that after we clear all overloads, the registry is # completely empty. @@ -2401,18 +2479,20 @@ class Z(Generic[P]): pass def test_pickle(self): - global P, P_co, P_contra + global P, P_co, P_contra, P_default P = ParamSpec('P') P_co = ParamSpec('P_co', covariant=True) P_contra = ParamSpec('P_contra', contravariant=True) + P_default = ParamSpec('P_default', default=int) for proto in range(pickle.HIGHEST_PROTOCOL): with self.subTest(f'Pickle protocol {proto}'): - for paramspec in (P, P_co, P_contra): + for paramspec in (P, P_co, P_contra, P_default): z = pickle.loads(pickle.dumps(paramspec, proto)) self.assertEqual(z.__name__, paramspec.__name__) self.assertEqual(z.__covariant__, paramspec.__covariant__) self.assertEqual(z.__contravariant__, paramspec.__contravariant__) self.assertEqual(z.__bound__, paramspec.__bound__) + self.assertEqual(z.__default__, paramspec.__default__) def test_eq(self): P = ParamSpec('P') @@ -2778,6 +2858,17 @@ def test_args_and_parameters(self): self.assertEqual(t.__args__, (Unpack[Ts],)) self.assertEqual(t.__parameters__, (Ts,)) + def test_pickle(self): + global Ts, Ts_default # pickle wants to reference the class by name + Ts = TypeVarTuple('Ts') + Ts_default = TypeVarTuple('Ts_default', default=Unpack[Tuple[int, str]]) + + for proto in range(pickle.HIGHEST_PROTOCOL): + for typevartuple in (Ts, Ts_default): + z = pickle.loads(pickle.dumps(typevartuple, proto)) + self.assertEqual(z.__name__, typevartuple.__name__) + self.assertEqual(z.__default__, typevartuple.__default__) + class FinalDecoratorTests(BaseTestCase): def test_final_unmodified(self): @@ -3005,8 +3096,11 @@ def test_all_names_in___all__(self): def test_typing_extensions_defers_when_possible(self): exclude = { 'overload', + 'ParamSpec', 'Text', 'TypedDict', + 'TypeVar', + 'TypeVarTuple', 'TYPE_CHECKING', 'Final', 'get_type_hints', @@ -3015,7 +3109,7 @@ def test_typing_extensions_defers_when_possible(self): if sys.version_info < (3, 10): exclude |= {'get_args', 'get_origin'} if sys.version_info < (3, 11): - exclude |= {'final', 'NamedTuple'} + exclude |= {'final', 'NamedTuple', 'Any'} for item in typing_extensions.__all__: if item not in exclude and hasattr(typing, item): self.assertIs( @@ -3333,5 +3427,66 @@ def test_same_as_typing_NamedTuple_38_minus(self): ) +class TypeVarLikeDefaultsTests(BaseTestCase): + def test_typevar(self): + T = typing_extensions.TypeVar('T', default=int) + self.assertEqual(T.__default__, int) + + class A(Generic[T]): ... + Alias = Optional[T] + + def test_paramspec(self): + P = ParamSpec('P', default=(str, int)) + self.assertEqual(P.__default__, (str, int)) + + class A(Generic[P]): ... + Alias = typing.Callable[P, None] + + def test_typevartuple(self): + Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]]) + self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]]) + + class A(Generic[Unpack[Ts]]): ... + Alias = Optional[Unpack[Ts]] + + def test_pickle(self): + global U, U_co, U_contra, U_default # pickle wants to reference the class by name + U = typing_extensions.TypeVar('U') + U_co = typing_extensions.TypeVar('U_co', covariant=True) + U_contra = typing_extensions.TypeVar('U_contra', contravariant=True) + U_default = typing_extensions.TypeVar('U_default', default=int) + for proto in range(pickle.HIGHEST_PROTOCOL): + for typevar in (U, U_co, U_contra, U_default): + z = pickle.loads(pickle.dumps(typevar, proto)) + self.assertEqual(z.__name__, typevar.__name__) + self.assertEqual(z.__covariant__, typevar.__covariant__) + self.assertEqual(z.__contravariant__, typevar.__contravariant__) + self.assertEqual(z.__bound__, typevar.__bound__) + self.assertEqual(z.__default__, typevar.__default__) + + +class TypeVarInferVarianceTests(BaseTestCase): + def test_typevar(self): + T = typing_extensions.TypeVar('T') + self.assertFalse(T.__infer_variance__) + T_infer = typing_extensions.TypeVar('T_infer', infer_variance=True) + self.assertTrue(T_infer.__infer_variance__) + T_noinfer = typing_extensions.TypeVar('T_noinfer', infer_variance=False) + self.assertFalse(T_noinfer.__infer_variance__) + + def test_pickle(self): + global U, U_infer # pickle wants to reference the class by name + U = typing_extensions.TypeVar('U') + U_infer = typing_extensions.TypeVar('U_infer', infer_variance=True) + for proto in range(pickle.HIGHEST_PROTOCOL): + for typevar in (U, U_infer): + z = pickle.loads(pickle.dumps(typevar, proto)) + self.assertEqual(z.__name__, typevar.__name__) + self.assertEqual(z.__covariant__, typevar.__covariant__) + self.assertEqual(z.__contravariant__, typevar.__contravariant__) + self.assertEqual(z.__bound__, typevar.__bound__) + self.assertEqual(z.__infer_variance__, typevar.__infer_variance__) + + if __name__ == '__main__': main() diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 31d3564e..ef42417c 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -8,9 +8,9 @@ import typing -# Please keep __all__ alphabetized within each category. __all__ = [ # Super-special typing primitives. + 'Any', 'ClassVar', 'Concatenate', 'Final', @@ -20,6 +20,7 @@ 'ParamSpecKwargs', 'Self', 'Type', + 'TypeVar', 'TypeVarTuple', 'Unpack', @@ -60,6 +61,7 @@ 'Literal', 'NewType', 'overload', + 'override', 'Protocol', 'reveal_type', 'runtime', @@ -149,6 +151,37 @@ def _collect_type_vars(types, typevar_types=None): T_co = typing.TypeVar('T_co', covariant=True) # Any type covariant containers. T_contra = typing.TypeVar('T_contra', contravariant=True) # Ditto contravariant. + +if sys.version_info >= (3, 11): + from typing import Any +else: + + class _AnyMeta(type): + def __instancecheck__(self, obj): + if self is Any: + raise TypeError("typing_extensions.Any cannot be used with isinstance()") + return super().__instancecheck__(obj) + + def __repr__(self): + if self is Any: + return "typing_extensions.Any" + return super().__repr__() + + class Any(metaclass=_AnyMeta): + """Special type indicating an unconstrained type. + - Any is compatible with every type. + - Any assumed to have all methods. + - All values assumed to be instances of Any. + Note that all the above statements are true from the point of view of + static type checkers. At runtime, Any should not be used with instance + checks. + """ + def __new__(cls, *args, **kwargs): + if cls is Any: + raise TypeError("Any cannot be instantiated") + return super().__new__(cls, *args, **kwargs) + + ClassVar = typing.ClassVar # On older versions of typing there is an internal class named "Final". @@ -431,7 +464,7 @@ def _no_init(self, *args, **kwargs): if type(self)._is_protocol: raise TypeError('Protocols cannot be instantiated') - class _ProtocolMeta(abc.ABCMeta): + class _ProtocolMeta(abc.ABCMeta): # noqa: B024 # This metaclass is a bit unfortunate and exists only because of the lack # of __instancehook__. def __instancecheck__(cls, instance): @@ -1115,6 +1148,44 @@ def __repr__(self): above.""") +class _DefaultMixin: + """Mixin for TypeVarLike defaults.""" + + __slots__ = () + + def __init__(self, default): + if isinstance(default, (tuple, list)): + self.__default__ = tuple((typing._type_check(d, "Default must be a type") + for d in default)) + elif default: + self.__default__ = typing._type_check(default, "Default must be a type") + else: + self.__default__ = None + + +# Add default and infer_variance parameters from PEP 696 and 695 +class TypeVar(typing.TypeVar, _DefaultMixin, _root=True): + """Type variable.""" + + __module__ = 'typing' + + def __init__(self, name, *constraints, bound=None, + covariant=False, contravariant=False, + default=None, infer_variance=False): + super().__init__(name, *constraints, bound=bound, covariant=covariant, + contravariant=contravariant) + _DefaultMixin.__init__(self, default) + self.__infer_variance__ = infer_variance + + # 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 + + # Python 3.10+ has PEP 612 if hasattr(typing, 'ParamSpecArgs'): ParamSpecArgs = typing.ParamSpecArgs @@ -1179,12 +1250,32 @@ def __eq__(self, other): # 3.10+ if hasattr(typing, 'ParamSpec'): - ParamSpec = typing.ParamSpec + + # Add default Parameter - PEP 696 + class ParamSpec(typing.ParamSpec, _DefaultMixin, _root=True): + """Parameter specification variable.""" + + __module__ = 'typing' + + def __init__(self, name, *, bound=None, covariant=False, contravariant=False, + default=None): + super().__init__(name, bound=bound, covariant=covariant, + contravariant=contravariant) + _DefaultMixin.__init__(self, default) + + # 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 + # 3.7-3.9 else: # Inherits from list as a workaround for Callable checks in Python < 3.9.2. - class ParamSpec(list): + class ParamSpec(list, _DefaultMixin): """Parameter specification variable. Usage:: @@ -1242,7 +1333,8 @@ def args(self): def kwargs(self): return ParamSpecKwargs(self) - def __init__(self, name, *, bound=None, covariant=False, contravariant=False): + def __init__(self, name, *, bound=None, covariant=False, contravariant=False, + default=None): super().__init__([self]) self.__name__ = name self.__covariant__ = bool(covariant) @@ -1251,6 +1343,7 @@ def __init__(self, name, *, bound=None, covariant=False, contravariant=False): self.__bound__ = typing._type_check(bound, 'Bound must be a type.') else: self.__bound__ = None + _DefaultMixin.__init__(self, default) # for pickling: try: @@ -1752,9 +1845,25 @@ def _is_unpack(obj): if hasattr(typing, "TypeVarTuple"): # 3.11+ - TypeVarTuple = typing.TypeVarTuple + + # Add default Parameter - PEP 696 + class TypeVarTuple(typing.TypeVarTuple, _DefaultMixin, _root=True): + """Type variable tuple.""" + + def __init__(self, name, *, default=None): + super().__init__(name) + _DefaultMixin.__init__(self, default) + + # 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 + else: - class TypeVarTuple: + class TypeVarTuple(_DefaultMixin): """Type variable tuple. Usage:: @@ -1804,8 +1913,9 @@ def get_shape(self) -> Tuple[*Ts]: def __iter__(self): yield self.__unpacked__ - def __init__(self, name): + def __init__(self, name, *, default=None): self.__name__ = name + _DefaultMixin.__init__(self, default) # for pickling: try: @@ -1968,6 +2078,36 @@ def decorator(cls_or_fn): return decorator +if hasattr(typing, "override"): + override = typing.override +else: + _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any]) + + def override(__arg: _F) -> _F: + """Indicate that a method is intended to override a method in a base class. + + Usage: + + class Base: + def method(self) -> None: ... + pass + + class Child(Base): + @override + def method(self) -> None: + super().method() + + When this decorator is applied to a method, the type checker will + validate that it overrides a method with the same name on a base class. + This helps prevent bugs that may occur when a base class is changed + without an equivalent change to a child class. + + See PEP 698 for details. + + """ + return __arg + + # We have to do some monkey patching to deal with the dual nature of # Unpack/TypeVarTuple: # - We want Unpack to be a kind of TypeVar so it gets accepted in diff --git a/test-requirements.txt b/test-requirements.txt index 658ae0a5..05c4c918 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,3 +1,3 @@ flake8 flake8-bugbear -flake8-pyi +flake8-pyi>=22.8.0