From 6a83d0d6850af6cd25038d12ae512a57ec52b90e Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Sun, 14 Aug 2022 23:42:42 +0100 Subject: [PATCH 01/10] Implement typing_extensions.Any --- src/test_typing_extensions.py | 25 +++++++++++++++++++++++++ src/typing_extensions.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index ee498e56..a0edc33a 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -160,6 +160,31 @@ def test_exception(self): assert_never(None) +class AnyTests(BaseTestCase): + 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") + self.assertEqual(repr(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): diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 31d3564e..ceffc3e3 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -11,6 +11,7 @@ # Please keep __all__ alphabetized within each category. __all__ = [ # Super-special typing primitives. + 'Any', 'ClassVar', 'Concatenate', 'Final', @@ -149,6 +150,38 @@ 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.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". From 4fe8d3bd1b555a85b2cc4f77bac27bfded490620 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Sun, 14 Aug 2022 23:48:21 +0100 Subject: [PATCH 02/10] Add changelog entry --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad7e43c9..5149fd5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 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). + # Release 4.3.0 (July 1, 2022) - Add `typing_extensions.NamedTuple`, allowing for generic `NamedTuple`s on From 4644d586e86d3788e49a777df97260eec785babd Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Sun, 14 Aug 2022 23:49:56 +0100 Subject: [PATCH 03/10] Fix typo --- 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 ceffc3e3..19d13590 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -158,7 +158,7 @@ def _collect_type_vars(types, typevar_types=None): class _AnyMeta(type): def __instancecheck__(self, obj): if self is Any: - raise TypeError("typing.Any cannot be used with isinstance()") + raise TypeError("typing_extensions.Any cannot be used with isinstance()") return super().__instancecheck__(obj) def __repr__(self): From 6066e07a08e517e3612ba3a72a4c837964a00fbe Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Mon, 15 Aug 2022 00:27:01 +0100 Subject: [PATCH 04/10] Change import --- src/test_typing_extensions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index a0edc33a..930bc811 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 From 09acc7236177436b3a2f437617b87a7ca16fb9e1 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Mon, 15 Aug 2022 00:38:46 +0100 Subject: [PATCH 05/10] Fix more tests --- src/test_typing_extensions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 930bc811..0ddfebb2 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -170,7 +170,7 @@ def test_repr(self): else: mod_name = 'typing_extensions' self.assertEqual(repr(Any), f"{mod_name}.Any") - self.assertEqual(repr(SubclassesAny), "") + self.assertEqual(repr(self.SubclassesAny), "") def test_instantiation(self): with self.assertRaises(TypeError): @@ -3040,7 +3040,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( From 42effe0a717c5a63f0be8f9de04a6c86af688e8e Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Mon, 15 Aug 2022 00:53:29 +0100 Subject: [PATCH 06/10] More more test fixing --- src/test_typing_extensions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 0ddfebb2..598b4710 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -170,7 +170,8 @@ def test_repr(self): else: mod_name = 'typing_extensions' self.assertEqual(repr(Any), f"{mod_name}.Any") - self.assertEqual(repr(self.SubclassesAny), "") + 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): From be49082b6ed66423d3903399a54e6449e3aded81 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Mon, 15 Aug 2022 00:55:05 +0100 Subject: [PATCH 07/10] Ok this should all work now --- src/test_typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 598b4710..60e85f1f 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -171,7 +171,7 @@ def test_repr(self): 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), "") + self.assertEqual(repr(self.SubclassesAny), "") def test_instantiation(self): with self.assertRaises(TypeError): From 6ca1f851d2c8538f53044007476ad2a71d49acbc Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Mon, 15 Aug 2022 11:28:25 +0100 Subject: [PATCH 08/10] Fix flake8 lint --- src/typing_extensions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 19d13590..0708aeea 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -166,7 +166,6 @@ def __repr__(self): return "typing_extensions.Any" return super().__repr__() - class Any(metaclass=_AnyMeta): """Special type indicating an unconstrained type. - Any is compatible with every type. From ccff9a1d5487b981167dd0ed76862e7506d35c3d Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Mon, 15 Aug 2022 11:33:11 +0100 Subject: [PATCH 09/10] Fix another flake8 lint --- src/test_typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 60e85f1f..eb459e7c 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -176,7 +176,7 @@ def test_repr(self): def test_instantiation(self): with self.assertRaises(TypeError): Any() - + self.SubclassesAny() def test_isinstance(self): From 4a617be30390309015e8354f458f71cf5de417fb Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Tue, 30 Aug 2022 17:23:40 +0100 Subject: [PATCH 10/10] Add subclassing tests --- src/test_typing_extensions.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index eb459e7c..562413b4 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -161,6 +161,22 @@ def test_exception(self): 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): ...