From 7d1aeea6b3482632b71dcabc11e021ede704bd66 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Tue, 30 Aug 2022 18:09:34 +0100 Subject: [PATCH] Implement typing_extensions.Any (#68) --- CHANGELOG.md | 5 ++++ src/test_typing_extensions.py | 48 ++++++++++++++++++++++++++++++++--- src/typing_extensions.py | 32 +++++++++++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) 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 diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index b9d9a4a4..18a9132f 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 @@ -160,6 +160,48 @@ def test_exception(self): assert_never(None) +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): + ... + + 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") + 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): + 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): @@ -3018,7 +3060,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( diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 4f0c8197..b03f5dd0 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,37 @@ 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_extensions.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".