diff --git a/CHANGELOG.md b/CHANGELOG.md index 79e36891..92587752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- Runtime support for PEP 702, adding `typing_extensions.deprecated`. Patch + by Jelle Zijlstra. - Add better default value for TypeVar `default` parameter, PEP 696. Enables runtime check if `None` was passed as default. Patch by Marc Mueller (@cdce8p). - The `@typing_extensions.override` decorator now sets the `.__override__` diff --git a/README.md b/README.md index 55314d46..15200c35 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ This module currently contains the following: - `override` (see [PEP 698](https://peps.python.org/pep-0698/)) - The `default=` argument to `TypeVar`, `ParamSpec`, and `TypeVarTuple` (see [PEP 696](https://peps.python.org/pep-0696/)) - The `infer_variance=` argument to `TypeVar` (see [PEP 695](https://peps.python.org/pep-0695/)) + - The `@deprecated` decorator (see [PEP 702](https://peps.python.org/pep-0698/)) - In `typing` since Python 3.11 diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 913b4901..79c0274d 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -29,7 +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 typing_extensions import override, deprecated from _typed_dict_test_helper import Foo, FooGeneric # Flags used to mark tests that only apply after a specific @@ -202,6 +202,35 @@ def static_method_bad_order(): self.assertIs(False, hasattr(Derived.static_method_bad_order, "__override__")) +class DeprecatedTests(BaseTestCase): + def test_deprecated(self): + @deprecated("A will go away soon") + class A: + pass + + self.assertEqual(A.__deprecated__, "A will go away soon") + self.assertIsInstance(A, type) + + @deprecated("b will go away soon") + def b(): + pass + + self.assertEqual(b.__deprecated__, "b will go away soon") + self.assertIsInstance(b, types.FunctionType) + + @overload + @deprecated("no more ints") + def h(x: int) -> int: ... + @overload + def h(x: str) -> str: ... + def h(x): + return x + + overloads = get_overloads(h) + self.assertEqual(len(overloads), 2) + self.assertEqual(overloads[0].__deprecated__, "no more ints") + + class AnyTests(BaseTestCase): def test_can_subclass(self): class Mock(Any): pass diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 37efdd09..c32c63d1 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -52,6 +52,7 @@ 'assert_type', 'clear_overloads', 'dataclass_transform', + 'deprecated', 'get_overloads', 'final', 'get_args', @@ -2129,6 +2130,49 @@ def method(self) -> None: return __arg +if hasattr(typing, "deprecated"): + deprecated = typing.deprecated +else: + _T = typing.TypeVar("_T") + + def deprecated(__msg: str) -> typing.Callable[[_T], _T]: + """Indicate that a class, function or overload is deprecated. + + Usage: + + @deprecated("Use B instead") + class A: + pass + + @deprecated("Use g instead") + def f(): + pass + + @overload + @deprecated("int support is deprecated") + def g(x: int) -> int: ... + @overload + def g(x: str) -> int: ... + + When this decorator is applied to an object, the type checker + will generate a diagnostic on usage of the deprecated object. + + No runtime warning is issued. The decorator sets the ``__deprecated__`` + attribute on the decorated object to the deprecation message + passed to the decorator. If applied to an overload, the decorator + must be after the ``@overload`` decorator for the attribute to + exist on the overload as returned by ``get_overloads()``. + + See PEP 702 for details. + + """ + def decorator(__arg: _T) -> _T: + __arg.__deprecated__ = __msg + return __arg + + return decorator + + # 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