From 60aa1e20c07af2f0165485a2cdbbd23a5265a194 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe <50501825+Gobot1234@users.noreply.github.com> Date: Fri, 12 Nov 2021 02:23:29 +0000 Subject: [PATCH] Add PEP 673 Self type (#933) --- typing_extensions/CHANGELOG | 4 ++ .../src_py3/test_typing_extensions.py | 38 +++++++++- .../src_py3/typing_extensions.py | 72 +++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/typing_extensions/CHANGELOG b/typing_extensions/CHANGELOG index ac8f868a8..4c3a51d6c 100644 --- a/typing_extensions/CHANGELOG +++ b/typing_extensions/CHANGELOG @@ -8,6 +8,10 @@ Dropped support for Python versions 3.5 and older. Simplified backports for Python 3.6.0 and newer. Patch by Adam Turner (@AA-Turner). +## Added in version 4.0.0 + +- Runtime support for PEP 673 and `typing_extensions.Self`. + ## Removed in version 4.0.0 The following non-exported but non-private names have been removed as they are diff --git a/typing_extensions/src_py3/test_typing_extensions.py b/typing_extensions/src_py3/test_typing_extensions.py index ff786022f..841d08cfc 100644 --- a/typing_extensions/src_py3/test_typing_extensions.py +++ b/typing_extensions/src_py3/test_typing_extensions.py @@ -16,7 +16,7 @@ from typing import Generic, NamedTuple from typing import no_type_check import typing_extensions -from typing_extensions import NoReturn, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict +from typing_extensions import NoReturn, 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 from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, overload @@ -2075,6 +2075,42 @@ def test_no_isinstance(self): issubclass(int, TypeGuard) + +class SelfTests(BaseTestCase): + def test_basics(self): + class Foo: + def bar(self) -> Self: ... + + self.assertEqual(gth(Foo.bar), {'return': Self}) + + def test_repr(self): + if hasattr(typing, 'Self'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(Self), '{}.Self'.format(mod_name)) + + def test_cannot_subscript(self): + with self.assertRaises(TypeError): + Self[int] + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(Self)): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + Self() + with self.assertRaises(TypeError): + type(Self)() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, Self) + with self.assertRaises(TypeError): + issubclass(int, Self) + class AllTests(BaseTestCase): def test_typing_extensions_includes_standard(self): diff --git a/typing_extensions/src_py3/typing_extensions.py b/typing_extensions/src_py3/typing_extensions.py index 1e96d1460..3e5b488c2 100644 --- a/typing_extensions/src_py3/typing_extensions.py +++ b/typing_extensions/src_py3/typing_extensions.py @@ -45,6 +45,7 @@ def _check_generic(cls, parameters): 'Concatenate', 'Final', 'ParamSpec', + 'Self', 'Type', # ABCs (from collections.abc). @@ -2046,3 +2047,74 @@ def __eq__(self, other): return self is other TypeGuard = _TypeGuard(_root=True) + + +if hasattr(typing, "Self"): + Self = typing.Self + +elif sys.version_info[:2] >= (3, 9): + class _SelfForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_SelfForm + def Self(self, params): + """Used to spell the type of "self" in classes. + + Example:: + + from typing import Self + + class ReturnsSelf: + def parse(self, data: bytes) -> Self: + ... + return self + + """ + + raise TypeError(f"{self} is not subscriptable") + +elif sys.version_info[:2] >= (3, 7): + class _SelfForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + Self = _SelfForm( + "Self", + doc="""Used to spell the type of "self" in classes. + + Example:: + + from typing import Self + + class ReturnsSelf: + def parse(self, data: bytes) -> Self: + ... + return self + + """ + ) +else: + class _Self(typing._FinalTypingBase, _root=True): + """Used to spell the type of "self" in classes. + + Example:: + + from typing import Self + + class ReturnsSelf: + def parse(self, data: bytes) -> Self: + ... + return self + + """ + + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass().") + + Self = _Self(_root=True)