From 86b933ad5dfb17ba280cc77065fb7fcf1d8782a0 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 3 Oct 2022 12:24:42 -0700 Subject: [PATCH 1/5] Add @typing.override --- CHANGELOG.md | 3 +++ src/test_typing_extensions.py | 15 +++++++++++++++ src/typing_extensions.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5149fd5f..93d79ceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ # Release 4.4.0 () + - 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). +- Runtime support for PEP 698, adding `typing_extensions.override`. Patch by + Jelle Zijlstra. # Release 4.3.0 (July 1, 2022) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 64d052b2..295e44ce 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -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,20 @@ 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) + + 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 b03f5dd0..963e0c61 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -61,6 +61,7 @@ 'Literal', 'NewType', 'overload', + 'override', 'Protocol', 'reveal_type', 'runtime', @@ -2000,6 +2001,34 @@ def decorator(cls_or_fn): return decorator +if hasattr(typing, "override"): + override = typing.override +else: + _T = typing.TypeVar("_T", bound=typing.Callable[..., typing.Any]) + + def override(__arg: _T) -> _T: + """Indicate that a method overrides 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 the base class is changed + without an equivalent change to a child class. + + """ + 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 From 62918a27a81f9ef0bc80cd86fe0347afc82fd602 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 3 Oct 2022 13:10:25 -0700 Subject: [PATCH 2/5] rename the TypeVar --- src/typing_extensions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 963e0c61..968dc467 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2004,9 +2004,9 @@ def decorator(cls_or_fn): if hasattr(typing, "override"): override = typing.override else: - _T = typing.TypeVar("_T", bound=typing.Callable[..., typing.Any]) + _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any]) - def override(__arg: _T) -> _T: + def override(__arg: _F) -> _F: """Indicate that a method overrides a method in a base class. Usage: From 9b21216178b0fe53fafe0128bf527e6d4d9a1004 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 3 Oct 2022 13:49:38 -0700 Subject: [PATCH 3/5] reference PEP 698 --- src/typing_extensions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 968dc467..e023a13d 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2025,6 +2025,8 @@ def method(self) -> None: This helps prevent bugs that may occur when the base class is changed without an equivalent change to a child class. + See PEP 698 for details. + """ return __arg From 116785cf94f6addda83cae8d1ff331ee92a86fee Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 3 Oct 2022 14:03:51 -0700 Subject: [PATCH 4/5] Update src/typing_extensions.py Co-authored-by: Steven Troxler --- src/typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index e023a13d..717c38c1 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2022,7 +2022,7 @@ def method(self) -> None: 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 the base class is changed + 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. From ef932b6fe4bc7e481f5084407e170f0d7d6ae18e Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 6 Oct 2022 15:03:01 -0700 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Alex Waygood --- src/test_typing_extensions.py | 1 + src/typing_extensions.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 4395c46c..15b2147b 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -173,6 +173,7 @@ def foo(self): self.assertIsSubclass(Derived, Base) self.assertEqual(Derived().foo(), 42) + self.assertEqual(dir(Base.foo), dir(Derived.foo)) class AnyTests(BaseTestCase): diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 8c9867c1..acb0a94f 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2085,7 +2085,7 @@ def decorator(cls_or_fn): _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any]) def override(__arg: _F) -> _F: - """Indicate that a method overrides a method in a base class. + """Indicate that a method is intended to override a method in a base class. Usage: