diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78610e27..e221022c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,8 +42,6 @@ jobs: # For available versions, see: # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json python-version: - - "3.7" - - "3.7.1" - "3.8" - "3.8.0" - "3.9" @@ -53,7 +51,6 @@ jobs: - "3.11" - "3.11.0" - "3.12" - - "pypy3.7" - "pypy3.8" - "pypy3.9" - "pypy3.10" diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e490c5c..f67da871 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# Release 4.8.0 (???) + +- Drop support for Python 3.7 (including PyPy-3.7). Patch by Alex Waygood. + # Release 4.7.1 (July 2, 2023) - Fix support for `TypedDict`, `NamedTuple` and `is_protocol` on PyPy-3.7 and diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9d07313e..a118a40e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,8 +18,6 @@ standard library, so that users can experiment with them before they are added t standard library. Such features should already be specified in a PEP or merged into CPython's `main` branch. -`typing_extensions` supports Python versions 3.7 and up. - # Versioning scheme Starting with version 4.0.0, `typing_extensions` uses diff --git a/README.md b/README.md index efd3a824..1eddb2a1 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,6 @@ 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. - ## Included items See [the documentation](https://typing-extensions.readthedocs.io/en/latest/#) for a diff --git a/doc/index.rst b/doc/index.rst index 5fd2b2e8..7e68e270 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -135,7 +135,7 @@ Example usage:: Python version support ---------------------- -``typing_extensions`` currently supports Python versions 3.7 and higher. In the future, +``typing_extensions`` currently supports Python versions 3.8 and higher. In the future, support for older Python versions will be dropped some time after that version reaches end of life. diff --git a/pyproject.toml b/pyproject.toml index 736e1e42..6c36b147 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,9 +7,9 @@ build-backend = "flit_core.buildapi" [project] name = "typing_extensions" version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" +description = "Backported and Experimental Type Hints for Python 3.8+" readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.8" license = { file = "LICENSE" } keywords = [ "annotations", @@ -34,7 +34,6 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index c2ab6d7f..78fe0c0f 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -42,7 +42,6 @@ # Flags used to mark tests that only apply after a specific # version of the typing module. -TYPING_3_8_0 = sys.version_info[:3] >= (3, 8, 0) TYPING_3_9_0 = sys.version_info[:3] >= (3, 9, 0) TYPING_3_10_0 = sys.version_info[:3] >= (3, 10, 0) @@ -52,10 +51,6 @@ # 3.12 changes the representation of Unpack[] (PEP 692) TYPING_3_12_0 = sys.version_info[:3] >= (3, 12, 0) -only_with_typing_Protocol = skipUnless( - hasattr(typing, "Protocol"), "Only relevant when typing.Protocol exists" -) - # https://github.com/python/cpython/pull/27017 was backported into some 3.9 and 3.10 # versions, but not all HAS_FORWARD_MODULE = "module" in inspect.signature(typing._type_check).parameters @@ -246,13 +241,7 @@ def some(arg: NoReturn) -> NoReturn: ... def some_str(arg: 'NoReturn') -> 'typing.NoReturn': ... expected = {'arg': NoReturn, 'return': NoReturn} - targets = [some] - - # On 3.7.0 and 3.7.1, https://github.com/python/cpython/pull/10772 - # wasn't applied yet and NoReturn fails _type_check. - if not ((3, 7, 0) <= sys.version_info < (3, 7, 2)): - targets.append(some_str) - for target in targets: + for target in some, some_str: with self.subTest(target=target): self.assertEqual(gth(target), expected) @@ -595,15 +584,11 @@ def test_basics(self): Final[int][str] def test_repr(self): - if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(Final), mod_name + '.Final') + self.assertEqual(repr(Final), 'typing.Final') cv = Final[int] - self.assertEqual(repr(cv), mod_name + '.Final[int]') + self.assertEqual(repr(cv), 'typing.Final[int]') cv = Final[Employee] - self.assertEqual(repr(cv), mod_name + f'.Final[{__name__}.Employee]') + self.assertEqual(repr(cv), f'typing.Final[{__name__}.Employee]') def test_cannot_subclass(self): with self.assertRaises(TypeError): @@ -1771,7 +1756,6 @@ class E(C, BP): pass self.assertNotIsInstance(D(), E) self.assertNotIsInstance(E(), D) - @only_with_typing_Protocol def test_runtimecheckable_on_typing_dot_Protocol(self): @runtime_checkable class Foo(typing.Protocol): @@ -1784,7 +1768,6 @@ def __init__(self): self.assertIsInstance(Bar(), Foo) self.assertNotIsInstance(object(), Foo) - @only_with_typing_Protocol def test_typing_dot_runtimecheckable_on_Protocol(self): @typing.runtime_checkable class Foo(Protocol): @@ -1797,7 +1780,6 @@ def __init__(self): self.assertIsInstance(Bar(), Foo) self.assertNotIsInstance(object(), Foo) - @only_with_typing_Protocol def test_typing_Protocol_and_extensions_Protocol_can_mix(self): class TypingProto(typing.Protocol): x: int @@ -3173,7 +3155,6 @@ def c(self) -> int: return 5 with self.assertRaisesRegex(TypeError, "not a Protocol"): get_protocol_members(ConcreteInherit()) - @only_with_typing_Protocol def test_get_protocol_members_typing(self): with self.assertRaisesRegex(TypeError, "not a Protocol"): get_protocol_members(typing.Protocol) @@ -3222,7 +3203,6 @@ def test_is_protocol(self): # Protocol is not itself a protocol self.assertFalse(is_protocol(Protocol)) - @only_with_typing_Protocol def test_is_protocol_with_typing(self): self.assertFalse(is_protocol(typing.Protocol)) @@ -3681,7 +3661,6 @@ class NewGeneric[T](TypedDict): if hasattr(typing, "TypedDict"): self.assertIs(is_typeddict(typing.TypedDict), False) - @skipUnless(TYPING_3_8_0, "Python 3.8+ required") def test_is_typeddict_against_typeddict_from_typing(self): Point = typing.TypedDict('Point', {'x': int, 'y': int}) @@ -3844,7 +3823,7 @@ class WithImplicitAny(B): def test_non_generic_subscript(self): # For backward compatibility, subscription works # on arbitrary TypedDict types. - # (But we don't attempt to backport this misfeature onto 3.7 and 3.8.) + # (But we don't attempt to backport this misfeature onto 3.8.) class TD(TypedDict): a: T A = TD[int] @@ -4034,17 +4013,8 @@ class C: classvar: Annotated[ClassVar[int], "a decoration"] = 4 const: Annotated[Final[int], "Const"] = 4 - if sys.version_info[:2] >= (3, 7): - self.assertEqual(get_type_hints(C, globals())["classvar"], ClassVar[int]) - self.assertEqual(get_type_hints(C, globals())["const"], Final[int]) - else: - self.assertEqual( - get_type_hints(C, globals())["classvar"], - Annotated[ClassVar[int], "a decoration"] - ) - self.assertEqual( - get_type_hints(C, globals())["const"], Annotated[Final[int], "Const"] - ) + self.assertEqual(get_type_hints(C, globals())["classvar"], ClassVar[int]) + self.assertEqual(get_type_hints(C, globals())["const"], Final[int]) def test_cannot_subclass(self): with self.assertRaisesRegex(TypeError, "Cannot subclass .*Annotated"): @@ -5069,11 +5039,8 @@ def test_typing_extensions_defers_when_possible(self): 'dataclass_transform', 'overload', 'ParamSpec', - 'Text', 'TypeVar', 'TypeVarTuple', - 'TYPE_CHECKING', - 'Final', 'get_type_hints', } if sys.version_info < (3, 10): @@ -5189,13 +5156,6 @@ class NonDefaultAfterDefault(NamedTuple): x: int = 3 y: int - @skipUnless( - ( - TYPING_3_8_0 - or hasattr(CoolEmployeeWithDefault, '_field_defaults') - ), - '"_field_defaults" attribute was added in a micro version of 3.7' - ) def test_field_defaults(self): self.assertEqual(CoolEmployeeWithDefault._field_defaults, dict(cool=0)) @@ -5296,7 +5256,7 @@ class Group(NamedTuple): self.assertEqual(a, (1, [2])) @skipIf(TYPING_3_9_0, "Test isn't relevant to 3.9+") - def test_non_generic_subscript_error_message_py38_minus(self): + def test_non_generic_subscript_error_message_py38(self): class Group(NamedTuple): key: T group: List[T] @@ -5389,10 +5349,7 @@ class CNT(NamedTuple): self.assertEqual(struct._fields, ()) self.assertEqual(struct.__annotations__, {}) self.assertIsInstance(struct(), struct) - # Attribute was added in a micro version of 3.7 - # and is tested more fully elsewhere - if hasattr(struct, "_field_defaults"): - self.assertEqual(struct._field_defaults, {}) + self.assertEqual(struct._field_defaults, {}) def test_namedtuple_errors(self): with self.assertRaises(TypeError): @@ -5429,15 +5386,6 @@ def test_copy_and_pickle(self): def test_docstring(self): self.assertIsInstance(NamedTuple.__doc__, str) - @skipUnless(TYPING_3_8_0, "NamedTuple had a bad signature on <=3.7") - def test_signature_is_same_as_typing_NamedTuple(self): - self.assertEqual(inspect.signature(NamedTuple), inspect.signature(typing.NamedTuple)) - - @skipIf(TYPING_3_8_0, "tests are only relevant to <=3.7") - def test_signature_on_37(self): - self.assertIsInstance(inspect.signature(NamedTuple), inspect.Signature) - self.assertFalse(hasattr(NamedTuple, "__text_signature__")) - @skipUnless(TYPING_3_9_0, "NamedTuple was a class on 3.8 and lower") def test_same_as_typing_NamedTuple_39_plus(self): self.assertEqual( @@ -5592,7 +5540,7 @@ def test_bound_errors(self): r"Bound must be a type\. Got \(1, 2\)\."): TypeVar('X', bound=(1, 2)) - # Technically we could run it on later versions of 3.7 and 3.8, + # Technically we could run it on later versions of 3.8, # but that's not worth the effort. @skipUnless(TYPING_3_9_0, "Fix was not backported") def test_missing__name__(self): diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 901f3b96..0978c5f3 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -248,32 +248,7 @@ def __repr__(self): return 'typing_extensions.' + self._name -# On older versions of typing there is an internal class named "Final". -# 3.8+ -if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): - Final = typing.Final -# 3.7 -else: - class _FinalForm(_ExtensionsSpecialForm, _root=True): - def __getitem__(self, parameters): - item = typing._type_check(parameters, - f'{self._name} accepts only a single type.') - return typing._GenericAlias(self, (item,)) - - Final = _FinalForm('Final', - doc="""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.""") +Final = typing.Final if sys.version_info >= (3, 11): final = typing.final @@ -465,8 +440,6 @@ def clear_overloads(): # Various ABCs mimicking those in collections.abc. # A few are simply re-exported for completeness. - - Awaitable = typing.Awaitable Coroutine = typing.Coroutine AsyncIterable = typing.AsyncIterable @@ -475,14 +448,7 @@ def clear_overloads(): ContextManager = typing.ContextManager AsyncContextManager = typing.AsyncContextManager DefaultDict = typing.DefaultDict - -# 3.7.2+ -if hasattr(typing, 'OrderedDict'): - OrderedDict = typing.OrderedDict -# 3.7.0-3.7.2 -else: - OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) - +OrderedDict = typing.OrderedDict Counter = typing.Counter ChainMap = typing.ChainMap AsyncGenerator = typing.AsyncGenerator @@ -508,12 +474,6 @@ def clear_overloads(): "__protocol_attrs__", "__callable_proto_members_only__", } -if sys.version_info < (3, 8): - _EXCLUDED_ATTRS |= { - "_gorg", "__next_in_mro__", "__extra__", "__tree_hash__", "__args__", - "__origin__" - } - if sys.version_info >= (3, 9): _EXCLUDED_ATTRS.add("__class_getitem__") @@ -535,46 +495,6 @@ def _get_protocol_attrs(cls): return attrs -def _maybe_adjust_parameters(cls): - """Helper function used in Protocol.__init_subclass__ and _TypedDictMeta.__new__. - - The contents of this function are very similar - to logic found in typing.Generic.__init_subclass__ - on the CPython main branch. - """ - tvars = [] - if '__orig_bases__' in cls.__dict__: - tvars = _collect_type_vars(cls.__orig_bases__) - # Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn]. - # If found, tvars must be a subset of it. - # If not found, tvars is it. - # Also check for and reject plain Generic, - # and reject multiple Generic[...] and/or Protocol[...]. - gvars = None - for base in cls.__orig_bases__: - if (isinstance(base, typing._GenericAlias) and - base.__origin__ in (typing.Generic, Protocol)): - # for error messages - the_base = base.__origin__.__name__ - if gvars is not None: - raise TypeError( - "Cannot inherit from Generic[...]" - " and/or Protocol[...] multiple types.") - 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) - raise TypeError(f"Some type variables ({s_vars}) are" - f" not listed in {the_base}[{s_args}]") - tvars = gvars - cls.__parameters__ = tuple(tvars) - - def _caller(depth=2): try: return sys._getframe(depth).f_globals.get('__name__', '__main__') @@ -598,17 +518,10 @@ def _no_init(self, *args, **kwargs): if type(self)._is_protocol: raise TypeError('Protocols cannot be instantiated') - if sys.version_info >= (3, 8): - # Inheriting from typing._ProtocolMeta isn't actually desirable, - # but is necessary to allow typing.Protocol and typing_extensions.Protocol - # to mix without getting TypeErrors about "metaclass conflict" - _typing_Protocol = typing.Protocol - _ProtocolMetaBase = type(_typing_Protocol) - else: - _typing_Protocol = _marker - _ProtocolMetaBase = abc.ABCMeta - - class _ProtocolMeta(_ProtocolMetaBase): + # Inheriting from typing._ProtocolMeta isn't actually desirable, + # but is necessary to allow typing.Protocol and typing_extensions.Protocol + # to mix without getting TypeErrors about "metaclass conflict" + class _ProtocolMeta(type(typing.Protocol)): # This metaclass is somewhat unfortunate, # but is necessary for several reasons... # @@ -618,10 +531,10 @@ class _ProtocolMeta(_ProtocolMetaBase): def __new__(mcls, name, bases, namespace, **kwargs): if name == "Protocol" and len(bases) < 2: pass - elif {Protocol, _typing_Protocol} & set(bases): + elif {Protocol, typing.Protocol} & set(bases): for base in bases: if not ( - base in {object, typing.Generic, Protocol, _typing_Protocol} + base in {object, typing.Generic, Protocol, typing.Protocol} or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, []) or is_protocol(base) ): @@ -699,12 +612,10 @@ def __instancecheck__(cls, instance): def __eq__(cls, other): # Hack so that typing.Generic.__class_getitem__ # treats typing_extensions.Protocol - # as equivalent to typing.Protocol on Python 3.8+ + # as equivalent to typing.Protocol if abc.ABCMeta.__eq__(cls, other) is True: return True - return ( - cls is Protocol and other is getattr(typing, "Protocol", object()) - ) + return cls is Protocol and other is typing.Protocol # This has to be defined, or the abc-module cache # complains about classes with this metaclass being unhashable, @@ -737,146 +648,33 @@ def _proto_hook(cls, other): return NotImplemented return True - if sys.version_info >= (3, 8): - class Protocol(typing.Generic, metaclass=_ProtocolMeta): - __doc__ = typing.Protocol.__doc__ - __slots__ = () - _is_protocol = True - _is_runtime_protocol = False - - def __init_subclass__(cls, *args, **kwargs): - super().__init_subclass__(*args, **kwargs) - - # Determine if this is a protocol or a concrete subclass. - if not cls.__dict__.get('_is_protocol', False): - cls._is_protocol = any(b is Protocol for b in cls.__bases__) - - # Set (or override) the protocol subclass hook. - if '__subclasshook__' not in cls.__dict__: - cls.__subclasshook__ = _proto_hook - - # Prohibit instantiation for protocol classes - if cls._is_protocol and cls.__init__ is Protocol.__init__: - cls.__init__ = _no_init - - else: - class Protocol(metaclass=_ProtocolMeta): - # There is quite a lot of overlapping code with typing.Generic. - # Unfortunately it is hard to avoid this on Python <3.8, - # as the typing module on Python 3.7 doesn't let us subclass typing.Generic! - """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_checkable act - as simple-minded runtime-checkable protocols that check - 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 - _is_runtime_protocol = False - - def __new__(cls, *args, **kwds): - if cls is Protocol: - raise TypeError("Type Protocol cannot be instantiated; " - "it can only be used as a base class") - return super().__new__(cls) - - @typing._tp_cache - def __class_getitem__(cls, params): - if not isinstance(params, tuple): - params = (params,) - if not params and cls is not typing.Tuple: - raise TypeError( - f"Parameter list to {cls.__qualname__}[...] cannot be empty") - msg = "Parameters to generic types must be types." - params = tuple(typing._type_check(p, msg) for p in params) - if cls is Protocol: - # Generic can only be subscripted with unique type variables. - if not all(isinstance(p, typing.TypeVar) for p in params): - i = 0 - while isinstance(params[i], typing.TypeVar): - i += 1 - raise TypeError( - "Parameters to Protocol[...] must all be type variables." - f" Parameter {i + 1} is {params[i]}") - if len(set(params)) != len(params): - raise TypeError( - "Parameters to Protocol[...] must all be unique") - else: - # Subscripting a regular Generic subclass. - _check_generic(cls, params, len(cls.__parameters__)) - return typing._GenericAlias(cls, params) - - def __init_subclass__(cls, *args, **kwargs): - if '__orig_bases__' in cls.__dict__: - error = typing.Generic in cls.__orig_bases__ - else: - error = typing.Generic in cls.__bases__ - if error: - raise TypeError("Cannot inherit from plain Generic") - _maybe_adjust_parameters(cls) - - # Determine if this is a protocol or a concrete subclass. - if not cls.__dict__.get('_is_protocol', None): - cls._is_protocol = any(b is Protocol for b in cls.__bases__) - - # Set (or override) the protocol subclass hook. - if '__subclasshook__' not in cls.__dict__: - cls.__subclasshook__ = _proto_hook + class Protocol(typing.Generic, metaclass=_ProtocolMeta): + __doc__ = typing.Protocol.__doc__ + __slots__ = () + _is_protocol = True + _is_runtime_protocol = False - # Prohibit instantiation for protocol classes - if cls._is_protocol and cls.__init__ is Protocol.__init__: - cls.__init__ = _no_init + def __init_subclass__(cls, *args, **kwargs): + super().__init_subclass__(*args, **kwargs) + # Determine if this is a protocol or a concrete subclass. + if not cls.__dict__.get('_is_protocol', False): + cls._is_protocol = any(b is Protocol for b in cls.__bases__) -if sys.version_info >= (3, 8): - runtime_checkable = typing.runtime_checkable -else: - def runtime_checkable(cls): - """Mark a protocol class as a runtime protocol, so that it - can be used with isinstance() and issubclass(). Raise TypeError - if applied to a non-protocol class. + # Set (or override) the protocol subclass hook. + if '__subclasshook__' not in cls.__dict__: + cls.__subclasshook__ = _proto_hook - This allows a simple-minded structural check very similar to the - one-offs in collections.abc such as Hashable. - """ - if not ( - (isinstance(cls, _ProtocolMeta) or issubclass(cls, typing.Generic)) - and getattr(cls, "_is_protocol", False) - ): - raise TypeError('@runtime_checkable can be only applied to protocol classes,' - f' got {cls!r}') - cls._is_runtime_protocol = True - return cls + # Prohibit instantiation for protocol classes + if cls._is_protocol and cls.__init__ is Protocol.__init__: + cls.__init__ = _no_init -# Exists for backwards compatibility. -runtime = runtime_checkable +# The "runtime" alias exists for backwards compatibility. +runtime = runtime_checkable = typing.runtime_checkable -# Our version of runtime-checkable protocols is faster on Python 3.7-3.11 +# Our version of runtime-checkable protocols is faster on Python 3.8-3.11 if sys.version_info >= (3, 12): SupportsInt = typing.SupportsInt SupportsFloat = typing.SupportsFloat @@ -986,11 +784,6 @@ def inner(func): # 3.10.0 and later _TAKES_MODULE = "module" in inspect.signature(typing._type_check).parameters - if sys.version_info >= (3, 8): - _fake_name = "Protocol" - else: - _fake_name = "_Protocol" - class _TypedDictMeta(type): def __new__(cls, name, bases, ns, total=True): """Create new typed dict class object. @@ -1011,10 +804,10 @@ def __new__(cls, name, bases, ns, total=True): generic_base = () # typing.py generally doesn't let you inherit from plain Generic, unless - # the name of the class happens to be "Protocol" (or "_Protocol" on 3.7). - tp_dict = type.__new__(_TypedDictMeta, _fake_name, (*generic_base, dict), ns) + # the name of the class happens to be "Protocol" + tp_dict = type.__new__(_TypedDictMeta, "Protocol", (*generic_base, dict), ns) tp_dict.__name__ = name - if tp_dict.__qualname__ == _fake_name: + if tp_dict.__qualname__ == "Protocol": tp_dict.__qualname__ = name if not hasattr(tp_dict, '__orig_bases__'): @@ -1077,7 +870,7 @@ def __subclasscheck__(cls, other): _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {}) @_ensure_subclassable(lambda bases: (_TypedDict,)) - def TypedDict(__typename, __fields=_marker, *, total=True, **kwargs): + def TypedDict(typename, fields=_marker, /, *, total=True, **kwargs): """A simple typed namespace. At runtime it is equivalent to a plain dict. TypedDict creates a dictionary type such that a type checker will expect all @@ -1124,20 +917,20 @@ class Point2D(TypedDict): See PEP 655 for more details on Required and NotRequired. """ - if __fields is _marker or __fields is None: - if __fields is _marker: + if fields is _marker or fields is None: + if fields is _marker: deprecated_thing = "Failing to pass a value for the 'fields' parameter" else: deprecated_thing = "Passing `None` as the 'fields' parameter" - example = f"`{__typename} = TypedDict({__typename!r}, {{}})`" + example = f"`{typename} = TypedDict({typename!r}, {{}})`" deprecation_msg = ( f"{deprecated_thing} is deprecated and will be disallowed in " "Python 3.15. To create a TypedDict class with 0 fields " "using the functional syntax, pass an empty dictionary, e.g. " ) + example + "." warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2) - __fields = kwargs + fields = kwargs elif kwargs: raise TypeError("TypedDict takes either a dict or keyword arguments," " but not both") @@ -1150,13 +943,13 @@ class Point2D(TypedDict): stacklevel=2, ) - ns = {'__annotations__': dict(__fields)} + ns = {'__annotations__': dict(fields)} module = _caller() if module is not None: # Setting correct module is necessary to make typed dict classes pickleable. ns['__module__'] = module - td = _TypedDictMeta(__typename, (), ns, total=total) + td = _TypedDictMeta(typename, (), ns, total=total) td.__orig_bases__ = (TypedDict,) return td @@ -1186,7 +979,7 @@ class Film(TypedDict): assert_type = typing.assert_type else: - def assert_type(__val, __typ): + 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 @@ -1199,12 +992,12 @@ def greet(name: str) -> None: At runtime this returns the first argument unchanged and otherwise does nothing. """ - return __val + return val -if hasattr(typing, "Required"): +if hasattr(typing, "Required"): # 3.11+ get_type_hints = typing.get_type_hints -else: +else: # <=3.10 # replaces _strip_annotations() def _strip_extras(t): """Strips Annotated, Required and NotRequired from a given type.""" @@ -1262,11 +1055,11 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): - If two dict arguments are passed, they specify globals and locals, respectively. """ - if hasattr(typing, "Annotated"): + if hasattr(typing, "Annotated"): # 3.9+ hint = typing.get_type_hints( obj, globalns=globalns, localns=localns, include_extras=True ) - else: + else: # 3.8 hint = typing.get_type_hints(obj, globalns=globalns, localns=localns) if include_extras: return hint @@ -1279,7 +1072,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): # Not exported and not a public API, but needed for get_origin() and get_args() # to work. _AnnotatedAlias = typing._AnnotatedAlias -# 3.7-3.8 +# 3.8 else: class _AnnotatedAlias(typing._GenericAlias, _root=True): """Runtime representation of an annotated type. @@ -1384,7 +1177,7 @@ def __init_subclass__(cls, *args, **kwargs): if sys.version_info[:2] >= (3, 10): get_origin = typing.get_origin get_args = typing.get_args -# 3.7-3.9 +# 3.8-3.9 else: try: # 3.9+ @@ -1462,7 +1255,7 @@ def TypeAlias(self, parameters): It's invalid when used anywhere except as in the example above. """ raise TypeError(f"{self} is not subscriptable") -# 3.7-3.8 +# 3.8 else: TypeAlias = _ExtensionsSpecialForm( 'TypeAlias', @@ -1519,7 +1312,7 @@ def __new__(cls, name, *constraints, bound=None, covariant=False, contravariant=False, default=_marker, infer_variance=False): if hasattr(typing, "TypeAliasType"): - # PEP 695 implemented, can pass infer_variance to typing.TypeVar + # PEP 695 implemented (3.12+), can pass infer_variance to typing.TypeVar typevar = typing.TypeVar(name, *constraints, bound=bound, covariant=covariant, contravariant=contravariant, infer_variance=infer_variance) @@ -1541,7 +1334,7 @@ def __init_subclass__(cls) -> None: if hasattr(typing, 'ParamSpecArgs'): ParamSpecArgs = typing.ParamSpecArgs ParamSpecKwargs = typing.ParamSpecKwargs -# 3.7-3.9 +# 3.8-3.9 else: class _Immutable: """Mixin to indicate that object should not be copied.""" @@ -1630,7 +1423,7 @@ def __new__(cls, name, *, bound=None, def __init_subclass__(cls) -> None: raise TypeError(f"type '{__name__}.ParamSpec' is not an acceptable base type") -# 3.7-3.9 +# 3.8-3.9 else: # Inherits from list as a workaround for Callable checks in Python < 3.9.2. @@ -1735,7 +1528,7 @@ def __call__(self, *args, **kwargs): pass -# 3.7-3.9 +# 3.8-3.9 if not hasattr(typing, 'Concatenate'): # Inherits from list as a workaround for Callable checks in Python < 3.9.2. class _ConcatenateGenericAlias(list): @@ -1770,7 +1563,7 @@ def __parameters__(self): ) -# 3.7-3.9 +# 3.8-3.9 @typing._tp_cache def _concatenate_getitem(self, parameters): if parameters == (): @@ -1804,7 +1597,7 @@ def Concatenate(self, parameters): See PEP 612 for detailed information. """ return _concatenate_getitem(self, parameters) -# 3.7-8 +# 3.8 else: class _ConcatenateForm(_ExtensionsSpecialForm, _root=True): def __getitem__(self, parameters): @@ -1874,7 +1667,7 @@ def is_str(val: Union[str, float]): """ item = typing._type_check(parameters, f'{self} accepts only a single type.') return typing._GenericAlias(self, (item,)) -# 3.7-3.8 +# 3.8 else: class _TypeGuardForm(_ExtensionsSpecialForm, _root=True): def __getitem__(self, parameters): @@ -1972,7 +1765,7 @@ def __getitem__(self, parameters): return self._getitem(self, parameters) -if hasattr(typing, "LiteralString"): +if hasattr(typing, "LiteralString"): # 3.11+ LiteralString = typing.LiteralString else: @_SpecialForm @@ -1995,7 +1788,7 @@ def query(sql: LiteralString) -> ...: raise TypeError(f"{self} is not subscriptable") -if hasattr(typing, "Self"): +if hasattr(typing, "Self"): # 3.11+ Self = typing.Self else: @_SpecialForm @@ -2016,7 +1809,7 @@ def parse(self, data: bytes) -> Self: raise TypeError(f"{self} is not subscriptable") -if hasattr(typing, "Never"): +if hasattr(typing, "Never"): # 3.11+ Never = typing.Never else: @_SpecialForm @@ -2046,10 +1839,10 @@ def int_or_str(arg: int | str) -> None: raise TypeError(f"{self} is not subscriptable") -if hasattr(typing, 'Required'): +if hasattr(typing, 'Required'): # 3.11+ Required = typing.Required NotRequired = typing.NotRequired -elif sys.version_info[:2] >= (3, 9): +elif sys.version_info[:2] >= (3, 9): # 3.9-3.10 @_ExtensionsSpecialForm def Required(self, parameters): """A special typing construct to mark a key of a total=False TypedDict @@ -2087,7 +1880,7 @@ class Movie(TypedDict): item = typing._type_check(parameters, f'{self._name} accepts only a single type.') return typing._GenericAlias(self, (item,)) -else: +else: # 3.8 class _RequiredForm(_ExtensionsSpecialForm, _root=True): def __getitem__(self, parameters): item = typing._type_check(parameters, @@ -2175,7 +1968,7 @@ def foo(**kwargs: Unpack[Movie]): ... def _is_unpack(obj): return get_origin(obj) is Unpack -elif sys.version_info[:2] >= (3, 9): +elif sys.version_info[:2] >= (3, 9): # 3.9+ class _UnpackSpecialForm(_ExtensionsSpecialForm, _root=True): def __init__(self, getitem): super().__init__(getitem) @@ -2192,7 +1985,7 @@ def Unpack(self, parameters): def _is_unpack(obj): return isinstance(obj, _UnpackAlias) -else: +else: # 3.8 class _UnpackAlias(typing._GenericAlias, _root=True): __class__ = typing.TypeVar @@ -2225,7 +2018,7 @@ def __new__(cls, name, *, default=_marker): def __init_subclass__(self, *args, **kwds): raise TypeError("Cannot subclass special typing classes") -else: +else: # <=3.10 class TypeVarTuple(_DefaultMixin): """Type variable tuple. @@ -2304,10 +2097,10 @@ def __init_subclass__(self, *args, **kwds): raise TypeError("Cannot subclass special typing classes") -if hasattr(typing, "reveal_type"): +if hasattr(typing, "reveal_type"): # 3.11+ reveal_type = typing.reveal_type -else: - def reveal_type(__obj: T) -> T: +else: # <=3.10 + def reveal_type(obj: T, /) -> T: """Reveal the inferred type of a variable. When a static type checker encounters a call to ``reveal_type()``, @@ -2323,14 +2116,14 @@ def reveal_type(__obj: T) -> T: argument and returns it unchanged. """ - print(f"Runtime type is {type(__obj).__name__!r}", file=sys.stderr) - return __obj + print(f"Runtime type is {type(obj).__name__!r}", file=sys.stderr) + return obj -if hasattr(typing, "assert_never"): +if hasattr(typing, "assert_never"): # 3.11+ assert_never = typing.assert_never -else: - def assert_never(__arg: Never) -> Never: +else: # <=3.10 + def assert_never(arg: Never, /) -> Never: """Assert to the type checker that a line of code is unreachable. Example:: @@ -2353,10 +2146,10 @@ def int_or_str(arg: int | str) -> None: raise AssertionError("Expected code to be unreachable") -if sys.version_info >= (3, 12): +if sys.version_info >= (3, 12): # 3.12+ # dataclass_transform exists in 3.11 but lacks the frozen_default parameter dataclass_transform = typing.dataclass_transform -else: +else: # <=3.11 def dataclass_transform( *, eq_default: bool = True, @@ -2443,12 +2236,12 @@ def decorator(cls_or_fn): return decorator -if hasattr(typing, "override"): +if hasattr(typing, "override"): # 3.12+ override = typing.override -else: +else: # <=3.11 _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any]) - def override(__arg: _F) -> _F: + def override(arg: _F, /) -> _F: """Indicate that a method is intended to override a method in a base class. Usage: @@ -2475,13 +2268,13 @@ def method(self) -> None: """ try: - __arg.__override__ = True + arg.__override__ = True except (AttributeError, TypeError): # Skip the attribute silently if it is not writable. # AttributeError happens if the object has __slots__ or a # read-only property, TypeError if it's a builtin class. pass - return __arg + return arg if hasattr(typing, "deprecated"): @@ -2490,7 +2283,8 @@ def method(self) -> None: _T = typing.TypeVar("_T") def deprecated( - __msg: str, + msg: str, + /, *, category: typing.Optional[typing.Type[Warning]] = DeprecationWarning, stacklevel: int = 1, @@ -2533,17 +2327,17 @@ def g(x: str) -> int: ... See PEP 702 for details. """ - def decorator(__arg: _T) -> _T: + def decorator(arg: _T, /) -> _T: if category is None: - __arg.__deprecated__ = __msg - return __arg - elif isinstance(__arg, type): - original_new = __arg.__new__ - has_init = __arg.__init__ is not object.__init__ + arg.__deprecated__ = msg + return arg + elif isinstance(arg, type): + original_new = arg.__new__ + has_init = arg.__init__ is not object.__init__ @functools.wraps(original_new) def __new__(cls, *args, **kwargs): - warnings.warn(__msg, category=category, stacklevel=stacklevel + 1) + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) if original_new is not object.__new__: return original_new(cls, *args, **kwargs) # Mirrors a similar check in object.__new__. @@ -2552,21 +2346,21 @@ def __new__(cls, *args, **kwargs): else: return original_new(cls) - __arg.__new__ = staticmethod(__new__) - __arg.__deprecated__ = __new__.__deprecated__ = __msg - return __arg - elif callable(__arg): - @functools.wraps(__arg) + arg.__new__ = staticmethod(__new__) + arg.__deprecated__ = __new__.__deprecated__ = msg + return arg + elif callable(arg): + @functools.wraps(arg) def wrapper(*args, **kwargs): - warnings.warn(__msg, category=category, stacklevel=stacklevel + 1) - return __arg(*args, **kwargs) + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + return arg(*args, **kwargs) - __arg.__deprecated__ = wrapper.__deprecated__ = __msg + arg.__deprecated__ = wrapper.__deprecated__ = msg return wrapper else: raise TypeError( "@deprecated decorator with non-None category must be applied to " - f"a class or callable, not {__arg!r}" + f"a class or callable, not {arg!r}" ) return decorator @@ -2584,7 +2378,7 @@ def wrapper(*args, **kwargs): typing._check_generic = _check_generic -# Backport typing.NamedTuple as it exists in Python 3.12. +# Backport typing.NamedTuple as it exists in Python 3.13. # In 3.11, the ability to define generic `NamedTuple`s was supported. # This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8. # On 3.12, we added __orig_bases__ to call-based NamedTuples @@ -2655,7 +2449,7 @@ def _namedtuple_mro_entries(bases): return (_NamedTuple,) @_ensure_subclassable(_namedtuple_mro_entries) - def NamedTuple(__typename, __fields=_marker, **kwargs): + def NamedTuple(typename, fields=_marker, /, **kwargs): """Typed version of namedtuple. Usage:: @@ -2675,7 +2469,7 @@ class Employee(NamedTuple): Employee = NamedTuple('Employee', [('name', str), ('id', int)]) """ - if __fields is _marker: + if fields is _marker: if kwargs: deprecated_thing = "Creating NamedTuple classes using keyword arguments" deprecation_msg = ( @@ -2684,14 +2478,14 @@ class Employee(NamedTuple): ) else: deprecated_thing = "Failing to pass a value for the 'fields' parameter" - example = f"`{__typename} = NamedTuple({__typename!r}, [])`" + example = f"`{typename} = NamedTuple({typename!r}, [])`" deprecation_msg = ( "{name} is deprecated and will be disallowed in Python {remove}. " "To create a NamedTuple class with 0 fields " "using the functional syntax, " "pass an empty list, e.g. " ) + example + "." - elif __fields is None: + elif fields is None: if kwargs: raise TypeError( "Cannot pass `None` as the 'fields' parameter " @@ -2699,7 +2493,7 @@ class Employee(NamedTuple): ) else: deprecated_thing = "Passing `None` as the 'fields' parameter" - example = f"`{__typename} = NamedTuple({__typename!r}, [])`" + example = f"`{typename} = NamedTuple({typename!r}, [])`" deprecation_msg = ( "{name} is deprecated and will be disallowed in Python {remove}. " "To create a NamedTuple class with 0 fields " @@ -2709,27 +2503,17 @@ class Employee(NamedTuple): elif kwargs: raise TypeError("Either list of fields or keywords" " can be provided to NamedTuple, not both") - if __fields is _marker or __fields is None: + if fields is _marker or fields is None: warnings.warn( deprecation_msg.format(name=deprecated_thing, remove="3.15"), DeprecationWarning, stacklevel=2, ) - __fields = kwargs.items() - nt = _make_nmtuple(__typename, __fields, module=_caller()) + fields = kwargs.items() + nt = _make_nmtuple(typename, fields, module=_caller()) nt.__orig_bases__ = (NamedTuple,) return nt - # On 3.8+, alter the signature so that it matches typing.NamedTuple. - # The signature of typing.NamedTuple on >=3.8 is invalid syntax in Python 3.7, - # so just leave the signature as it is on 3.7. - if sys.version_info >= (3, 8): - _new_signature = '(typename, fields=None, /, **kwargs)' - if isinstance(NamedTuple, _types.FunctionType): - NamedTuple.__text_signature__ = _new_signature - else: - NamedTuple.__call__.__text_signature__ = _new_signature - if hasattr(collections.abc, "Buffer"): Buffer = collections.abc.Buffer @@ -2764,7 +2548,7 @@ class Buffer(abc.ABC): if hasattr(_types, "get_original_bases"): get_original_bases = _types.get_original_bases else: - def get_original_bases(__cls): + def get_original_bases(cls, /): """Return the class's "original" bases prior to modification by `__mro_entries__`. Examples:: @@ -2786,13 +2570,13 @@ class Baz(list[str]): ... assert get_original_bases(int) == (object,) """ try: - return __cls.__orig_bases__ + return cls.__orig_bases__ except AttributeError: try: - return __cls.__bases__ + return cls.__bases__ except AttributeError: raise TypeError( - f'Expected an instance of type, not {type(__cls).__name__!r}' + f'Expected an instance of type, not {type(cls).__name__!r}' ) from None @@ -2920,13 +2704,13 @@ def __init__(self, name: str, value, *, type_params=()): # Setting this attribute closes the TypeAliasType from further modification self.__name__ = name - def __setattr__(self, __name: str, __value: object) -> None: + def __setattr__(self, name: str, value: object, /) -> None: if hasattr(self, "__name__"): - self._raise_attribute_error(__name) - super().__setattr__(__name, __value) + self._raise_attribute_error(name) + super().__setattr__(name, value) - def __delattr__(self, __name: str) -> Never: - self._raise_attribute_error(__name) + def __delattr__(self, name: str, /) -> Never: + self._raise_attribute_error(name) def _raise_attribute_error(self, name: str) -> Never: # Match the Python 3.12 error messages exactly @@ -2987,7 +2771,7 @@ def __ror__(self, left): is_protocol = typing.is_protocol get_protocol_members = typing.get_protocol_members else: - def is_protocol(__tp: type) -> bool: + def is_protocol(tp: type, /) -> bool: """Return True if the given type is a Protocol. Example:: @@ -3002,13 +2786,13 @@ def is_protocol(__tp: type) -> bool: False """ return ( - isinstance(__tp, type) - and getattr(__tp, '_is_protocol', False) - and __tp is not Protocol - and __tp is not getattr(typing, "Protocol", object()) + isinstance(tp, type) + and getattr(tp, '_is_protocol', False) + and tp is not Protocol + and tp is not typing.Protocol ) - def get_protocol_members(__tp: type) -> typing.FrozenSet[str]: + def get_protocol_members(tp: type, /) -> typing.FrozenSet[str]: """Return the set of members defined in a Protocol. Example:: @@ -3022,11 +2806,11 @@ def get_protocol_members(__tp: type) -> typing.FrozenSet[str]: Raise a TypeError for arguments that are not Protocols. """ - if not is_protocol(__tp): - raise TypeError(f'{__tp!r} is not a Protocol') - if hasattr(__tp, '__protocol_attrs__'): - return frozenset(__tp.__protocol_attrs__) - return frozenset(_get_protocol_attrs(__tp)) + if not is_protocol(tp): + raise TypeError(f'{tp!r} is not a Protocol') + if hasattr(tp, '__protocol_attrs__'): + return frozenset(tp.__protocol_attrs__) + return frozenset(_get_protocol_attrs(tp)) # Aliases for items that have always been in typing. diff --git a/tox.ini b/tox.ini index 3d583efc..5bed0225 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] isolated_build = True -envlist = py37, py38, py39, py310, py311, py312 +envlist = py38, py39, py310, py311, py312 [testenv] changedir = src