From 933f5a0d20541ee439000b51763451cc27003e57 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 22 Aug 2025 15:25:32 +0100 Subject: [PATCH 1/3] Delete many tests (testing to see if I get a comment from the new coverage workflow when I make a PR from a branch on the upstream repo) --- src/test_typing_extensions.py | 9743 ++++++++++++--------------------- 1 file changed, 3512 insertions(+), 6231 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 1ef9f013..2e897880 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -532,48 +532,6 @@ def test_pickle(self): self.assertIs(self.bottom_type, pickle.loads(pickled)) -class NoReturnTests(BottomTypeTestsMixin, BaseTestCase): - bottom_type = NoReturn - - def test_repr(self): - if hasattr(typing, 'NoReturn'): - self.assertEqual(repr(NoReturn), 'typing.NoReturn') - else: - self.assertEqual(repr(NoReturn), 'typing_extensions.NoReturn') - - def test_get_type_hints(self): - def some(arg: NoReturn) -> NoReturn: ... - def some_str(arg: 'NoReturn') -> 'typing.NoReturn': ... - - expected = {'arg': NoReturn, 'return': NoReturn} - for target in some, some_str: - with self.subTest(target=target): - self.assertEqual(gth(target), expected) - - def test_not_equality(self): - self.assertNotEqual(NoReturn, Never) - self.assertNotEqual(Never, NoReturn) - - -class NeverTests(BottomTypeTestsMixin, BaseTestCase): - bottom_type = Never - - def test_repr(self): - if hasattr(typing, 'Never'): - self.assertEqual(repr(Never), 'typing.Never') - else: - self.assertEqual(repr(Never), 'typing_extensions.Never') - - def test_get_type_hints(self): - def some(arg: Never) -> Never: ... - def some_str(arg: 'Never') -> 'typing_extensions.Never': ... - - expected = {'arg': Never, 'return': Never} - for target in [some, some_str]: - with self.subTest(target=target): - self.assertEqual(gth(target), expected) - - class AssertNeverTests(BaseTestCase): def test_exception(self): with self.assertRaises(AssertionError): @@ -2432,6932 +2390,4255 @@ class NT(NamedTuple): ) -class ProtocolTests(BaseTestCase): - def test_runtime_alias(self): - self.assertIs(runtime, runtime_checkable) - - def test_basic_protocol(self): - @runtime_checkable - class P(Protocol): - def meth(self): - pass - class C: pass - class D: - def meth(self): - pass - def f(): - pass - self.assertIsSubclass(D, P) - self.assertIsInstance(D(), P) - self.assertNotIsSubclass(C, P) - self.assertNotIsInstance(C(), P) - self.assertNotIsSubclass(types.FunctionType, P) - self.assertNotIsInstance(f, P) - - def test_everything_implements_empty_protocol(self): - @runtime_checkable - class Empty(Protocol): pass - class C: pass - def f(): - pass - for thing in (object, type, tuple, C, types.FunctionType): - self.assertIsSubclass(thing, Empty) - for thing in (object(), 1, (), typing, f): - self.assertIsInstance(thing, Empty) - - def test_function_implements_protocol(self): - def f(): - pass - self.assertIsInstance(f, HasCallProtocol) - - def test_no_inheritance_from_nominal(self): - class C: pass - class BP(Protocol): pass - with self.assertRaises(TypeError): - class P(C, Protocol): - pass - with self.assertRaises(TypeError): - class Q(Protocol, C): - pass - with self.assertRaises(TypeError): - class R(BP, C, Protocol): - pass - class D(BP, C): pass - class E(C, BP): pass - self.assertNotIsInstance(D(), E) - self.assertNotIsInstance(E(), D) - - def test_runtimecheckable_on_typing_dot_Protocol(self): - @runtime_checkable - class Foo(typing.Protocol): - x: int - - class Bar: - def __init__(self): - self.x = 42 +class Point2DGeneric(Generic[T], TypedDict): + a: T + b: T - self.assertIsInstance(Bar(), Foo) - self.assertNotIsInstance(object(), Foo) - def test_typing_dot_runtimecheckable_on_Protocol(self): - @typing.runtime_checkable - class Foo(Protocol): - x: int +class Bar(Foo): + b: int - class Bar: - def __init__(self): - self.x = 42 - self.assertIsInstance(Bar(), Foo) - self.assertNotIsInstance(object(), Foo) +class BarGeneric(FooGeneric[T], total=False): + b: int - def test_typing_Protocol_and_extensions_Protocol_can_mix(self): - class TypingProto(typing.Protocol): - x: int - class ExtensionsProto(Protocol): - y: int +class TypedDictTests(BaseTestCase): + def test_basics_functional_syntax(self): + Emp = TypedDict('Emp', {'name': str, 'id': int}) + self.assertIsSubclass(Emp, dict) + self.assertIsSubclass(Emp, typing.MutableMapping) + self.assertNotIsSubclass(Emp, collections.abc.Sequence) + jim = Emp(name='Jim', id=1) + self.assertIs(type(jim), dict) + self.assertEqual(jim['name'], 'Jim') + self.assertEqual(jim['id'], 1) + self.assertEqual(Emp.__name__, 'Emp') + self.assertEqual(Emp.__module__, __name__) + self.assertEqual(Emp.__bases__, (dict,)) + self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) + self.assertEqual(Emp.__total__, True) - class SubProto(TypingProto, ExtensionsProto, typing.Protocol): - z: int + def test_allowed_as_type_argument(self): + # https://github.com/python/typing_extensions/issues/613 + obj = typing.Type[typing_extensions.TypedDict] + self.assertIs(typing_extensions.get_origin(obj), type) + self.assertEqual(typing_extensions.get_args(obj), (typing_extensions.TypedDict,)) - class SubProto2(TypingProto, ExtensionsProto, Protocol): - z: int + @skipIf(sys.version_info < (3, 13), "Change in behavior in 3.13") + def test_keywords_syntax_raises_on_3_13(self): + with self.assertRaises(TypeError), self.assertWarns(DeprecationWarning): + TypedDict('Emp', name=str, id=int) - class SubProto3(ExtensionsProto, TypingProto, typing.Protocol): - z: int + @skipIf(sys.version_info >= (3, 13), "3.13 removes support for kwargs") + def test_basics_keywords_syntax(self): + with self.assertWarns(DeprecationWarning): + Emp = TypedDict('Emp', name=str, id=int) + self.assertIsSubclass(Emp, dict) + self.assertIsSubclass(Emp, typing.MutableMapping) + self.assertNotIsSubclass(Emp, collections.abc.Sequence) + jim = Emp(name='Jim', id=1) + self.assertIs(type(jim), dict) + self.assertEqual(jim['name'], 'Jim') + self.assertEqual(jim['id'], 1) + self.assertEqual(Emp.__name__, 'Emp') + self.assertEqual(Emp.__module__, __name__) + self.assertEqual(Emp.__bases__, (dict,)) + self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) + self.assertEqual(Emp.__total__, True) - class SubProto4(ExtensionsProto, TypingProto, Protocol): - z: int + @skipIf(sys.version_info >= (3, 13), "3.13 removes support for kwargs") + def test_typeddict_special_keyword_names(self): + with self.assertWarns(DeprecationWarning): + TD = TypedDict("TD", cls=type, self=object, typename=str, _typename=int, + fields=list, _fields=dict, + closed=bool, extra_items=bool) + self.assertEqual(TD.__name__, 'TD') + self.assertEqual(TD.__annotations__, {'cls': type, 'self': object, 'typename': str, + '_typename': int, 'fields': list, '_fields': dict, + 'closed': bool, 'extra_items': bool}) + self.assertIsNone(TD.__closed__) + self.assertIs(TD.__extra_items__, NoExtraItems) + a = TD(cls=str, self=42, typename='foo', _typename=53, + fields=[('bar', tuple)], _fields={'baz', set}, + closed=None, extra_items="tea pot") + self.assertEqual(a['cls'], str) + self.assertEqual(a['self'], 42) + self.assertEqual(a['typename'], 'foo') + self.assertEqual(a['_typename'], 53) + self.assertEqual(a['fields'], [('bar', tuple)]) + self.assertEqual(a['_fields'], {'baz', set}) + self.assertIsNone(a['closed']) + self.assertEqual(a['extra_items'], "tea pot") - for proto in ( - ExtensionsProto, SubProto, SubProto2, SubProto3, SubProto4 - ): - with self.subTest(proto=proto.__name__): - self.assertTrue(is_protocol(proto)) - if Protocol is not typing.Protocol: - self.assertIsInstance(proto, typing_extensions._ProtocolMeta) - self.assertIsInstance(proto.__protocol_attrs__, set) - with self.assertRaisesRegex( - TypeError, "Protocols cannot be instantiated" - ): - proto() - # check these don't raise - runtime_checkable(proto) - typing.runtime_checkable(proto) - - class Concrete(SubProto): pass - class Concrete2(SubProto2): pass - class Concrete3(SubProto3): pass - class Concrete4(SubProto4): pass - - for cls in Concrete, Concrete2, Concrete3, Concrete4: - with self.subTest(cls=cls.__name__): - self.assertFalse(is_protocol(cls)) - # Check that this doesn't raise: - self.assertIsInstance(cls(), cls) - with self.assertRaises(TypeError): - runtime_checkable(cls) - with self.assertRaises(TypeError): - typing.runtime_checkable(cls) - - def test_no_instantiation(self): - class P(Protocol): pass - with self.assertRaises(TypeError): - P() - class C(P): pass - self.assertIsInstance(C(), C) - T = TypeVar('T') - class PG(Protocol[T]): pass + def test_typeddict_create_errors(self): with self.assertRaises(TypeError): - PG() + TypedDict.__new__() with self.assertRaises(TypeError): - PG[int]() + TypedDict() with self.assertRaises(TypeError): - PG[T]() - class CG(PG[T]): pass - self.assertIsInstance(CG[int](), CG) - - def test_protocol_defining_init_does_not_get_overridden(self): - # check that P.__init__ doesn't get clobbered - # see https://bugs.python.org/issue44807 - - class P(Protocol): - x: int - def __init__(self, x: int) -> None: - self.x = x - class C: pass - - c = C() - P.__init__(c, 1) - self.assertEqual(c.x, 1) - - def test_concrete_class_inheriting_init_from_protocol(self): - class P(Protocol): - x: int - def __init__(self, x: int) -> None: - self.x = x - - class C(P): pass - - c = C(1) - self.assertIsInstance(c, C) - self.assertEqual(c.x, 1) + TypedDict('Emp', [('name', str)], None) - def test_cannot_instantiate_abstract(self): - @runtime_checkable - class P(Protocol): - @abc.abstractmethod - def ameth(self) -> int: - raise NotImplementedError - class B(P): - pass - class C(B): - def ameth(self) -> int: - return 26 + def test_typeddict_errors(self): + Emp = TypedDict('Emp', {'name': str, 'id': int}) + self.assertEqual(TypedDict.__module__, 'typing_extensions') + jim = Emp(name='Jim', id=1) with self.assertRaises(TypeError): - B() - self.assertIsInstance(C(), P) - - def test_subprotocols_extending(self): - class P1(Protocol): - def meth1(self): - pass - @runtime_checkable - class P2(P1, Protocol): - def meth2(self): - pass - class C: - def meth1(self): - pass - def meth2(self): - pass - class C1: - def meth1(self): - pass - class C2: - def meth2(self): - pass - self.assertNotIsInstance(C1(), P2) - self.assertNotIsInstance(C2(), P2) - self.assertNotIsSubclass(C1, P2) - self.assertNotIsSubclass(C2, P2) - self.assertIsInstance(C(), P2) - self.assertIsSubclass(C, P2) - - def test_subprotocols_merging(self): - class P1(Protocol): - def meth1(self): - pass - class P2(Protocol): - def meth2(self): - pass - @runtime_checkable - class P(P1, P2, Protocol): - pass - class C: - def meth1(self): - pass - def meth2(self): - pass - class C1: - def meth1(self): - pass - class C2: - def meth2(self): - pass - self.assertNotIsInstance(C1(), P) - self.assertNotIsInstance(C2(), P) - self.assertNotIsSubclass(C1, P) - self.assertNotIsSubclass(C2, P) - self.assertIsInstance(C(), P) - self.assertIsSubclass(C, P) - - def test_protocols_issubclass(self): - T = TypeVar('T') - @runtime_checkable - class P(Protocol): - def x(self): ... - @runtime_checkable - class PG(Protocol[T]): - def x(self): ... - class BadP(Protocol): - def x(self): ... - class BadPG(Protocol[T]): - def x(self): ... - class C: - def x(self): ... - self.assertIsSubclass(C, P) - self.assertIsSubclass(C, PG) - self.assertIsSubclass(BadP, PG) - - no_subscripted_generics = ( - "Subscripted generics cannot be used with class and instance checks" - ) - - with self.assertRaisesRegex(TypeError, no_subscripted_generics): - issubclass(C, PG[T]) - with self.assertRaisesRegex(TypeError, no_subscripted_generics): - issubclass(C, PG[C]) + isinstance({}, Emp) + with self.assertRaises(TypeError): + isinstance(jim, Emp) + with self.assertRaises(TypeError): + issubclass(dict, Emp) - only_runtime_checkable_protocols = ( - "Instance and class checks can only be used with " - "@runtime_checkable protocols" - ) + if not TYPING_3_11_0: + with self.assertRaises(TypeError), self.assertWarns(DeprecationWarning): + TypedDict('Hi', x=1) + with self.assertRaises(TypeError): + TypedDict('Hi', [('x', int), ('y', 1)]) + with self.assertRaises(TypeError): + TypedDict('Hi', [('x', int)], y=int) - with self.assertRaisesRegex(TypeError, only_runtime_checkable_protocols): - issubclass(C, BadP) - with self.assertRaisesRegex(TypeError, only_runtime_checkable_protocols): - issubclass(C, BadPG) - - with self.assertRaisesRegex(TypeError, no_subscripted_generics): - issubclass(P, PG[T]) - with self.assertRaisesRegex(TypeError, no_subscripted_generics): - issubclass(PG, PG[int]) - - only_classes_allowed = r"issubclass\(\) arg 1 must be a class" - - with self.assertRaisesRegex(TypeError, only_classes_allowed): - issubclass(1, P) - with self.assertRaisesRegex(TypeError, only_classes_allowed): - issubclass(1, PG) - with self.assertRaisesRegex(TypeError, only_classes_allowed): - issubclass(1, BadP) - with self.assertRaisesRegex(TypeError, only_classes_allowed): - issubclass(1, BadPG) - - def test_implicit_issubclass_between_two_protocols(self): - @runtime_checkable - class CallableMembersProto(Protocol): - def meth(self): ... - - # All the below protocols should be considered "subclasses" - # of CallableMembersProto at runtime, - # even though none of them explicitly subclass CallableMembersProto - - class IdenticalProto(Protocol): - def meth(self): ... - - class SupersetProto(Protocol): - def meth(self): ... - def meth2(self): ... - - class NonCallableMembersProto(Protocol): - meth: Callable[[], None] - - class NonCallableMembersSupersetProto(Protocol): - meth: Callable[[], None] - meth2: Callable[[str, int], bool] - - class MixedMembersProto1(Protocol): - meth: Callable[[], None] - def meth2(self): ... - - class MixedMembersProto2(Protocol): - def meth(self): ... - meth2: Callable[[str, int], bool] - - for proto in ( - IdenticalProto, SupersetProto, NonCallableMembersProto, - NonCallableMembersSupersetProto, MixedMembersProto1, MixedMembersProto2 - ): - with self.subTest(proto=proto.__name__): - self.assertIsSubclass(proto, CallableMembersProto) + def test_py36_class_syntax_usage(self): + self.assertEqual(LabelPoint2D.__name__, 'LabelPoint2D') + self.assertEqual(LabelPoint2D.__module__, __name__) + self.assertEqual(LabelPoint2D.__annotations__, {'x': int, 'y': int, 'label': str}) + self.assertEqual(LabelPoint2D.__bases__, (dict,)) + self.assertEqual(LabelPoint2D.__total__, True) + self.assertNotIsSubclass(LabelPoint2D, typing.Sequence) + not_origin = Point2D(x=0, y=1) + self.assertEqual(not_origin['x'], 0) + self.assertEqual(not_origin['y'], 1) + other = LabelPoint2D(x=0, y=1, label='hi') + self.assertEqual(other['label'], 'hi') - # These two shouldn't be considered subclasses of CallableMembersProto, however, - # since they don't have the `meth` protocol member + def test_pickle(self): + global EmpD # pickle wants to reference the class by name + EmpD = TypedDict('EmpD', {'name': str, 'id': int}) + jane = EmpD({'name': 'jane', 'id': 37}) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + z = pickle.dumps(jane, proto) + jane2 = pickle.loads(z) + self.assertEqual(jane2, jane) + self.assertEqual(jane2, {'name': 'jane', 'id': 37}) + ZZ = pickle.dumps(EmpD, proto) + EmpDnew = pickle.loads(ZZ) + self.assertEqual(EmpDnew({'name': 'jane', 'id': 37}), jane) - class EmptyProtocol(Protocol): ... - class UnrelatedProtocol(Protocol): - def wut(self): ... + def test_pickle_generic(self): + point = Point2DGeneric(a=5.0, b=3.0) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + z = pickle.dumps(point, proto) + point2 = pickle.loads(z) + self.assertEqual(point2, point) + self.assertEqual(point2, {'a': 5.0, 'b': 3.0}) + ZZ = pickle.dumps(Point2DGeneric, proto) + Point2DGenericNew = pickle.loads(ZZ) + self.assertEqual(Point2DGenericNew({'a': 5.0, 'b': 3.0}), point) - self.assertNotIsSubclass(EmptyProtocol, CallableMembersProto) - self.assertNotIsSubclass(UnrelatedProtocol, CallableMembersProto) + def test_optional(self): + EmpD = TypedDict('EmpD', {'name': str, 'id': int}) - # These aren't protocols at all (despite having annotations), - # so they should only be considered subclasses of CallableMembersProto - # if they *actually have an attribute* matching the `meth` member - # (just having an annotation is insufficient) + self.assertEqual(typing.Optional[EmpD], typing.Union[None, EmpD]) + self.assertNotEqual(typing.List[EmpD], typing.Tuple[EmpD]) - class AnnotatedButNotAProtocol: - meth: Callable[[], None] + def test_total(self): + D = TypedDict('D', {'x': int}, total=False) + self.assertEqual(D(), {}) + self.assertEqual(D(x=1), {'x': 1}) + self.assertEqual(D.__total__, False) + self.assertEqual(D.__required_keys__, frozenset()) + self.assertEqual(D.__optional_keys__, {'x'}) - class NotAProtocolButAnImplicitSubclass: - def meth(self): pass + self.assertEqual(Options(), {}) + self.assertEqual(Options(log_level=2), {'log_level': 2}) + self.assertEqual(Options.__total__, False) + self.assertEqual(Options.__required_keys__, frozenset()) + self.assertEqual(Options.__optional_keys__, {'log_level', 'log_path'}) - class NotAProtocolButAnImplicitSubclass2: - meth: Callable[[], None] - def meth(self): pass + def test_total_inherits_non_total(self): + class TD1(TypedDict, total=False): + a: int - class NotAProtocolButAnImplicitSubclass3: - meth: Callable[[], None] - meth2: Callable[[int, str], bool] - def meth(self): pass - def meth2(self, x, y): return True + self.assertIs(TD1.__total__, False) - self.assertNotIsSubclass(AnnotatedButNotAProtocol, CallableMembersProto) - self.assertIsSubclass(NotAProtocolButAnImplicitSubclass, CallableMembersProto) - self.assertIsSubclass(NotAProtocolButAnImplicitSubclass2, CallableMembersProto) - self.assertIsSubclass(NotAProtocolButAnImplicitSubclass3, CallableMembersProto) + class TD2(TD1): + b: str - @skip_if_py312b1 - def test_issubclass_and_isinstance_on_Protocol_itself(self): - class C: - def x(self): pass + self.assertIs(TD2.__total__, True) - self.assertNotIsSubclass(object, Protocol) - self.assertNotIsInstance(object(), Protocol) + def test_total_with_assigned_value(self): + class TD(TypedDict): + __total__ = "some_value" - self.assertNotIsSubclass(str, Protocol) - self.assertNotIsInstance('foo', Protocol) + self.assertIs(TD.__total__, True) - self.assertNotIsSubclass(C, Protocol) - self.assertNotIsInstance(C(), Protocol) + class TD2(TypedDict, total=True): + __total__ = "some_value" - only_classes_allowed = r"issubclass\(\) arg 1 must be a class" + self.assertIs(TD2.__total__, True) - with self.assertRaisesRegex(TypeError, only_classes_allowed): - issubclass(1, Protocol) - with self.assertRaisesRegex(TypeError, only_classes_allowed): - issubclass('foo', Protocol) - with self.assertRaisesRegex(TypeError, only_classes_allowed): - issubclass(C(), Protocol) + class TD3(TypedDict, total=False): + __total__ = "some value" - T = TypeVar('T') + self.assertIs(TD3.__total__, False) - @runtime_checkable - class EmptyProtocol(Protocol): pass + TD4 = TypedDict('TD4', {'__total__': "some_value"}) # noqa: F821 + self.assertIs(TD4.__total__, True) - @runtime_checkable - class SupportsStartsWith(Protocol): - def startswith(self, x: str) -> bool: ... - @runtime_checkable - class SupportsX(Protocol[T]): - def x(self): ... + def test_optional_keys(self): + class Point2Dor3D(Point2D, total=False): + z: int - for proto in EmptyProtocol, SupportsStartsWith, SupportsX: - with self.subTest(proto=proto.__name__): - self.assertIsSubclass(proto, Protocol) + assert Point2Dor3D.__required_keys__ == frozenset(['x', 'y']) + assert Point2Dor3D.__optional_keys__ == frozenset(['z']) - # gh-105237 / PR #105239: - # check that the presence of Protocol subclasses - # where `issubclass(X, )` evaluates to True - # doesn't influence the result of `issubclass(X, Protocol)` + def test_keys_inheritance(self): + class BaseAnimal(TypedDict): + name: str - self.assertIsSubclass(object, EmptyProtocol) - self.assertIsInstance(object(), EmptyProtocol) - self.assertNotIsSubclass(object, Protocol) - self.assertNotIsInstance(object(), Protocol) + class Animal(BaseAnimal, total=False): + voice: str + tail: bool - self.assertIsSubclass(str, SupportsStartsWith) - self.assertIsInstance('foo', SupportsStartsWith) - self.assertNotIsSubclass(str, Protocol) - self.assertNotIsInstance('foo', Protocol) + class Cat(Animal): + fur_color: str - self.assertIsSubclass(C, SupportsX) - self.assertIsInstance(C(), SupportsX) - self.assertNotIsSubclass(C, Protocol) - self.assertNotIsInstance(C(), Protocol) + assert BaseAnimal.__required_keys__ == frozenset(['name']) + assert BaseAnimal.__optional_keys__ == frozenset([]) + assert BaseAnimal.__annotations__ == {'name': str} - @skip_if_py312b1 - def test_isinstance_checks_not_at_whim_of_gc(self): - self.addCleanup(gc.enable) - gc.disable() - - with self.assertRaisesRegex( - TypeError, - "Protocols can only inherit from other protocols" - ): - class Foo(collections.abc.Mapping, Protocol): - pass - - self.assertNotIsInstance([], collections.abc.Mapping) + assert Animal.__required_keys__ == frozenset(['name']) + assert Animal.__optional_keys__ == frozenset(['tail', 'voice']) + assert Animal.__annotations__ == { + 'name': str, + 'tail': bool, + 'voice': str, + } - def test_protocols_issubclass_non_callable(self): - class C: - x = 1 + assert Cat.__required_keys__ == frozenset(['name', 'fur_color']) + assert Cat.__optional_keys__ == frozenset(['tail', 'voice']) + assert Cat.__annotations__ == { + 'fur_color': str, + 'name': str, + 'tail': bool, + 'voice': str, + } - @runtime_checkable - class PNonCall(Protocol): - x = 1 + @skipIf(sys.version_info == (3, 14, 0, "beta", 1), "Broken on beta 1, fixed in beta 2") + def test_inheritance_pep563(self): + def _make_td(future, class_name, annos, base, extra_names=None): + lines = [] + if future: + lines.append('from __future__ import annotations') + lines.append('from typing import TypedDict') + lines.append(f'class {class_name}({base}):') + for name, anno in annos.items(): + lines.append(f' {name}: {anno}') + code = '\n'.join(lines) + ns = {**extra_names} if extra_names else {} + exec(code, ns) + return ns[class_name] - non_callable_members_illegal = ( - "Protocols with non-method members don't support issubclass()" - ) + for base_future in (True, False): + for child_future in (True, False): + with self.subTest(base_future=base_future, child_future=child_future): + base = _make_td( + base_future, "Base", {"base": "int"}, "TypedDict" + ) + if sys.version_info >= (3, 14): + self.assertIsNotNone(base.__annotate__) + child = _make_td( + child_future, "Child", {"child": "int"}, "Base", {"Base": base} + ) + base_anno = typing.ForwardRef("int", module="builtins") if base_future else int + child_anno = typing.ForwardRef("int", module="builtins") if child_future else int + self.assertEqual(base.__annotations__, {'base': base_anno}) + self.assertEqual( + child.__annotations__, {'child': child_anno, 'base': base_anno} + ) - with self.assertRaisesRegex(TypeError, non_callable_members_illegal): - issubclass(C, PNonCall) + def test_required_notrequired_keys(self): + self.assertEqual(NontotalMovie.__required_keys__, + frozenset({"title"})) + self.assertEqual(NontotalMovie.__optional_keys__, + frozenset({"year"})) - self.assertIsInstance(C(), PNonCall) - PNonCall.register(C) + self.assertEqual(TotalMovie.__required_keys__, + frozenset({"title"})) + self.assertEqual(TotalMovie.__optional_keys__, + frozenset({"year"})) - with self.assertRaisesRegex(TypeError, non_callable_members_illegal): - issubclass(C, PNonCall) + self.assertEqual(VeryAnnotated.__required_keys__, + frozenset()) + self.assertEqual(VeryAnnotated.__optional_keys__, + frozenset({"a"})) - self.assertIsInstance(C(), PNonCall) + self.assertEqual(AnnotatedMovie.__required_keys__, + frozenset({"title"})) + self.assertEqual(AnnotatedMovie.__optional_keys__, + frozenset({"year"})) - # check that non-protocol subclasses are not affected - class D(PNonCall): ... + self.assertEqual(WeirdlyQuotedMovie.__required_keys__, + frozenset({"title"})) + self.assertEqual(WeirdlyQuotedMovie.__optional_keys__, + frozenset({"year"})) - self.assertNotIsSubclass(C, D) - self.assertNotIsInstance(C(), D) - D.register(C) - self.assertIsSubclass(C, D) - self.assertIsInstance(C(), D) + self.assertEqual(ChildTotalMovie.__required_keys__, + frozenset({"title"})) + self.assertEqual(ChildTotalMovie.__optional_keys__, + frozenset({"year"})) - with self.assertRaisesRegex(TypeError, non_callable_members_illegal): - issubclass(D, PNonCall) + self.assertEqual(ChildDeeplyAnnotatedMovie.__required_keys__, + frozenset({"title"})) + self.assertEqual(ChildDeeplyAnnotatedMovie.__optional_keys__, + frozenset({"year"})) - def test_no_weird_caching_with_issubclass_after_isinstance(self): - @runtime_checkable - class Spam(Protocol): - x: int + def test_multiple_inheritance(self): + class One(TypedDict): + one: int + class Two(TypedDict): + two: str + class Untotal(TypedDict, total=False): + untotal: str + Inline = TypedDict('Inline', {'inline': bool}) + class Regular: + pass - class Eggs: - def __init__(self) -> None: - self.x = 42 + class Child(One, Two): + child: bool + self.assertEqual( + Child.__required_keys__, + frozenset(['one', 'two', 'child']), + ) + self.assertEqual( + Child.__optional_keys__, + frozenset([]), + ) + self.assertEqual( + Child.__annotations__, + {'one': int, 'two': str, 'child': bool}, + ) - self.assertIsInstance(Eggs(), Spam) + class ChildWithOptional(One, Untotal): + child: bool + self.assertEqual( + ChildWithOptional.__required_keys__, + frozenset(['one', 'child']), + ) + self.assertEqual( + ChildWithOptional.__optional_keys__, + frozenset(['untotal']), + ) + self.assertEqual( + ChildWithOptional.__annotations__, + {'one': int, 'untotal': str, 'child': bool}, + ) - # gh-104555: If we didn't override ABCMeta.__subclasscheck__ in _ProtocolMeta, - # TypeError wouldn't be raised here, - # as the cached result of the isinstance() check immediately above - # would mean the issubclass() call would short-circuit - # before we got to the "raise TypeError" line - with self.assertRaisesRegex( - TypeError, - "Protocols with non-method members don't support issubclass()" - ): - issubclass(Eggs, Spam) + class ChildWithTotalFalse(One, Untotal, total=False): + child: bool + self.assertEqual( + ChildWithTotalFalse.__required_keys__, + frozenset(['one']), + ) + self.assertEqual( + ChildWithTotalFalse.__optional_keys__, + frozenset(['untotal', 'child']), + ) + self.assertEqual( + ChildWithTotalFalse.__annotations__, + {'one': int, 'untotal': str, 'child': bool}, + ) - def test_no_weird_caching_with_issubclass_after_isinstance_2(self): - @runtime_checkable - class Spam(Protocol): - x: int + class ChildWithInlineAndOptional(Untotal, Inline): + child: bool + self.assertEqual( + ChildWithInlineAndOptional.__required_keys__, + frozenset(['inline', 'child']), + ) + self.assertEqual( + ChildWithInlineAndOptional.__optional_keys__, + frozenset(['untotal']), + ) + self.assertEqual( + ChildWithInlineAndOptional.__annotations__, + {'inline': bool, 'untotal': str, 'child': bool}, + ) - class Eggs: ... + wrong_bases = [ + (One, Regular), + (Regular, One), + (One, Two, Regular), + (Inline, Regular), + (Untotal, Regular), + ] + for bases in wrong_bases: + with self.subTest(bases=bases): + with self.assertRaisesRegex( + TypeError, + 'cannot inherit from both a TypedDict type and a non-TypedDict', + ): + class Wrong(*bases): + pass - self.assertNotIsInstance(Eggs(), Spam) + def test_closed_values(self): + class Implicit(TypedDict): ... + class ExplicitTrue(TypedDict, closed=True): ... + class ExplicitFalse(TypedDict, closed=False): ... - # gh-104555: If we didn't override ABCMeta.__subclasscheck__ in _ProtocolMeta, - # TypeError wouldn't be raised here, - # as the cached result of the isinstance() check immediately above - # would mean the issubclass() call would short-circuit - # before we got to the "raise TypeError" line - with self.assertRaisesRegex( - TypeError, - "Protocols with non-method members don't support issubclass()" - ): - issubclass(Eggs, Spam) + self.assertIsNone(Implicit.__closed__) + self.assertIs(ExplicitTrue.__closed__, True) + self.assertIs(ExplicitFalse.__closed__, False) - def test_no_weird_caching_with_issubclass_after_isinstance_3(self): - @runtime_checkable - class Spam(Protocol): - x: int - class Eggs: - def __getattr__(self, attr): - if attr == "x": - return 42 - raise AttributeError(attr) + @skipIf(TYPING_3_14_0, "only supported on older versions") + def test_closed_typeddict_compat(self): + class Closed(TypedDict, closed=True): + __extra_items__: None - self.assertNotIsInstance(Eggs(), Spam) + class Unclosed(TypedDict, closed=False): + ... - # gh-104555: If we didn't override ABCMeta.__subclasscheck__ in _ProtocolMeta, - # TypeError wouldn't be raised here, - # as the cached result of the isinstance() check immediately above - # would mean the issubclass() call would short-circuit - # before we got to the "raise TypeError" line - with self.assertRaisesRegex( - TypeError, - "Protocols with non-method members don't support issubclass()" - ): - issubclass(Eggs, Spam) + class ChildUnclosed(Closed, Unclosed): + ... - def test_protocols_isinstance(self): - T = TypeVar('T') - @runtime_checkable - class P(Protocol): - def meth(x): ... - @runtime_checkable - class PG(Protocol[T]): - def meth(x): ... - @runtime_checkable - class WeirdProto(Protocol): - meth = str.maketrans - @runtime_checkable - class WeirdProto2(Protocol): - meth = lambda *args, **kwargs: None # noqa: E731 - class CustomCallable: - def __call__(self, *args, **kwargs): - pass - @runtime_checkable - class WeirderProto(Protocol): - meth = CustomCallable() - class BadP(Protocol): - def meth(x): ... - class BadPG(Protocol[T]): - def meth(x): ... - class C: - def meth(x): ... - class C2: - def __init__(self): - self.meth = lambda: None - for klass in C, C2: - for proto in P, PG, WeirdProto, WeirdProto2, WeirderProto: - with self.subTest(klass=klass.__name__, proto=proto.__name__): - self.assertIsInstance(klass(), proto) - - no_subscripted_generics = ( - "Subscripted generics cannot be used with class and instance checks" - ) + self.assertIsNone(ChildUnclosed.__closed__) + self.assertEqual(ChildUnclosed.__extra_items__, NoExtraItems) - with self.assertRaisesRegex(TypeError, no_subscripted_generics): - isinstance(C(), PG[T]) - with self.assertRaisesRegex(TypeError, no_subscripted_generics): - isinstance(C(), PG[C]) + class ChildClosed(Unclosed, Closed): + ... - only_runtime_checkable_msg = ( - "Instance and class checks can only be used " - "with @runtime_checkable protocols" - ) + self.assertIsNone(ChildClosed.__closed__) + self.assertEqual(ChildClosed.__extra_items__, NoExtraItems) - with self.assertRaisesRegex(TypeError, only_runtime_checkable_msg): - isinstance(C(), BadP) - with self.assertRaisesRegex(TypeError, only_runtime_checkable_msg): - isinstance(C(), BadPG) + def test_extra_items_class_arg(self): + class TD(TypedDict, extra_items=int): + a: str - def test_protocols_isinstance_properties_and_descriptors(self): - class C: - @property - def attr(self): - return 42 + self.assertIs(TD.__extra_items__, int) + self.assertEqual(TD.__annotations__, {'a': str}) + self.assertEqual(TD.__required_keys__, frozenset({'a'})) + self.assertEqual(TD.__optional_keys__, frozenset()) - class CustomDescriptor: - def __get__(self, obj, objtype=None): - return 42 + class NoExtra(TypedDict): + a: str - class D: - attr = CustomDescriptor() + self.assertIs(NoExtra.__extra_items__, NoExtraItems) + self.assertEqual(NoExtra.__annotations__, {'a': str}) + self.assertEqual(NoExtra.__required_keys__, frozenset({'a'})) + self.assertEqual(NoExtra.__optional_keys__, frozenset()) - # Check that properties set on superclasses - # are still found by the isinstance() logic - class E(C): ... - class F(D): ... + def test_is_typeddict(self): + self.assertIs(is_typeddict(Point2D), True) + self.assertIs(is_typeddict(Point2Dor3D), True) + self.assertIs(is_typeddict(Union[str, int]), False) + # classes, not instances + self.assertIs(is_typeddict(Point2D()), False) + call_based = TypedDict('call_based', {'a': int}) + self.assertIs(is_typeddict(call_based), True) + self.assertIs(is_typeddict(call_based()), False) - class Empty: ... + T = TypeVar("T") + class BarGeneric(TypedDict, Generic[T]): + a: T + self.assertIs(is_typeddict(BarGeneric), True) + self.assertIs(is_typeddict(BarGeneric[int]), False) + self.assertIs(is_typeddict(BarGeneric()), False) - T = TypeVar('T') + if hasattr(typing, "TypeAliasType"): + ns = {"TypedDict": TypedDict} + exec("""if True: + class NewGeneric[T](TypedDict): + a: T + """, ns) + NewGeneric = ns["NewGeneric"] + self.assertIs(is_typeddict(NewGeneric), True) + self.assertIs(is_typeddict(NewGeneric[int]), False) + self.assertIs(is_typeddict(NewGeneric()), False) - @runtime_checkable - class P(Protocol): - @property - def attr(self): ... + # The TypedDict constructor is not itself a TypedDict + self.assertIs(is_typeddict(TypedDict), False) + if hasattr(typing, "TypedDict"): + self.assertIs(is_typeddict(typing.TypedDict), False) - @runtime_checkable - class P1(Protocol): - attr: int + def test_is_typeddict_against_typeddict_from_typing(self): + Point = typing.TypedDict('Point', {'x': int, 'y': int}) - @runtime_checkable - class PG(Protocol[T]): - @property - def attr(self): ... + class PointDict2D(typing.TypedDict): + x: int + y: int - @runtime_checkable - class PG1(Protocol[T]): - attr: T + class PointDict3D(PointDict2D, total=False): + z: int - @runtime_checkable - class MethodP(Protocol): - def attr(self): ... + assert is_typeddict(Point) is True + assert is_typeddict(PointDict2D) is True + assert is_typeddict(PointDict3D) is True - @runtime_checkable - class MethodPG(Protocol[T]): - def attr(self) -> T: ... + @skipUnless(HAS_FORWARD_MODULE, "ForwardRef.__forward_module__ was added in 3.9.7") + def test_get_type_hints_cross_module_subclass(self): + self.assertNotIn("_DoNotImport", globals()) + self.assertEqual( + {k: v.__name__ for k, v in get_type_hints(Bar).items()}, + {'a': "_DoNotImport", 'b': "int"} + ) - for protocol_class in P, P1, PG, PG1, MethodP, MethodPG: - for klass in C, D, E, F: - with self.subTest( - klass=klass.__name__, - protocol_class=protocol_class.__name__ - ): - self.assertIsInstance(klass(), protocol_class) + def test_get_type_hints_generic(self): + self.assertEqual( + get_type_hints(BarGeneric), + {'a': typing.Optional[T], 'b': int} + ) - with self.subTest(klass="Empty", protocol_class=protocol_class.__name__): - self.assertNotIsInstance(Empty(), protocol_class) + class FooBarGeneric(BarGeneric[int]): + c: str - class BadP(Protocol): - @property - def attr(self): ... + self.assertEqual( + get_type_hints(FooBarGeneric), + {'a': typing.Optional[T], 'b': int, 'c': str} + ) - class BadP1(Protocol): - attr: int + @skipUnless(TYPING_3_12_0, "PEP 695 required") + def test_pep695_generic_typeddict(self): + ns = {"TypedDict": TypedDict} + exec("""if True: + class A[T](TypedDict): + a: T + """, ns) + A = ns["A"] + T, = A.__type_params__ + self.assertIsInstance(T, TypeVar) + self.assertEqual(T.__name__, 'T') + self.assertEqual(A.__bases__, (Generic, dict)) + self.assertEqual(A.__orig_bases__, (TypedDict, Generic[T])) + self.assertEqual(A.__mro__, (A, Generic, dict, object)) + self.assertEqual(A.__parameters__, (T,)) + self.assertEqual(A[str].__parameters__, ()) + self.assertEqual(A[str].__args__, (str,)) - class BadPG(Protocol[T]): - @property - def attr(self): ... + def test_generic_inheritance(self): + class A(TypedDict, Generic[T]): + a: T - class BadPG1(Protocol[T]): - attr: T + self.assertEqual(A.__bases__, (Generic, dict)) + self.assertEqual(A.__orig_bases__, (TypedDict, Generic[T])) + self.assertEqual(A.__mro__, (A, Generic, dict, object)) + self.assertEqual(A.__parameters__, (T,)) + self.assertEqual(A[str].__parameters__, ()) + self.assertEqual(A[str].__args__, (str,)) - cases = ( - PG[T], PG[C], PG1[T], PG1[C], MethodPG[T], - MethodPG[C], BadP, BadP1, BadPG, BadPG1 - ) + class A2(Generic[T], TypedDict): + a: T - for obj in cases: - for klass in C, D, E, F, Empty: - with self.subTest(klass=klass.__name__, obj=obj): - with self.assertRaises(TypeError): - isinstance(klass(), obj) + self.assertEqual(A2.__bases__, (Generic, dict)) + self.assertEqual(A2.__orig_bases__, (Generic[T], TypedDict)) + self.assertEqual(A2.__mro__, (A2, Generic, dict, object)) + self.assertEqual(A2.__parameters__, (T,)) + self.assertEqual(A2[str].__parameters__, ()) + self.assertEqual(A2[str].__args__, (str,)) - def test_protocols_isinstance_not_fooled_by_custom_dir(self): - @runtime_checkable - class HasX(Protocol): - x: int + class B(A[KT], total=False): + b: KT - class CustomDirWithX: - x = 10 - def __dir__(self): - return [] + self.assertEqual(B.__bases__, (Generic, dict)) + self.assertEqual(B.__orig_bases__, (A[KT],)) + self.assertEqual(B.__mro__, (B, Generic, dict, object)) + self.assertEqual(B.__parameters__, (KT,)) + self.assertEqual(B.__total__, False) + self.assertEqual(B.__optional_keys__, frozenset(['b'])) + self.assertEqual(B.__required_keys__, frozenset(['a'])) - class CustomDirWithoutX: - def __dir__(self): - return ["x"] + self.assertEqual(B[str].__parameters__, ()) + self.assertEqual(B[str].__args__, (str,)) + self.assertEqual(B[str].__origin__, B) - self.assertIsInstance(CustomDirWithX(), HasX) - self.assertNotIsInstance(CustomDirWithoutX(), HasX) + class C(B[int]): + c: int - def test_protocols_isinstance_attribute_access_with_side_effects(self): - class C: - @property - def attr(self): - raise AttributeError('no') + self.assertEqual(C.__bases__, (Generic, dict)) + self.assertEqual(C.__orig_bases__, (B[int],)) + self.assertEqual(C.__mro__, (C, Generic, dict, object)) + self.assertEqual(C.__parameters__, ()) + self.assertEqual(C.__total__, True) + self.assertEqual(C.__optional_keys__, frozenset(['b'])) + self.assertEqual(C.__required_keys__, frozenset(['a', 'c'])) + assert C.__annotations__ == { + 'a': T, + 'b': KT, + 'c': int, + } + with self.assertRaises(TypeError): + C[str] - class CustomDescriptor: - def __get__(self, obj, objtype=None): - raise RuntimeError("NO") + class Point3D(Point2DGeneric[T], Generic[T, KT]): + c: KT - class D: - attr = CustomDescriptor() + self.assertEqual(Point3D.__bases__, (Generic, dict)) + self.assertEqual(Point3D.__orig_bases__, (Point2DGeneric[T], Generic[T, KT])) + self.assertEqual(Point3D.__mro__, (Point3D, Generic, dict, object)) + self.assertEqual(Point3D.__parameters__, (T, KT)) + self.assertEqual(Point3D.__total__, True) + self.assertEqual(Point3D.__optional_keys__, frozenset()) + self.assertEqual(Point3D.__required_keys__, frozenset(['a', 'b', 'c'])) + self.assertEqual(Point3D.__annotations__, { + 'a': T, + 'b': T, + 'c': KT, + }) + self.assertEqual(Point3D[int, str].__origin__, Point3D) - # Check that properties set on superclasses - # are still found by the isinstance() logic - class E(C): ... - class F(D): ... + with self.assertRaises(TypeError): + Point3D[int] - class WhyWouldYouDoThis: - def __getattr__(self, name): - raise RuntimeError("wut") + with self.assertRaises(TypeError): + class Point3D(Point2DGeneric[T], Generic[KT]): + c: KT - T = TypeVar('T') + def test_implicit_any_inheritance(self): + class A(TypedDict, Generic[T]): + a: T - @runtime_checkable - class P(Protocol): - @property - def attr(self): ... + class B(A[KT], total=False): + b: KT - @runtime_checkable - class P1(Protocol): - attr: int + class WithImplicitAny(B): + c: int - @runtime_checkable - class PG(Protocol[T]): - @property - def attr(self): ... + self.assertEqual(WithImplicitAny.__bases__, (Generic, dict,)) + self.assertEqual(WithImplicitAny.__mro__, (WithImplicitAny, Generic, dict, object)) + # Consistent with GenericTests.test_implicit_any + self.assertEqual(WithImplicitAny.__parameters__, ()) + self.assertEqual(WithImplicitAny.__total__, True) + self.assertEqual(WithImplicitAny.__optional_keys__, frozenset(['b'])) + self.assertEqual(WithImplicitAny.__required_keys__, frozenset(['a', 'c'])) + assert WithImplicitAny.__annotations__ == { + 'a': T, + 'b': KT, + 'c': int, + } + with self.assertRaises(TypeError): + WithImplicitAny[str] - @runtime_checkable - class PG1(Protocol[T]): - attr: T + def test_non_generic_subscript(self): + # For backward compatibility, subscription works + # on arbitrary TypedDict types. + class TD(TypedDict): + a: T + A = TD[int] + self.assertEqual(A.__origin__, TD) + self.assertEqual(A.__parameters__, ()) + self.assertEqual(A.__args__, (int,)) + a = A(a=1) + self.assertIs(type(a), dict) + self.assertEqual(a, {'a': 1}) - @runtime_checkable - class MethodP(Protocol): - def attr(self): ... + def test_orig_bases(self): + T = TypeVar('T') - @runtime_checkable - class MethodPG(Protocol[T]): - def attr(self) -> T: ... + class Parent(TypedDict): + pass - for protocol_class in P, P1, PG, PG1, MethodP, MethodPG: - for klass in C, D, E, F: - with self.subTest( - klass=klass.__name__, - protocol_class=protocol_class.__name__ - ): - self.assertIsInstance(klass(), protocol_class) + class Child(Parent): + pass - with self.subTest( - klass="WhyWouldYouDoThis", - protocol_class=protocol_class.__name__ - ): - self.assertNotIsInstance(WhyWouldYouDoThis(), protocol_class) + class OtherChild(Parent): + pass - def test_protocols_isinstance___slots__(self): - # As per the consensus in https://github.com/python/typing/issues/1367, - # this is desirable behaviour - @runtime_checkable - class HasX(Protocol): - x: int + class MixedChild(Child, OtherChild, Parent): + pass - class HasNothingButSlots: - __slots__ = ("x",) + class GenericParent(TypedDict, Generic[T]): + pass - self.assertIsInstance(HasNothingButSlots(), HasX) + class GenericChild(GenericParent[int]): + pass - def test_protocols_isinstance_py36(self): - class APoint: - def __init__(self, x, y, label): - self.x = x - self.y = y - self.label = label - class BPoint: - label = 'B' - def __init__(self, x, y): - self.x = x - self.y = y - class C: - def __init__(self, attr): - self.attr = attr - def meth(self, arg): - return 0 - class Bad: pass - self.assertIsInstance(APoint(1, 2, 'A'), Point) - self.assertIsInstance(BPoint(1, 2), Point) - self.assertNotIsInstance(MyPoint(), Point) - self.assertIsInstance(BPoint(1, 2), Position) - self.assertIsInstance(Other(), Proto) - self.assertIsInstance(Concrete(), Proto) - self.assertIsInstance(C(42), Proto) - self.assertNotIsInstance(Bad(), Proto) - self.assertNotIsInstance(Bad(), Point) - self.assertNotIsInstance(Bad(), Position) - self.assertNotIsInstance(Bad(), Concrete) - self.assertNotIsInstance(Other(), Concrete) - self.assertIsInstance(NT(1, 2), Position) - - def test_runtime_checkable_with_match_args(self): - @runtime_checkable - class P_regular(Protocol): - x: int - y: int + class OtherGenericChild(GenericParent[str]): + pass - @runtime_checkable - class P_match(Protocol): - __match_args__ = ("x", "y") - x: int - y: int + class MixedGenericChild(GenericChild, OtherGenericChild, GenericParent[float]): + pass - class Regular: - def __init__(self, x: int, y: int): - self.x = x - self.y = y + class MultipleGenericBases(GenericParent[int], GenericParent[float]): + pass - class WithMatch: - __match_args__ = ("x", "y", "z") - def __init__(self, x: int, y: int, z: int): - self.x = x - self.y = y - self.z = z + CallTypedDict = TypedDict('CallTypedDict', {}) - class Nope: ... + self.assertEqual(Parent.__orig_bases__, (TypedDict,)) + self.assertEqual(Child.__orig_bases__, (Parent,)) + self.assertEqual(OtherChild.__orig_bases__, (Parent,)) + self.assertEqual(MixedChild.__orig_bases__, (Child, OtherChild, Parent,)) + self.assertEqual(GenericParent.__orig_bases__, (TypedDict, Generic[T])) + self.assertEqual(GenericChild.__orig_bases__, (GenericParent[int],)) + self.assertEqual(OtherGenericChild.__orig_bases__, (GenericParent[str],)) + self.assertEqual(MixedGenericChild.__orig_bases__, (GenericChild, OtherGenericChild, GenericParent[float])) + self.assertEqual(MultipleGenericBases.__orig_bases__, (GenericParent[int], GenericParent[float])) + self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,)) - self.assertIsInstance(Regular(1, 2), P_regular) - self.assertIsInstance(Regular(1, 2), P_match) - self.assertIsInstance(WithMatch(1, 2, 3), P_regular) - self.assertIsInstance(WithMatch(1, 2, 3), P_match) - self.assertNotIsInstance(Nope(), P_regular) - self.assertNotIsInstance(Nope(), P_match) + def test_zero_fields_typeddicts(self): + T1 = TypedDict("T1", {}) + class T2(TypedDict): pass + try: + ns = {"TypedDict": TypedDict} + exec("class T3[tvar](TypedDict): pass", ns) + T3 = ns["T3"] + except SyntaxError: + class T3(TypedDict): pass + S = TypeVar("S") + class T4(TypedDict, Generic[S]): pass - def test_protocols_isinstance_init(self): - T = TypeVar('T') - @runtime_checkable - class P(Protocol): - x = 1 - @runtime_checkable - class PG(Protocol[T]): - x = 1 - class C: - def __init__(self, x): - self.x = x - self.assertIsInstance(C(1), P) - self.assertIsInstance(C(1), PG) + expected_warning = re.escape( + "Failing to pass a value for the 'fields' parameter is deprecated " + "and will be disallowed in Python 3.15. " + "To create a TypedDict class with 0 fields " + "using the functional syntax, " + "pass an empty dictionary, e.g. `T5 = TypedDict('T5', {})`." + ) + with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"): + T5 = TypedDict('T5') - def test_protocols_isinstance_monkeypatching(self): - @runtime_checkable - class HasX(Protocol): - x: int + expected_warning = re.escape( + "Passing `None` as the 'fields' parameter is deprecated " + "and will be disallowed in Python 3.15. " + "To create a TypedDict class with 0 fields " + "using the functional syntax, " + "pass an empty dictionary, e.g. `T6 = TypedDict('T6', {})`." + ) + with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"): + T6 = TypedDict('T6', None) - class Foo: ... + for klass in T1, T2, T3, T4, T5, T6: + with self.subTest(klass=klass.__name__): + self.assertEqual(klass.__annotations__, {}) + self.assertEqual(klass.__required_keys__, set()) + self.assertEqual(klass.__optional_keys__, set()) + self.assertIsInstance(klass(), dict) - f = Foo() - self.assertNotIsInstance(f, HasX) - f.x = 42 - self.assertIsInstance(f, HasX) - del f.x - self.assertNotIsInstance(f, HasX) + def test_readonly_inheritance(self): + class Base1(TypedDict): + a: ReadOnly[int] - @skip_if_py312b1 - def test_runtime_checkable_generic_non_protocol(self): - # Make sure this doesn't raise AttributeError - with self.assertRaisesRegex( - TypeError, - "@runtime_checkable can be only applied to protocol classes", - ): - @runtime_checkable - class Foo(Generic[T]): ... + class Child1(Base1): + b: str - def test_runtime_checkable_generic(self): - @runtime_checkable - class Foo(Protocol[T]): - def meth(self) -> T: ... + self.assertEqual(Child1.__readonly_keys__, frozenset({'a'})) + self.assertEqual(Child1.__mutable_keys__, frozenset({'b'})) - class Impl: - def meth(self) -> int: ... + class Base2(TypedDict): + a: int - self.assertIsSubclass(Impl, Foo) + class Child2(Base2): + b: ReadOnly[str] - class NotImpl: - def method(self) -> int: ... + self.assertEqual(Child2.__readonly_keys__, frozenset({'b'})) + self.assertEqual(Child2.__mutable_keys__, frozenset({'a'})) - self.assertNotIsSubclass(NotImpl, Foo) + def test_make_mutable_key_readonly(self): + class Base(TypedDict): + a: int - if sys.version_info >= (3, 12): - exec(textwrap.dedent( - """ - @skip_if_py312b1 - def test_pep695_generics_can_be_runtime_checkable(self): - @runtime_checkable - class HasX(Protocol): - x: int + self.assertEqual(Base.__readonly_keys__, frozenset()) + self.assertEqual(Base.__mutable_keys__, frozenset({'a'})) - class Bar[T]: - x: T - def __init__(self, x): - self.x = x + class Child(Base): + a: ReadOnly[int] # type checker error, but allowed at runtime - class Capybara[T]: - y: str - def __init__(self, y): - self.y = y + self.assertEqual(Child.__readonly_keys__, frozenset({'a'})) + self.assertEqual(Child.__mutable_keys__, frozenset()) - self.assertIsInstance(Bar(1), HasX) - self.assertNotIsInstance(Capybara('a'), HasX) - """ - )) + def test_can_make_readonly_key_mutable(self): + class Base(TypedDict): + a: ReadOnly[int] - @skip_if_py312b1 - def test_protocols_isinstance_generic_classes(self): - T = TypeVar("T") + class Child(Base): + a: int - class Foo(Generic[T]): - x: T + self.assertEqual(Child.__readonly_keys__, frozenset()) + self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) - def __init__(self, x): - self.x = x + def test_combine_qualifiers(self): + class AllTheThings(TypedDict): + a: Annotated[Required[ReadOnly[int]], "why not"] + b: Required[Annotated[ReadOnly[int], "why not"]] + c: ReadOnly[NotRequired[Annotated[int, "why not"]]] + d: NotRequired[Annotated[int, "why not"]] - class Bar(Foo[int]): - ... + self.assertEqual(AllTheThings.__required_keys__, frozenset({'a', 'b'})) + self.assertEqual(AllTheThings.__optional_keys__, frozenset({'c', 'd'})) + self.assertEqual(AllTheThings.__readonly_keys__, frozenset({'a', 'b', 'c'})) + self.assertEqual(AllTheThings.__mutable_keys__, frozenset({'d'})) - @runtime_checkable - class HasX(Protocol): - x: int + self.assertEqual( + get_type_hints(AllTheThings, include_extras=False), + {'a': int, 'b': int, 'c': int, 'd': int}, + ) + self.assertEqual( + get_type_hints(AllTheThings, include_extras=True), + { + 'a': Annotated[Required[ReadOnly[int]], 'why not'], + 'b': Required[Annotated[ReadOnly[int], 'why not']], + 'c': ReadOnly[NotRequired[Annotated[int, 'why not']]], + 'd': NotRequired[Annotated[int, 'why not']], + }, + ) - foo = Foo(1) - self.assertIsInstance(foo, HasX) + @skipIf(TYPING_3_14_0, "Old syntax only supported on <3.14") + def test_extra_keys_non_readonly_legacy(self): + class Base(TypedDict, closed=True): + __extra_items__: str - bar = Bar(2) - self.assertIsInstance(bar, HasX) + class Child(Base): + a: NotRequired[int] - def test_protocols_support_register(self): - @runtime_checkable - class P(Protocol): - x = 1 - class PM(Protocol): - def meth(self): pass - class D(PM): pass - class C: pass - D.register(C) - P.register(C) - self.assertIsInstance(C(), P) - self.assertIsInstance(C(), D) - - def test_none_on_non_callable_doesnt_block_implementation(self): - @runtime_checkable - class P(Protocol): - x = 1 - class A: - x = 1 - class B(A): - x = None - class C: - def __init__(self): - self.x = None - self.assertIsInstance(B(), P) - self.assertIsInstance(C(), P) - - def test_none_on_callable_blocks_implementation(self): - @runtime_checkable - class P(Protocol): - def x(self): ... - class A: - def x(self): ... - class B(A): - x = None - class C: - def __init__(self): - self.x = None - self.assertNotIsInstance(B(), P) - self.assertNotIsInstance(C(), P) + self.assertEqual(Child.__required_keys__, frozenset({})) + self.assertEqual(Child.__optional_keys__, frozenset({'a'})) + self.assertEqual(Child.__readonly_keys__, frozenset({})) + self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) - def test_non_protocol_subclasses(self): - class P(Protocol): - x = 1 - @runtime_checkable - class PR(Protocol): - def meth(self): pass - class NonP(P): - x = 1 - class NonPR(PR): pass - class C(metaclass=abc.ABCMeta): - x = 1 - class D(metaclass=abc.ABCMeta): - def meth(self): pass # noqa: B027 - self.assertNotIsInstance(C(), NonP) - self.assertNotIsInstance(D(), NonPR) - self.assertNotIsSubclass(C, NonP) - self.assertNotIsSubclass(D, NonPR) - self.assertIsInstance(NonPR(), PR) - self.assertIsSubclass(NonPR, PR) - - self.assertNotIn("__protocol_attrs__", vars(NonP)) - self.assertNotIn("__protocol_attrs__", vars(NonPR)) - self.assertNotIn("__non_callable_proto_members__", vars(NonP)) - self.assertNotIn("__non_callable_proto_members__", vars(NonPR)) - - acceptable_extra_attrs = { - '_is_protocol', '_is_runtime_protocol', '__parameters__', - '__init__', '__annotations__', '__subclasshook__', '__annotate__' - } - self.assertLessEqual(vars(NonP).keys(), vars(C).keys() | acceptable_extra_attrs) - self.assertLessEqual( - vars(NonPR).keys(), vars(D).keys() | acceptable_extra_attrs - ) + @skipIf(TYPING_3_14_0, "Only supported on <3.14") + def test_extra_keys_readonly_legacy(self): + class Base(TypedDict, closed=True): + __extra_items__: ReadOnly[str] - def test_custom_subclasshook(self): - class P(Protocol): - x = 1 - class OKClass: pass - class BadClass: - x = 1 - class C(P): - @classmethod - def __subclasshook__(cls, other): - return other.__name__.startswith("OK") - self.assertIsInstance(OKClass(), C) - self.assertNotIsInstance(BadClass(), C) - self.assertIsSubclass(OKClass, C) - self.assertNotIsSubclass(BadClass, C) + class Child(Base): + a: NotRequired[str] - @skipIf( - sys.version_info[:4] == (3, 12, 0, 'beta') and sys.version_info[4] < 4, - "Early betas of Python 3.12 had a bug" - ) - def test_custom_subclasshook_2(self): - @runtime_checkable - class HasX(Protocol): - # The presence of a non-callable member - # would mean issubclass() checks would fail with TypeError - # if it weren't for the custom `__subclasshook__` method - x = 1 + self.assertEqual(Child.__required_keys__, frozenset({})) + self.assertEqual(Child.__optional_keys__, frozenset({'a'})) + self.assertEqual(Child.__readonly_keys__, frozenset({})) + self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) - @classmethod - def __subclasshook__(cls, other): - return hasattr(other, 'x') + @skipIf(TYPING_3_14_0, "Only supported on <3.14") + def test_extra_keys_readonly_explicit_closed_legacy(self): + class Base(TypedDict, closed=True): + __extra_items__: ReadOnly[str] - class Empty: pass + class Child(Base, closed=True): + a: NotRequired[str] - class ImplementsHasX: - x = 1 + self.assertEqual(Child.__required_keys__, frozenset({})) + self.assertEqual(Child.__optional_keys__, frozenset({'a'})) + self.assertEqual(Child.__readonly_keys__, frozenset({})) + self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) - self.assertIsInstance(ImplementsHasX(), HasX) - self.assertNotIsInstance(Empty(), HasX) - self.assertIsSubclass(ImplementsHasX, HasX) - self.assertNotIsSubclass(Empty, HasX) + @skipIf(TYPING_3_14_0, "Only supported on <3.14") + def test_extra_key_required_legacy(self): + with self.assertRaisesRegex( + TypeError, + "Special key __extra_items__ does not support Required" + ): + TypedDict("A", {"__extra_items__": Required[int]}, closed=True) - # isinstance() and issubclass() checks against this still raise TypeError, - # despite the presence of the custom __subclasshook__ method, - # as it's not decorated with @runtime_checkable - class NotRuntimeCheckable(Protocol): - @classmethod - def __subclasshook__(cls, other): - return hasattr(other, 'x') + with self.assertRaisesRegex( + TypeError, + "Special key __extra_items__ does not support NotRequired" + ): + TypedDict("A", {"__extra_items__": NotRequired[int]}, closed=True) - must_be_runtime_checkable = ( - "Instance and class checks can only be used " - "with @runtime_checkable protocols" - ) + def test_regular_extra_items_legacy(self): + class ExtraReadOnly(TypedDict): + __extra_items__: ReadOnly[str] - with self.assertRaisesRegex(TypeError, must_be_runtime_checkable): - issubclass(object, NotRuntimeCheckable) - with self.assertRaisesRegex(TypeError, must_be_runtime_checkable): - isinstance(object(), NotRuntimeCheckable) + self.assertEqual(ExtraReadOnly.__required_keys__, frozenset({'__extra_items__'})) + self.assertEqual(ExtraReadOnly.__optional_keys__, frozenset({})) + self.assertEqual(ExtraReadOnly.__readonly_keys__, frozenset({'__extra_items__'})) + self.assertEqual(ExtraReadOnly.__mutable_keys__, frozenset({})) + self.assertIs(ExtraReadOnly.__extra_items__, NoExtraItems) + self.assertIsNone(ExtraReadOnly.__closed__) - @skip_if_py312b1 - def test_issubclass_fails_correctly(self): - @runtime_checkable - class NonCallableMembers(Protocol): - x = 1 + class ExtraRequired(TypedDict): + __extra_items__: Required[str] - class NotRuntimeCheckable(Protocol): - def callable_member(self) -> int: ... + self.assertEqual(ExtraRequired.__required_keys__, frozenset({'__extra_items__'})) + self.assertEqual(ExtraRequired.__optional_keys__, frozenset({})) + self.assertEqual(ExtraRequired.__readonly_keys__, frozenset({})) + self.assertEqual(ExtraRequired.__mutable_keys__, frozenset({'__extra_items__'})) + self.assertIs(ExtraRequired.__extra_items__, NoExtraItems) + self.assertIsNone(ExtraRequired.__closed__) - @runtime_checkable - class RuntimeCheckable(Protocol): - def callable_member(self) -> int: ... + class ExtraNotRequired(TypedDict): + __extra_items__: NotRequired[str] - class C: pass + self.assertEqual(ExtraNotRequired.__required_keys__, frozenset({})) + self.assertEqual(ExtraNotRequired.__optional_keys__, frozenset({'__extra_items__'})) + self.assertEqual(ExtraNotRequired.__readonly_keys__, frozenset({})) + self.assertEqual(ExtraNotRequired.__mutable_keys__, frozenset({'__extra_items__'})) + self.assertIs(ExtraNotRequired.__extra_items__, NoExtraItems) + self.assertIsNone(ExtraNotRequired.__closed__) - # These three all exercise different code paths, - # but should result in the same error message: - for protocol in NonCallableMembers, NotRuntimeCheckable, RuntimeCheckable: - with self.subTest(proto_name=protocol.__name__): - with self.assertRaisesRegex( - TypeError, r"issubclass\(\) arg 1 must be a class" - ): - issubclass(C(), protocol) + @skipIf(TYPING_3_14_0, "Only supported on <3.14") + def test_closed_inheritance_legacy(self): + class Base(TypedDict, closed=True): + __extra_items__: ReadOnly[Union[str, None]] - def test_defining_generic_protocols(self): - T = TypeVar('T') - S = TypeVar('S') - @runtime_checkable - class PR(Protocol[T, S]): - def meth(self): pass - class P(PR[int, T], Protocol[T]): - y = 1 - with self.assertRaises(TypeError): - issubclass(PR[int, T], PR) - with self.assertRaises(TypeError): - issubclass(P[str], PR) - with self.assertRaises(TypeError): - PR[int] - with self.assertRaises(TypeError): - P[int, str] - if not TYPING_3_10_0: - with self.assertRaises(TypeError): - PR[int, 1] - with self.assertRaises(TypeError): - PR[int, ClassVar] - class C(PR[int, T]): pass - self.assertIsInstance(C[str](), C) + self.assertEqual(Base.__required_keys__, frozenset({})) + self.assertEqual(Base.__optional_keys__, frozenset({})) + self.assertEqual(Base.__readonly_keys__, frozenset({})) + self.assertEqual(Base.__mutable_keys__, frozenset({})) + self.assertEqual(Base.__annotations__, {}) + self.assertEqual(Base.__extra_items__, ReadOnly[Union[str, None]]) + self.assertIs(Base.__closed__, True) - def test_defining_generic_protocols_old_style(self): - T = TypeVar('T') - S = TypeVar('S') - @runtime_checkable - class PR(Protocol, Generic[T, S]): - def meth(self): pass - class P(PR[int, str], Protocol): - y = 1 - with self.assertRaises(TypeError): - self.assertIsSubclass(PR[int, str], PR) - self.assertIsSubclass(P, PR) - with self.assertRaises(TypeError): - PR[int] - if not TYPING_3_10_0: - with self.assertRaises(TypeError): - PR[int, 1] - class P1(Protocol, Generic[T]): - def bar(self, x: T) -> str: ... - class P2(Generic[T], Protocol): - def bar(self, x: T) -> str: ... - @runtime_checkable - class PSub(P1[str], Protocol): - x = 1 - class Test: - x = 1 - def bar(self, x: str) -> str: - return x - self.assertIsInstance(Test(), PSub) - if not TYPING_3_10_0: - with self.assertRaises(TypeError): - PR[int, ClassVar] + class Child(Base, closed=True): + a: int + __extra_items__: int - if hasattr(typing, "TypeAliasType"): - exec(textwrap.dedent( - """ - def test_pep695_generic_protocol_callable_members(self): - @runtime_checkable - class Foo[T](Protocol): - def meth(self, x: T) -> None: ... + self.assertEqual(Child.__required_keys__, frozenset({'a'})) + self.assertEqual(Child.__optional_keys__, frozenset({})) + self.assertEqual(Child.__readonly_keys__, frozenset({})) + self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) + self.assertEqual(Child.__annotations__, {"a": int}) + self.assertIs(Child.__extra_items__, int) + self.assertIs(Child.__closed__, True) - class Bar[T]: - def meth(self, x: T) -> None: ... + class GrandChild(Child, closed=True): + __extra_items__: str - self.assertIsInstance(Bar(), Foo) - self.assertIsSubclass(Bar, Foo) + self.assertEqual(GrandChild.__required_keys__, frozenset({'a'})) + self.assertEqual(GrandChild.__optional_keys__, frozenset({})) + self.assertEqual(GrandChild.__readonly_keys__, frozenset({})) + self.assertEqual(GrandChild.__mutable_keys__, frozenset({'a'})) + self.assertEqual(GrandChild.__annotations__, {"a": int}) + self.assertIs(GrandChild.__extra_items__, str) + self.assertIs(GrandChild.__closed__, True) - @runtime_checkable - class SupportsTrunc[T](Protocol): - def __trunc__(self) -> T: ... + def test_closed_inheritance(self): + class Base(TypedDict, extra_items=ReadOnly[Union[str, None]]): + a: int - self.assertIsInstance(0.0, SupportsTrunc) - self.assertIsSubclass(float, SupportsTrunc) + self.assertEqual(Base.__required_keys__, frozenset({"a"})) + self.assertEqual(Base.__optional_keys__, frozenset({})) + self.assertEqual(Base.__readonly_keys__, frozenset({})) + self.assertEqual(Base.__mutable_keys__, frozenset({"a"})) + self.assertEqual(Base.__annotations__, {"a": int}) + self.assertEqual(Base.__extra_items__, ReadOnly[Union[str, None]]) + self.assertIsNone(Base.__closed__) - def test_no_weird_caching_with_issubclass_after_isinstance_pep695(self): - @runtime_checkable - class Spam[T](Protocol): - x: T + class Child(Base, extra_items=int): + a: str - class Eggs[T]: - def __init__(self, x: T) -> None: - self.x = x + self.assertEqual(Child.__required_keys__, frozenset({'a'})) + self.assertEqual(Child.__optional_keys__, frozenset({})) + self.assertEqual(Child.__readonly_keys__, frozenset({})) + self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) + self.assertEqual(Child.__annotations__, {"a": str}) + self.assertIs(Child.__extra_items__, int) + self.assertIsNone(Child.__closed__) - self.assertIsInstance(Eggs(42), Spam) + class GrandChild(Child, closed=True): + a: float - # gh-104555: If we didn't override ABCMeta.__subclasscheck__ in _ProtocolMeta, - # TypeError wouldn't be raised here, - # as the cached result of the isinstance() check immediately above - # would mean the issubclass() call would short-circuit - # before we got to the "raise TypeError" line - with self.assertRaises(TypeError): - issubclass(Eggs, Spam) - """ - )) + self.assertEqual(GrandChild.__required_keys__, frozenset({'a'})) + self.assertEqual(GrandChild.__optional_keys__, frozenset({})) + self.assertEqual(GrandChild.__readonly_keys__, frozenset({})) + self.assertEqual(GrandChild.__mutable_keys__, frozenset({'a'})) + self.assertEqual(GrandChild.__annotations__, {"a": float}) + self.assertIs(GrandChild.__extra_items__, NoExtraItems) + self.assertIs(GrandChild.__closed__, True) - def test_init_called(self): - T = TypeVar('T') - class P(Protocol[T]): pass - class C(P[T]): - def __init__(self): - self.test = 'OK' - self.assertEqual(C[int]().test, 'OK') + class GrandGrandChild(GrandChild): + ... + self.assertEqual(GrandGrandChild.__required_keys__, frozenset({'a'})) + self.assertEqual(GrandGrandChild.__optional_keys__, frozenset({})) + self.assertEqual(GrandGrandChild.__readonly_keys__, frozenset({})) + self.assertEqual(GrandGrandChild.__mutable_keys__, frozenset({'a'})) + self.assertEqual(GrandGrandChild.__annotations__, {"a": float}) + self.assertIs(GrandGrandChild.__extra_items__, NoExtraItems) + self.assertIsNone(GrandGrandChild.__closed__) - def test_protocols_bad_subscripts(self): - T = TypeVar('T') - S = TypeVar('S') - with self.assertRaises(TypeError): - class P(Protocol[T, T]): pass - with self.assertRaises(TypeError): - class P2(Protocol[int]): pass - with self.assertRaises(TypeError): - class P3(Protocol[T], Protocol[S]): pass - with self.assertRaises(TypeError): - class P4(typing.Mapping[T, S], Protocol[T]): pass + def test_implicit_extra_items(self): + class Base(TypedDict): + a: int - def test_generic_protocols_repr(self): - T = TypeVar('T') - S = TypeVar('S') - class P(Protocol[T, S]): pass - self.assertTrue(repr(P[T, S]).endswith('P[~T, ~S]')) - self.assertTrue(repr(P[int, str]).endswith('P[int, str]')) + self.assertIs(Base.__extra_items__, NoExtraItems) + self.assertIsNone(Base.__closed__) - def test_generic_protocols_eq(self): - T = TypeVar('T') - S = TypeVar('S') - class P(Protocol[T, S]): pass - self.assertEqual(P, P) - self.assertEqual(P[int, T], P[int, T]) - self.assertEqual(P[T, T][Tuple[T, S]][int, str], - P[Tuple[int, str], Tuple[int, str]]) + class ChildA(Base, closed=True): + ... - def test_generic_protocols_special_from_generic(self): - T = TypeVar('T') - class P(Protocol[T]): pass - self.assertEqual(P.__parameters__, (T,)) - self.assertEqual(P[int].__parameters__, ()) - self.assertEqual(P[int].__args__, (int,)) - self.assertIs(P[int].__origin__, P) - - def test_generic_protocols_special_from_protocol(self): - @runtime_checkable - class PR(Protocol): - x = 1 - class P(Protocol): - def meth(self): - pass - T = TypeVar('T') - class PG(Protocol[T]): - x = 1 - def meth(self): - pass - self.assertTrue(P._is_protocol) - self.assertTrue(PR._is_protocol) - self.assertTrue(PG._is_protocol) - self.assertFalse(P._is_runtime_protocol) - self.assertTrue(PR._is_runtime_protocol) - self.assertTrue(PG[int]._is_protocol) - self.assertEqual(typing_extensions._get_protocol_attrs(P), {'meth'}) - self.assertEqual(typing_extensions._get_protocol_attrs(PR), {'x'}) - self.assertEqual(frozenset(typing_extensions._get_protocol_attrs(PG)), - frozenset({'x', 'meth'})) - - def test_no_runtime_deco_on_nominal(self): - with self.assertRaises(TypeError): - @runtime_checkable - class C: pass - class Proto(Protocol): - x = 1 - with self.assertRaises(TypeError): - @runtime_checkable - class Concrete(Proto): - pass + self.assertEqual(ChildA.__extra_items__, NoExtraItems) + self.assertIs(ChildA.__closed__, True) - def test_none_treated_correctly(self): - @runtime_checkable - class P(Protocol): - x: int = None - class B: pass - self.assertNotIsInstance(B(), P) - class C: - x = 1 - class D: - x = None - self.assertIsInstance(C(), P) - self.assertIsInstance(D(), P) - class CI: - def __init__(self): - self.x = 1 - class DI: - def __init__(self): - self.x = None - self.assertIsInstance(CI(), P) - self.assertIsInstance(DI(), P) - - def test_protocols_in_unions(self): - class P(Protocol): - x: int = None - Alias = typing.Union[typing.Iterable, P] - Alias2 = typing.Union[P, typing.Iterable] - self.assertEqual(Alias, Alias2) - - def test_protocols_pickleable(self): - global P, CP # pickle wants to reference the class by name - T = TypeVar('T') - - @runtime_checkable - class P(Protocol[T]): - x = 1 - class CP(P[int]): - pass + @skipIf(TYPING_3_14_0, "Backwards compatibility only for Python 3.13") + def test_implicit_extra_items_before_3_14(self): + class Base(TypedDict): + a: int + class ChildB(Base, closed=True): + __extra_items__: None - c = CP() - c.foo = 42 - c.bar = 'abc' - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(c, proto) - x = pickle.loads(z) - self.assertEqual(x.foo, 42) - self.assertEqual(x.bar, 'abc') - self.assertEqual(x.x, 1) - self.assertEqual(x.__dict__, {'foo': 42, 'bar': 'abc'}) - s = pickle.dumps(P) - D = pickle.loads(s) - class E: - x = 1 - self.assertIsInstance(E(), D) - - def test_collections_protocols_allowed(self): - @runtime_checkable - class Custom(collections.abc.Iterable, Protocol): - def close(self): pass - - class A: ... - class B: - def __iter__(self): - return [] - def close(self): - return 0 - - self.assertIsSubclass(B, Custom) - self.assertNotIsSubclass(A, Custom) + self.assertIs(ChildB.__extra_items__, type(None)) + self.assertIs(ChildB.__closed__, True) - @skipUnless( - hasattr(collections.abc, "Buffer"), - "needs collections.abc.Buffer to exist" + @skipIf( + TYPING_3_13_0, + "The keyword argument alternative to define a " + "TypedDict type using the functional syntax is no longer supported" ) - @skip_if_py312b1 - def test_collections_abc_buffer_protocol_allowed(self): - @runtime_checkable - class ReleasableBuffer(collections.abc.Buffer, Protocol): - def __release_buffer__(self, mv: memoryview) -> None: ... - - class C: pass - class D: - def __buffer__(self, flags: int) -> memoryview: - return memoryview(b'') - def __release_buffer__(self, mv: memoryview) -> None: - pass + def test_backwards_compatibility(self): + with self.assertWarns(DeprecationWarning): + TD = TypedDict("TD", closed=int) + self.assertIs(TD.__closed__, None) + self.assertEqual(TD.__annotations__, {"closed": int}) - self.assertIsSubclass(D, ReleasableBuffer) - self.assertIsInstance(D(), ReleasableBuffer) - self.assertNotIsSubclass(C, ReleasableBuffer) - self.assertNotIsInstance(C(), ReleasableBuffer) + with self.assertWarns(DeprecationWarning): + TD = TypedDict("TD", extra_items=int) + self.assertIs(TD.__extra_items__, NoExtraItems) + self.assertEqual(TD.__annotations__, {"extra_items": int}) - def test_builtin_protocol_allowlist(self): - with self.assertRaises(TypeError): - class CustomProtocol(TestCase, Protocol): - pass + def test_cannot_combine_closed_and_extra_items(self): + with self.assertRaisesRegex( + TypeError, + "Cannot combine closed=True and extra_items" + ): + class TD(TypedDict, closed=True, extra_items=range): + x: str - class CustomContextManager(typing.ContextManager, Protocol): - pass + def test_typed_dict_signature(self): + self.assertListEqual( + list(inspect.signature(TypedDict).parameters), + ['typename', 'fields', 'total', 'closed', 'extra_items', 'kwargs'] + ) - @skip_if_py312b1 - def test_typing_extensions_protocol_allowlist(self): - @runtime_checkable - class ReleasableBuffer(Buffer, Protocol): - def __release_buffer__(self, mv: memoryview) -> None: ... + def test_inline_too_many_arguments(self): + with self.assertRaises(TypeError): + TypedDict[{"a": int}, "extra"] - class C: pass - class D: - def __buffer__(self, flags: int) -> memoryview: - return memoryview(b'') - def __release_buffer__(self, mv: memoryview) -> None: - pass + def test_inline_not_a_dict(self): + with self.assertRaises(TypeError): + TypedDict["not_a_dict"] - self.assertIsSubclass(D, ReleasableBuffer) - self.assertIsInstance(D(), ReleasableBuffer) - self.assertNotIsSubclass(C, ReleasableBuffer) - self.assertNotIsInstance(C(), ReleasableBuffer) + # a tuple of elements isn't allowed, even if the first element is a dict: + with self.assertRaises(TypeError): + TypedDict[({"key": int},)] - def test_non_runtime_protocol_isinstance_check(self): - class P(Protocol): - x: int + def test_inline_empty(self): + TD = TypedDict[{}] + self.assertIs(TD.__total__, True) + self.assertIs(TD.__closed__, True) + self.assertEqual(TD.__extra_items__, NoExtraItems) + self.assertEqual(TD.__required_keys__, set()) + self.assertEqual(TD.__optional_keys__, set()) + self.assertEqual(TD.__readonly_keys__, set()) + self.assertEqual(TD.__mutable_keys__, set()) - with self.assertRaisesRegex(TypeError, "@runtime_checkable"): - isinstance(1, P) + def test_inline(self): + TD = TypedDict[{ + "a": int, + "b": Required[int], + "c": NotRequired[int], + "d": ReadOnly[int], + }] + self.assertIsSubclass(TD, dict) + self.assertIsSubclass(TD, typing.MutableMapping) + self.assertNotIsSubclass(TD, collections.abc.Sequence) + self.assertTrue(is_typeddict(TD)) + self.assertEqual(TD.__name__, "") + self.assertEqual( + TD.__annotations__, + {"a": int, "b": Required[int], "c": NotRequired[int], "d": ReadOnly[int]}, + ) + self.assertEqual(TD.__module__, __name__) + self.assertEqual(TD.__bases__, (dict,)) + self.assertIs(TD.__total__, True) + self.assertIs(TD.__closed__, True) + self.assertEqual(TD.__extra_items__, NoExtraItems) + self.assertEqual(TD.__required_keys__, {"a", "b", "d"}) + self.assertEqual(TD.__optional_keys__, {"c"}) + self.assertEqual(TD.__readonly_keys__, {"d"}) + self.assertEqual(TD.__mutable_keys__, {"a", "b", "c"}) - def test_no_init_same_for_different_protocol_implementations(self): - class CustomProtocolWithoutInitA(Protocol): - pass + inst = TD(a=1, b=2, d=3) + self.assertIs(type(inst), dict) + self.assertEqual(inst["a"], 1) - class CustomProtocolWithoutInitB(Protocol): - pass + def test_annotations(self): + # _type_check is applied + with self.assertRaisesRegex(TypeError, "Plain typing.Optional is not valid as type argument"): + class X(TypedDict): + a: Optional - self.assertEqual(CustomProtocolWithoutInitA.__init__, CustomProtocolWithoutInitB.__init__) + # _type_convert is applied + class Y(TypedDict): + a: None + b: "int" + if sys.version_info >= (3, 14): + import annotationlib - def test_protocol_generic_over_paramspec(self): - P = ParamSpec("P") - T = TypeVar("T") - T2 = TypeVar("T2") + fwdref = EqualToForwardRef('int', module=__name__) + self.assertEqual(Y.__annotations__, {'a': type(None), 'b': fwdref}) + self.assertEqual(Y.__annotate__(annotationlib.Format.FORWARDREF), {'a': type(None), 'b': fwdref}) + else: + self.assertEqual(Y.__annotations__, {'a': type(None), 'b': typing.ForwardRef('int', module=__name__)}) - class MemoizedFunc(Protocol[P, T, T2]): - cache: typing.Dict[T2, T] - def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: ... + @skipUnless(TYPING_3_14_0, "Only supported on 3.14") + def test_delayed_type_check(self): + # _type_check is also applied later + class Z(TypedDict): + a: undefined # noqa: F821 - self.assertEqual(MemoizedFunc.__parameters__, (P, T, T2)) - self.assertTrue(MemoizedFunc._is_protocol) + with self.assertRaises(NameError): + Z.__annotations__ - with self.assertRaises(TypeError): - MemoizedFunc[[int, str, str]] + undefined = Final + with self.assertRaisesRegex(TypeError, "Plain typing.Final is not valid as type argument"): + Z.__annotations__ - if sys.version_info >= (3, 10): - # These unfortunately don't pass on 3.9, - # due to typing._type_check on older Python versions - X = MemoizedFunc[[int, str, str], T, T2] - self.assertEqual(X.__parameters__, (T, T2)) - self.assertEqual(X.__args__, ((int, str, str), T, T2)) + undefined = None # noqa: F841 + self.assertEqual(Z.__annotations__, {'a': type(None)}) - Y = X[bytes, memoryview] - self.assertEqual(Y.__parameters__, ()) - self.assertEqual(Y.__args__, ((int, str, str), bytes, memoryview)) + @skipUnless(TYPING_3_14_0, "Only supported on 3.14") + def test_deferred_evaluation(self): + class A(TypedDict): + x: NotRequired[undefined] # noqa: F821 + y: ReadOnly[undefined] # noqa: F821 + z: Required[undefined] # noqa: F821 - # Regression test; fixing #126 might cause an error here - with self.assertRaisesRegex(TypeError, "not a generic class"): - Y[int] + self.assertEqual(A.__required_keys__, frozenset({'y', 'z'})) + self.assertEqual(A.__optional_keys__, frozenset({'x'})) + self.assertEqual(A.__readonly_keys__, frozenset({'y'})) + self.assertEqual(A.__mutable_keys__, frozenset({'x', 'z'})) - def test_protocol_generic_over_typevartuple(self): - Ts = TypeVarTuple("Ts") - T = TypeVar("T") - T2 = TypeVar("T2") + with self.assertRaises(NameError): + A.__annotations__ - class MemoizedFunc(Protocol[Unpack[Ts], T, T2]): - cache: typing.Dict[T2, T] - def __call__(self, *args: Unpack[Ts]) -> T: ... + import annotationlib + self.assertEqual( + A.__annotate__(annotationlib.Format.STRING), + {'x': 'NotRequired[undefined]', 'y': 'ReadOnly[undefined]', + 'z': 'Required[undefined]'}, + ) - self.assertEqual(MemoizedFunc.__parameters__, (Ts, T, T2)) - self.assertTrue(MemoizedFunc._is_protocol) + def test_dunder_dict(self): + self.assertIsInstance(TypedDict.__dict__, dict) - things = "arguments" if sys.version_info >= (3, 10) else "parameters" +class AnnotatedTests(BaseTestCase): - # A bug was fixed in 3.11.1 - # (https://github.com/python/cpython/commit/74920aa27d0c57443dd7f704d6272cca9c507ab3) - # That means this assertion doesn't pass on 3.11.0, - # but it passes on all other Python versions - if sys.version_info[:3] != (3, 11, 0): - with self.assertRaisesRegex(TypeError, f"Too few {things}"): - MemoizedFunc[int] - - X = MemoizedFunc[int, T, T2] - self.assertEqual(X.__parameters__, (T, T2)) - self.assertEqual(X.__args__, (int, T, T2)) - - Y = X[bytes, memoryview] - self.assertEqual(Y.__parameters__, ()) - self.assertEqual(Y.__args__, (int, bytes, memoryview)) - - def test_get_protocol_members(self): - with self.assertRaisesRegex(TypeError, "not a Protocol"): - get_protocol_members(object) - with self.assertRaisesRegex(TypeError, "not a Protocol"): - get_protocol_members(object()) - with self.assertRaisesRegex(TypeError, "not a Protocol"): - get_protocol_members(Protocol) - with self.assertRaisesRegex(TypeError, "not a Protocol"): - get_protocol_members(Generic) - - class P(Protocol): - a: int - def b(self) -> str: ... - @property - def c(self) -> int: ... + def test_repr(self): + if hasattr(typing, 'Annotated'): + mod_name = 'typing' + else: + mod_name = "typing_extensions" + self.assertEqual( + repr(Annotated[int, 4, 5]), + mod_name + ".Annotated[int, 4, 5]" + ) + self.assertEqual( + repr(Annotated[List[int], 4, 5]), + mod_name + ".Annotated[typing.List[int], 4, 5]" + ) - self.assertEqual(get_protocol_members(P), {'a', 'b', 'c'}) - self.assertIsInstance(get_protocol_members(P), frozenset) - self.assertIsNot(get_protocol_members(P), P.__protocol_attrs__) + def test_flatten(self): + A = Annotated[Annotated[int, 4], 5] + self.assertEqual(A, Annotated[int, 4, 5]) + self.assertEqual(A.__metadata__, (4, 5)) + self.assertEqual(A.__origin__, int) - class Concrete: - a: int - def b(self) -> str: return "capybara" - @property - def c(self) -> int: return 5 + def test_specialize(self): + L = Annotated[List[T], "my decoration"] + LI = Annotated[List[int], "my decoration"] + self.assertEqual(L[int], Annotated[List[int], "my decoration"]) + self.assertEqual(L[int].__metadata__, ("my decoration",)) + self.assertEqual(L[int].__origin__, List[int]) + with self.assertRaises(TypeError): + LI[int] + with self.assertRaises(TypeError): + L[int, float] - with self.assertRaisesRegex(TypeError, "not a Protocol"): - get_protocol_members(Concrete) - with self.assertRaisesRegex(TypeError, "not a Protocol"): - get_protocol_members(Concrete()) + def test_hash_eq(self): + self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1) + self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4]) + self.assertNotEqual(Annotated[int, 4, 5], Annotated[str, 4, 5]) + self.assertNotEqual(Annotated[int, 4], Annotated[int, 4, 4]) + self.assertEqual( + {Annotated[int, 4, 5], Annotated[int, 4, 5], Annotated[T, 4, 5]}, + {Annotated[int, 4, 5], Annotated[T, 4, 5]} + ) - class ConcreteInherit(P): - a: int = 42 - def b(self) -> str: return "capybara" - @property - def c(self) -> int: return 5 + def test_instantiate(self): + class C: + classvar = 4 - with self.assertRaisesRegex(TypeError, "not a Protocol"): - get_protocol_members(ConcreteInherit) - with self.assertRaisesRegex(TypeError, "not a Protocol"): - get_protocol_members(ConcreteInherit()) + def __init__(self, x): + self.x = x - def test_get_protocol_members_typing(self): - with self.assertRaisesRegex(TypeError, "not a Protocol"): - get_protocol_members(typing.Protocol) + def __eq__(self, other): + if not isinstance(other, C): + return NotImplemented + return other.x == self.x - class P(typing.Protocol): - a: int - def b(self) -> str: ... - @property - def c(self) -> int: ... + A = Annotated[C, "a decoration"] + a = A(5) + c = C(5) + self.assertEqual(a, c) + self.assertEqual(a.x, c.x) + self.assertEqual(a.classvar, c.classvar) - self.assertEqual(get_protocol_members(P), {'a', 'b', 'c'}) - self.assertIsInstance(get_protocol_members(P), frozenset) - if hasattr(P, "__protocol_attrs__"): - self.assertIsNot(get_protocol_members(P), P.__protocol_attrs__) + def test_instantiate_generic(self): + MyCount = Annotated[typing_extensions.Counter[T], "my decoration"] + self.assertEqual(MyCount([4, 4, 5]), {4: 2, 5: 1}) + self.assertEqual(MyCount[int]([4, 4, 5]), {4: 2, 5: 1}) - class Concrete: - a: int - def b(self) -> str: return "capybara" - @property - def c(self) -> int: return 5 + def test_cannot_instantiate_forward(self): + A = Annotated["int", (5, 6)] + with self.assertRaises(TypeError): + A(5) - with self.assertRaisesRegex(TypeError, "not a Protocol"): - get_protocol_members(Concrete) - with self.assertRaisesRegex(TypeError, "not a Protocol"): - get_protocol_members(Concrete()) + def test_cannot_instantiate_type_var(self): + A = Annotated[T, (5, 6)] + with self.assertRaises(TypeError): + A(5) - class ConcreteInherit(P): - a: int = 42 - def b(self) -> str: return "capybara" - @property - def c(self) -> int: return 5 + def test_cannot_getattr_typevar(self): + with self.assertRaises(AttributeError): + Annotated[T, (5, 7)].x - with self.assertRaisesRegex(TypeError, "not a Protocol"): - get_protocol_members(ConcreteInherit) - with self.assertRaisesRegex(TypeError, "not a Protocol"): - get_protocol_members(ConcreteInherit()) + def test_attr_passthrough(self): + class C: + classvar = 4 - def test_is_protocol(self): - self.assertTrue(is_protocol(Proto)) - self.assertTrue(is_protocol(Point)) - self.assertFalse(is_protocol(Concrete)) - self.assertFalse(is_protocol(Concrete())) - self.assertFalse(is_protocol(Generic)) - self.assertFalse(is_protocol(object)) + A = Annotated[C, "a decoration"] + self.assertEqual(A.classvar, 4) + A.x = 5 + self.assertEqual(C.x, 5) - # Protocol is not itself a protocol - self.assertFalse(is_protocol(Protocol)) + @skipIf(sys.version_info[:2] == (3, 10), "Waiting for https://github.com/python/cpython/issues/90649 bugfix.") + def test_special_form_containment(self): + class C: + classvar: Annotated[ClassVar[int], "a decoration"] = 4 + const: Annotated[Final[int], "Const"] = 4 - def test_is_protocol_with_typing(self): - self.assertFalse(is_protocol(typing.Protocol)) + self.assertEqual(get_type_hints(C, globals())["classvar"], ClassVar[int]) + self.assertEqual(get_type_hints(C, globals())["const"], Final[int]) - class TypingProto(typing.Protocol): - a: int + def test_cannot_subclass(self): + with self.assertRaisesRegex(TypeError, "Cannot subclass .*Annotated"): + class C(Annotated): + pass - self.assertTrue(is_protocol(TypingProto)) + def test_cannot_check_instance(self): + with self.assertRaises(TypeError): + isinstance(5, Annotated[int, "positive"]) - class Concrete(TypingProto): - a: int + def test_cannot_check_subclass(self): + with self.assertRaises(TypeError): + issubclass(int, Annotated[int, "positive"]) - self.assertFalse(is_protocol(Concrete)) + def test_pickle(self): + samples = [typing.Any, typing.Union[int, str], + typing.Optional[str], Tuple[int, ...], + typing.Callable[[str], bytes], + Self, LiteralString, Never] - @skip_if_py312b1 - def test_interaction_with_isinstance_checks_on_superclasses_with_ABCMeta(self): - # Ensure the cache is empty, or this test won't work correctly - collections.abc.Sized._abc_registry_clear() + for t in samples: + x = Annotated[t, "a"] - class Foo(collections.abc.Sized, Protocol): pass + for prot in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=prot, type=t): + pickled = pickle.dumps(x, prot) + restored = pickle.loads(pickled) + self.assertEqual(x, restored) - # CPython gh-105144: this previously raised TypeError - # if a Protocol subclass of Sized had been created - # before any isinstance() checks against Sized - self.assertNotIsInstance(1, collections.abc.Sized) + global _Annotated_test_G - @skip_if_py312b1 - def test_interaction_with_isinstance_checks_on_superclasses_with_ABCMeta_2(self): - # Ensure the cache is empty, or this test won't work correctly - collections.abc.Sized._abc_registry_clear() + class _Annotated_test_G(Generic[T]): + x = 1 - class Foo(typing.Sized, Protocol): pass + G = Annotated[_Annotated_test_G[int], "A decoration"] + G.foo = 42 + G.bar = 'abc' - # CPython gh-105144: this previously raised TypeError - # if a Protocol subclass of Sized had been created - # before any isinstance() checks against Sized - self.assertNotIsInstance(1, typing.Sized) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + z = pickle.dumps(G, proto) + x = pickle.loads(z) + self.assertEqual(x.foo, 42) + self.assertEqual(x.bar, 'abc') + self.assertEqual(x.x, 1) - def test_empty_protocol_decorated_with_final(self): - @final - @runtime_checkable - class EmptyProtocol(Protocol): ... + def test_subst(self): + dec = "a decoration" + dec2 = "another decoration" - self.assertIsSubclass(object, EmptyProtocol) - self.assertIsInstance(object(), EmptyProtocol) + S = Annotated[T, dec2] + self.assertEqual(S[int], Annotated[int, dec2]) - def test_protocol_decorated_with_final_callable_members(self): - @final - @runtime_checkable - class ProtocolWithMethod(Protocol): - def startswith(self, string: str) -> bool: ... + self.assertEqual(S[Annotated[int, dec]], Annotated[int, dec, dec2]) + L = Annotated[List[T], dec] - self.assertIsSubclass(str, ProtocolWithMethod) - self.assertNotIsSubclass(int, ProtocolWithMethod) - self.assertIsInstance('foo', ProtocolWithMethod) - self.assertNotIsInstance(42, ProtocolWithMethod) + self.assertEqual(L[int], Annotated[List[int], dec]) + with self.assertRaises(TypeError): + L[int, int] - def test_protocol_decorated_with_final_noncallable_members(self): - @final - @runtime_checkable - class ProtocolWithNonCallableMember(Protocol): - x: int + self.assertEqual(S[L[int]], Annotated[List[int], dec, dec2]) - class Foo: - x = 42 + D = Annotated[Dict[KT, VT], dec] + self.assertEqual(D[str, int], Annotated[Dict[str, int], dec]) + with self.assertRaises(TypeError): + D[int] - only_callable_members_please = ( - r"Protocols with non-method members don't support issubclass()" - ) + It = Annotated[int, dec] + with self.assertRaises(TypeError): + It[None] - with self.assertRaisesRegex(TypeError, only_callable_members_please): - issubclass(Foo, ProtocolWithNonCallableMember) + LI = L[int] + with self.assertRaises(TypeError): + LI[None] - with self.assertRaisesRegex(TypeError, only_callable_members_please): - issubclass(int, ProtocolWithNonCallableMember) + def test_annotated_in_other_types(self): + X = List[Annotated[T, 5]] + self.assertEqual(X[int], List[Annotated[int, 5]]) - self.assertIsInstance(Foo(), ProtocolWithNonCallableMember) - self.assertNotIsInstance(42, ProtocolWithNonCallableMember) + def test_nested_annotated_with_unhashable_metadata(self): + X = Annotated[ + List[Annotated[str, {"unhashable_metadata"}]], + "metadata" + ] + self.assertEqual(X.__origin__, List[Annotated[str, {"unhashable_metadata"}]]) + self.assertEqual(X.__metadata__, ("metadata",)) - def test_protocol_decorated_with_final_mixed_members(self): - @final - @runtime_checkable - class ProtocolWithMixedMembers(Protocol): - x: int - def method(self) -> None: ... + def test_compatibility(self): + # Test that the _AnnotatedAlias compatibility alias works + self.assertTrue(hasattr(typing_extensions, "_AnnotatedAlias")) + self.assertIs(typing_extensions._AnnotatedAlias, typing._AnnotatedAlias) - class Foo: - x = 42 - def method(self) -> None: ... - only_callable_members_please = ( - r"Protocols with non-method members don't support issubclass()" +class GetTypeHintsTests(BaseTestCase): + def test_get_type_hints(self): + def foobar(x: List['X']): ... + X = Annotated[int, (1, 10)] + self.assertEqual( + get_type_hints(foobar, globals(), locals()), + {'x': List[int]} + ) + self.assertEqual( + get_type_hints(foobar, globals(), locals(), include_extras=True), + {'x': List[Annotated[int, (1, 10)]]} + ) + BA = Tuple[Annotated[T, (1, 0)], ...] + def barfoo(x: BA): ... + self.assertEqual(get_type_hints(barfoo, globals(), locals())['x'], Tuple[T, ...]) + self.assertIs( + get_type_hints(barfoo, globals(), locals(), include_extras=True)['x'], + BA + ) + def barfoo2(x: typing.Callable[..., Annotated[List[T], "const"]], + y: typing.Union[int, Annotated[T, "mutable"]]): ... + self.assertEqual( + get_type_hints(barfoo2, globals(), locals()), + {'x': typing.Callable[..., List[T]], 'y': typing.Union[int, T]} + ) + BA2 = typing.Callable[..., List[T]] + def barfoo3(x: BA2): ... + self.assertIs( + get_type_hints(barfoo3, globals(), locals(), include_extras=True)["x"], + BA2 ) - with self.assertRaisesRegex(TypeError, only_callable_members_please): - issubclass(Foo, ProtocolWithMixedMembers) + def test_get_type_hints_refs(self): - with self.assertRaisesRegex(TypeError, only_callable_members_please): - issubclass(int, ProtocolWithMixedMembers) + Const = Annotated[T, "Const"] - self.assertIsInstance(Foo(), ProtocolWithMixedMembers) - self.assertNotIsInstance(42, ProtocolWithMixedMembers) + class MySet(Generic[T]): - def test_protocol_issubclass_error_message(self): - @runtime_checkable - class Vec2D(Protocol): - x: float - y: float + def __ior__(self, other: "Const[MySet[T]]") -> "MySet[T]": + ... - def square_norm(self) -> float: - return self.x ** 2 + self.y ** 2 + def __iand__(self, other: Const["MySet[T]"]) -> "MySet[T]": + ... - self.assertEqual(Vec2D.__protocol_attrs__, {'x', 'y', 'square_norm'}) - expected_error_message = ( - "Protocols with non-method members don't support issubclass()." - " Non-method members: 'x', 'y'." + self.assertEqual( + get_type_hints(MySet.__iand__, globals(), locals()), + {'other': MySet[T], 'return': MySet[T]} ) - with self.assertRaisesRegex(TypeError, re.escape(expected_error_message)): - issubclass(int, Vec2D) - - def test_nonruntime_protocol_interaction_with_evil_classproperty(self): - class classproperty: - def __get__(self, instance, type): - raise RuntimeError("NO") - class Commentable(Protocol): - evil = classproperty() + self.assertEqual( + get_type_hints(MySet.__iand__, globals(), locals(), include_extras=True), + {'other': Const[MySet[T]], 'return': MySet[T]} + ) - # recognised as a protocol attr, - # but not actually accessed by the protocol metaclass - # (which would raise RuntimeError) for non-runtime protocols. - # See gh-113320 - self.assertEqual(get_protocol_members(Commentable), {"evil"}) + self.assertEqual( + get_type_hints(MySet.__ior__, globals(), locals()), + {'other': MySet[T], 'return': MySet[T]} + ) - def test_runtime_protocol_interaction_with_evil_classproperty(self): - class CustomError(Exception): pass + def test_get_type_hints_typeddict(self): + assert get_type_hints(TotalMovie) == {'title': str, 'year': int} + assert get_type_hints(TotalMovie, include_extras=True) == { + 'title': str, + 'year': NotRequired[int], + } - class classproperty: - def __get__(self, instance, type): - raise CustomError + assert get_type_hints(AnnotatedMovie) == {'title': str, 'year': int} + assert get_type_hints(AnnotatedMovie, include_extras=True) == { + 'title': Annotated[Required[str], "foobar"], + 'year': NotRequired[Annotated[int, 2000]], + } - with self.assertRaises(TypeError) as cm: - @runtime_checkable - class Commentable(Protocol): - evil = classproperty() + def test_orig_bases(self): + T = TypeVar('T') - exc = cm.exception - self.assertEqual( - exc.args[0], - "Failed to determine whether protocol member 'evil' is a method member" - ) - self.assertIs(type(exc.__cause__), CustomError) + class Parent(TypedDict): + pass - def test_extensions_runtimecheckable_on_typing_Protocol(self): - @runtime_checkable - class Functor(typing.Protocol): - def foo(self) -> None: ... + class Child(Parent): + pass - self.assertNotIsSubclass(object, Functor) + class OtherChild(Parent): + pass - class Bar: - def foo(self): pass + class MixedChild(Child, OtherChild, Parent): + pass - self.assertIsSubclass(Bar, Functor) + class GenericParent(TypedDict, Generic[T]): + pass + class GenericChild(GenericParent[int]): + pass -class SpecificProtocolTests(BaseTestCase): - def test_reader_runtime_checkable(self): - class MyReader: - def read(self, n: int) -> bytes: - return b"" + class OtherGenericChild(GenericParent[str]): + pass - class WrongReader: - def readx(self, n: int) -> bytes: - return b"" + class MixedGenericChild(GenericChild, OtherGenericChild, GenericParent[float]): + pass - self.assertIsInstance(MyReader(), typing_extensions.Reader) - self.assertNotIsInstance(WrongReader(), typing_extensions.Reader) + class MultipleGenericBases(GenericParent[int], GenericParent[float]): + pass - def test_writer_runtime_checkable(self): - class MyWriter: - def write(self, b: bytes) -> int: - return 0 + CallTypedDict = TypedDict('CallTypedDict', {}) - class WrongWriter: - def writex(self, b: bytes) -> int: - return 0 + self.assertEqual(Parent.__orig_bases__, (TypedDict,)) + self.assertEqual(Child.__orig_bases__, (Parent,)) + self.assertEqual(OtherChild.__orig_bases__, (Parent,)) + self.assertEqual(MixedChild.__orig_bases__, (Child, OtherChild, Parent,)) + self.assertEqual(GenericParent.__orig_bases__, (TypedDict, Generic[T])) + self.assertEqual(GenericChild.__orig_bases__, (GenericParent[int],)) + self.assertEqual(OtherGenericChild.__orig_bases__, (GenericParent[str],)) + self.assertEqual(MixedGenericChild.__orig_bases__, (GenericChild, OtherGenericChild, GenericParent[float])) + self.assertEqual(MultipleGenericBases.__orig_bases__, (GenericParent[int], GenericParent[float])) + self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,)) - self.assertIsInstance(MyWriter(), typing_extensions.Writer) - self.assertNotIsInstance(WrongWriter(), typing_extensions.Writer) +class TypeAliasTests(BaseTestCase): + def test_canonical_usage_with_variable_annotation(self): + ns = {} + exec('Alias: TypeAlias = Employee', globals(), ns) -class Point2DGeneric(Generic[T], TypedDict): - a: T - b: T + def test_canonical_usage_with_type_comment(self): + Alias: TypeAlias = Employee # noqa: F841 + def test_cannot_instantiate(self): + with self.assertRaises(TypeError): + TypeAlias() -class Bar(Foo): - b: int + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(42, TypeAlias) + def test_no_issubclass(self): + with self.assertRaises(TypeError): + issubclass(Employee, TypeAlias) -class BarGeneric(FooGeneric[T], total=False): - b: int + with self.assertRaises(TypeError): + issubclass(TypeAlias, Employee) + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(TypeAlias): + pass -class TypedDictTests(BaseTestCase): - def test_basics_functional_syntax(self): - Emp = TypedDict('Emp', {'name': str, 'id': int}) - self.assertIsSubclass(Emp, dict) - self.assertIsSubclass(Emp, typing.MutableMapping) - self.assertNotIsSubclass(Emp, collections.abc.Sequence) - jim = Emp(name='Jim', id=1) - self.assertIs(type(jim), dict) - self.assertEqual(jim['name'], 'Jim') - self.assertEqual(jim['id'], 1) - self.assertEqual(Emp.__name__, 'Emp') - self.assertEqual(Emp.__module__, __name__) - self.assertEqual(Emp.__bases__, (dict,)) - self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) - self.assertEqual(Emp.__total__, True) + with self.assertRaises(TypeError): + class D(type(TypeAlias)): + pass - def test_allowed_as_type_argument(self): - # https://github.com/python/typing_extensions/issues/613 - obj = typing.Type[typing_extensions.TypedDict] - self.assertIs(typing_extensions.get_origin(obj), type) - self.assertEqual(typing_extensions.get_args(obj), (typing_extensions.TypedDict,)) + def test_repr(self): + if hasattr(typing, 'TypeAlias'): + self.assertEqual(repr(TypeAlias), 'typing.TypeAlias') + else: + self.assertEqual(repr(TypeAlias), 'typing_extensions.TypeAlias') - @skipIf(sys.version_info < (3, 13), "Change in behavior in 3.13") - def test_keywords_syntax_raises_on_3_13(self): - with self.assertRaises(TypeError), self.assertWarns(DeprecationWarning): - TypedDict('Emp', name=str, id=int) + def test_cannot_subscript(self): + with self.assertRaises(TypeError): + TypeAlias[int] - @skipIf(sys.version_info >= (3, 13), "3.13 removes support for kwargs") - def test_basics_keywords_syntax(self): - with self.assertWarns(DeprecationWarning): - Emp = TypedDict('Emp', name=str, id=int) - self.assertIsSubclass(Emp, dict) - self.assertIsSubclass(Emp, typing.MutableMapping) - self.assertNotIsSubclass(Emp, collections.abc.Sequence) - jim = Emp(name='Jim', id=1) - self.assertIs(type(jim), dict) - self.assertEqual(jim['name'], 'Jim') - self.assertEqual(jim['id'], 1) - self.assertEqual(Emp.__name__, 'Emp') - self.assertEqual(Emp.__module__, __name__) - self.assertEqual(Emp.__bases__, (dict,)) - self.assertEqual(Emp.__annotations__, {'name': str, 'id': int}) - self.assertEqual(Emp.__total__, True) +class ParamSpecTests(BaseTestCase): - @skipIf(sys.version_info >= (3, 13), "3.13 removes support for kwargs") - def test_typeddict_special_keyword_names(self): - with self.assertWarns(DeprecationWarning): - TD = TypedDict("TD", cls=type, self=object, typename=str, _typename=int, - fields=list, _fields=dict, - closed=bool, extra_items=bool) - self.assertEqual(TD.__name__, 'TD') - self.assertEqual(TD.__annotations__, {'cls': type, 'self': object, 'typename': str, - '_typename': int, 'fields': list, '_fields': dict, - 'closed': bool, 'extra_items': bool}) - self.assertIsNone(TD.__closed__) - self.assertIs(TD.__extra_items__, NoExtraItems) - a = TD(cls=str, self=42, typename='foo', _typename=53, - fields=[('bar', tuple)], _fields={'baz', set}, - closed=None, extra_items="tea pot") - self.assertEqual(a['cls'], str) - self.assertEqual(a['self'], 42) - self.assertEqual(a['typename'], 'foo') - self.assertEqual(a['_typename'], 53) - self.assertEqual(a['fields'], [('bar', tuple)]) - self.assertEqual(a['_fields'], {'baz', set}) - self.assertIsNone(a['closed']) - self.assertEqual(a['extra_items'], "tea pot") - - def test_typeddict_create_errors(self): - with self.assertRaises(TypeError): - TypedDict.__new__() - with self.assertRaises(TypeError): - TypedDict() - with self.assertRaises(TypeError): - TypedDict('Emp', [('name', str)], None) + def test_basic_plain(self): + P = ParamSpec('P') + self.assertEqual(P, P) + self.assertIsInstance(P, ParamSpec) + self.assertEqual(P.__name__, 'P') + # Should be hashable + hash(P) - def test_typeddict_errors(self): - Emp = TypedDict('Emp', {'name': str, 'id': int}) - self.assertEqual(TypedDict.__module__, 'typing_extensions') - jim = Emp(name='Jim', id=1) - with self.assertRaises(TypeError): - isinstance({}, Emp) - with self.assertRaises(TypeError): - isinstance(jim, Emp) - with self.assertRaises(TypeError): - issubclass(dict, Emp) + def test_repr(self): + P = ParamSpec('P') + P_co = ParamSpec('P_co', covariant=True) + P_contra = ParamSpec('P_contra', contravariant=True) + P_infer = ParamSpec('P_infer', infer_variance=True) + P_2 = ParamSpec('P_2') + self.assertEqual(repr(P), '~P') + self.assertEqual(repr(P_2), '~P_2') - if not TYPING_3_11_0: - with self.assertRaises(TypeError), self.assertWarns(DeprecationWarning): - TypedDict('Hi', x=1) - with self.assertRaises(TypeError): - TypedDict('Hi', [('x', int), ('y', 1)]) - with self.assertRaises(TypeError): - TypedDict('Hi', [('x', int)], y=int) + # Note: PEP 612 doesn't require these to be repr-ed correctly, but + # just follow CPython. + self.assertEqual(repr(P_co), '+P_co') + self.assertEqual(repr(P_contra), '-P_contra') + # On other versions we use typing.ParamSpec, but it is not aware of + # infer_variance=. Not worth creating our own version of ParamSpec + # for this. + if hasattr(typing, 'TypeAliasType') or not hasattr(typing, 'ParamSpec'): + self.assertEqual(repr(P_infer), 'P_infer') + else: + self.assertEqual(repr(P_infer), '~P_infer') - def test_py36_class_syntax_usage(self): - self.assertEqual(LabelPoint2D.__name__, 'LabelPoint2D') - self.assertEqual(LabelPoint2D.__module__, __name__) - self.assertEqual(LabelPoint2D.__annotations__, {'x': int, 'y': int, 'label': str}) - self.assertEqual(LabelPoint2D.__bases__, (dict,)) - self.assertEqual(LabelPoint2D.__total__, True) - self.assertNotIsSubclass(LabelPoint2D, typing.Sequence) - not_origin = Point2D(x=0, y=1) - self.assertEqual(not_origin['x'], 0) - self.assertEqual(not_origin['y'], 1) - other = LabelPoint2D(x=0, y=1, label='hi') - self.assertEqual(other['label'], 'hi') + def test_variance(self): + P_co = ParamSpec('P_co', covariant=True) + P_contra = ParamSpec('P_contra', contravariant=True) + P_infer = ParamSpec('P_infer', infer_variance=True) - def test_pickle(self): - global EmpD # pickle wants to reference the class by name - EmpD = TypedDict('EmpD', {'name': str, 'id': int}) - jane = EmpD({'name': 'jane', 'id': 37}) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(jane, proto) - jane2 = pickle.loads(z) - self.assertEqual(jane2, jane) - self.assertEqual(jane2, {'name': 'jane', 'id': 37}) - ZZ = pickle.dumps(EmpD, proto) - EmpDnew = pickle.loads(ZZ) - self.assertEqual(EmpDnew({'name': 'jane', 'id': 37}), jane) + self.assertIs(P_co.__covariant__, True) + self.assertIs(P_co.__contravariant__, False) + self.assertIs(P_co.__infer_variance__, False) - def test_pickle_generic(self): - point = Point2DGeneric(a=5.0, b=3.0) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(point, proto) - point2 = pickle.loads(z) - self.assertEqual(point2, point) - self.assertEqual(point2, {'a': 5.0, 'b': 3.0}) - ZZ = pickle.dumps(Point2DGeneric, proto) - Point2DGenericNew = pickle.loads(ZZ) - self.assertEqual(Point2DGenericNew({'a': 5.0, 'b': 3.0}), point) + self.assertIs(P_contra.__covariant__, False) + self.assertIs(P_contra.__contravariant__, True) + self.assertIs(P_contra.__infer_variance__, False) - def test_optional(self): - EmpD = TypedDict('EmpD', {'name': str, 'id': int}) + self.assertIs(P_infer.__covariant__, False) + self.assertIs(P_infer.__contravariant__, False) + self.assertIs(P_infer.__infer_variance__, True) - self.assertEqual(typing.Optional[EmpD], typing.Union[None, EmpD]) - self.assertNotEqual(typing.List[EmpD], typing.Tuple[EmpD]) + def test_valid_uses(self): + P = ParamSpec('P') + T = TypeVar('T') + C1 = typing.Callable[P, int] + self.assertEqual(C1.__args__, (P, int)) + self.assertEqual(C1.__parameters__, (P,)) + C2 = typing.Callable[P, T] + self.assertEqual(C2.__args__, (P, T)) + self.assertEqual(C2.__parameters__, (P, T)) - def test_total(self): - D = TypedDict('D', {'x': int}, total=False) - self.assertEqual(D(), {}) - self.assertEqual(D(x=1), {'x': 1}) - self.assertEqual(D.__total__, False) - self.assertEqual(D.__required_keys__, frozenset()) - self.assertEqual(D.__optional_keys__, {'x'}) + # Test collections.abc.Callable too. + # Note: no tests for Callable.__parameters__ here + # because types.GenericAlias Callable is hardcoded to search + # for tp_name "TypeVar" in C. This was changed in 3.10. + C3 = collections.abc.Callable[P, int] + self.assertEqual(C3.__args__, (P, int)) + C4 = collections.abc.Callable[P, T] + self.assertEqual(C4.__args__, (P, T)) - self.assertEqual(Options(), {}) - self.assertEqual(Options(log_level=2), {'log_level': 2}) - self.assertEqual(Options.__total__, False) - self.assertEqual(Options.__required_keys__, frozenset()) - self.assertEqual(Options.__optional_keys__, {'log_level', 'log_path'}) + # ParamSpec instances should also have args and kwargs attributes. + # Note: not in dir(P) because of __class__ hacks + self.assertTrue(hasattr(P, 'args')) + self.assertTrue(hasattr(P, 'kwargs')) - def test_total_inherits_non_total(self): - class TD1(TypedDict, total=False): - a: int + @skipIf((3, 10, 0) <= sys.version_info[:3] <= (3, 10, 2), "Needs https://github.com/python/cpython/issues/90834.") + def test_args_kwargs(self): + P = ParamSpec('P') + P_2 = ParamSpec('P_2') + # Note: not in dir(P) because of __class__ hacks + self.assertTrue(hasattr(P, 'args')) + self.assertTrue(hasattr(P, 'kwargs')) + self.assertIsInstance(P.args, ParamSpecArgs) + self.assertIsInstance(P.kwargs, ParamSpecKwargs) + self.assertIs(P.args.__origin__, P) + self.assertIs(P.kwargs.__origin__, P) + self.assertEqual(P.args, P.args) + self.assertEqual(P.kwargs, P.kwargs) + self.assertNotEqual(P.args, P_2.args) + self.assertNotEqual(P.kwargs, P_2.kwargs) + self.assertNotEqual(P.args, P.kwargs) + self.assertNotEqual(P.kwargs, P.args) + self.assertNotEqual(P.args, P_2.kwargs) + self.assertEqual(repr(P.args), "P.args") + self.assertEqual(repr(P.kwargs), "P.kwargs") - self.assertIs(TD1.__total__, False) + def test_user_generics(self): + T = TypeVar("T") + P = ParamSpec("P") + P_2 = ParamSpec("P_2") - class TD2(TD1): - b: str + class X(Generic[T, P]): + pass - self.assertIs(TD2.__total__, True) + class Y(Protocol[T, P]): + pass - def test_total_with_assigned_value(self): - class TD(TypedDict): - __total__ = "some_value" + things = "arguments" if sys.version_info >= (3, 10) else "parameters" + for klass in X, Y: + with self.subTest(klass=klass.__name__): + G1 = klass[int, P_2] + self.assertEqual(G1.__args__, (int, P_2)) + self.assertEqual(G1.__parameters__, (P_2,)) - self.assertIs(TD.__total__, True) + G2 = klass[int, Concatenate[int, P_2]] + self.assertEqual(G2.__args__, (int, Concatenate[int, P_2])) + self.assertEqual(G2.__parameters__, (P_2,)) - class TD2(TypedDict, total=True): - __total__ = "some_value" + G3 = klass[int, Concatenate[int, ...]] + self.assertEqual(G3.__args__, (int, Concatenate[int, ...])) + self.assertEqual(G3.__parameters__, ()) - self.assertIs(TD2.__total__, True) + with self.assertRaisesRegex( + TypeError, + f"Too few {things} for {klass}" + ): + klass[int] - class TD3(TypedDict, total=False): - __total__ = "some value" + # The following are some valid uses cases in PEP 612 that don't work: + # These do not work in 3.9, _type_check blocks the list and ellipsis. + # G3 = X[int, [int, bool]] + # G4 = X[int, ...] + # G5 = Z[[int, str, bool]] - self.assertIs(TD3.__total__, False) + def test_single_argument_generic(self): + P = ParamSpec("P") + T = TypeVar("T") + P_2 = ParamSpec("P_2") - TD4 = TypedDict('TD4', {'__total__': "some_value"}) # noqa: F821 - self.assertIs(TD4.__total__, True) + class Z(Generic[P]): + pass + class ProtoZ(Protocol[P]): + pass - def test_optional_keys(self): - class Point2Dor3D(Point2D, total=False): - z: int + for klass in Z, ProtoZ: + with self.subTest(klass=klass.__name__): + # Note: For 3.10+ __args__ are nested tuples here ((int, ),) instead of (int, ) + G6 = klass[int, str, T] + G6args = G6.__args__[0] if sys.version_info >= (3, 10) else G6.__args__ + self.assertEqual(G6args, (int, str, T)) + self.assertEqual(G6.__parameters__, (T,)) - assert Point2Dor3D.__required_keys__ == frozenset(['x', 'y']) - assert Point2Dor3D.__optional_keys__ == frozenset(['z']) + # P = [int] + G7 = klass[int] + G7args = G7.__args__[0] if sys.version_info >= (3, 10) else G7.__args__ + self.assertEqual(G7args, (int,)) + self.assertEqual(G7.__parameters__, ()) - def test_keys_inheritance(self): - class BaseAnimal(TypedDict): - name: str + G8 = klass[Concatenate[T, ...]] + self.assertEqual(G8.__args__, (Concatenate[T, ...], )) + self.assertEqual(G8.__parameters__, (T,)) - class Animal(BaseAnimal, total=False): - voice: str - tail: bool + G9 = klass[Concatenate[T, P_2]] + self.assertEqual(G9.__args__, (Concatenate[T, P_2], )) - class Cat(Animal): - fur_color: str + # This is an invalid form but useful for testing correct subsitution + G10 = klass[int, Concatenate[str, P]] + G10args = G10.__args__[0] if sys.version_info >= (3, 10) else G10.__args__ + self.assertEqual(G10args, (int, Concatenate[str, P], )) - assert BaseAnimal.__required_keys__ == frozenset(['name']) - assert BaseAnimal.__optional_keys__ == frozenset([]) - assert BaseAnimal.__annotations__ == {'name': str} + @skipUnless(TYPING_3_10_0, "ParamSpec not present before 3.10") + def test_is_param_expr(self): + P = ParamSpec("P") + P_typing = typing.ParamSpec("P_typing") + self.assertTrue(typing_extensions._is_param_expr(P)) + self.assertTrue(typing_extensions._is_param_expr(P_typing)) + if hasattr(typing, "_is_param_expr"): + self.assertTrue(typing._is_param_expr(P)) + self.assertTrue(typing._is_param_expr(P_typing)) - assert Animal.__required_keys__ == frozenset(['name']) - assert Animal.__optional_keys__ == frozenset(['tail', 'voice']) - assert Animal.__annotations__ == { - 'name': str, - 'tail': bool, - 'voice': str, - } + def test_single_argument_generic_with_parameter_expressions(self): + P = ParamSpec("P") + T = TypeVar("T") + P_2 = ParamSpec("P_2") - assert Cat.__required_keys__ == frozenset(['name', 'fur_color']) - assert Cat.__optional_keys__ == frozenset(['tail', 'voice']) - assert Cat.__annotations__ == { - 'fur_color': str, - 'name': str, - 'tail': bool, - 'voice': str, - } + class Z(Generic[P]): + pass - @skipIf(sys.version_info == (3, 14, 0, "beta", 1), "Broken on beta 1, fixed in beta 2") - def test_inheritance_pep563(self): - def _make_td(future, class_name, annos, base, extra_names=None): - lines = [] - if future: - lines.append('from __future__ import annotations') - lines.append('from typing import TypedDict') - lines.append(f'class {class_name}({base}):') - for name, anno in annos.items(): - lines.append(f' {name}: {anno}') - code = '\n'.join(lines) - ns = {**extra_names} if extra_names else {} - exec(code, ns) - return ns[class_name] + class ProtoZ(Protocol[P]): + pass - for base_future in (True, False): - for child_future in (True, False): - with self.subTest(base_future=base_future, child_future=child_future): - base = _make_td( - base_future, "Base", {"base": "int"}, "TypedDict" - ) - if sys.version_info >= (3, 14): - self.assertIsNotNone(base.__annotate__) - child = _make_td( - child_future, "Child", {"child": "int"}, "Base", {"Base": base} - ) - base_anno = typing.ForwardRef("int", module="builtins") if base_future else int - child_anno = typing.ForwardRef("int", module="builtins") if child_future else int - self.assertEqual(base.__annotations__, {'base': base_anno}) - self.assertEqual( - child.__annotations__, {'child': child_anno, 'base': base_anno} - ) + things = "arguments" if sys.version_info >= (3, 10) else "parameters" + for klass in Z, ProtoZ: + with self.subTest(klass=klass.__name__): + G8 = klass[Concatenate[T, ...]] - def test_required_notrequired_keys(self): - self.assertEqual(NontotalMovie.__required_keys__, - frozenset({"title"})) - self.assertEqual(NontotalMovie.__optional_keys__, - frozenset({"year"})) + H8_1 = G8[int] + self.assertEqual(H8_1.__parameters__, ()) + with self.assertRaisesRegex(TypeError, "not a generic class"): + H8_1[str] - self.assertEqual(TotalMovie.__required_keys__, - frozenset({"title"})) - self.assertEqual(TotalMovie.__optional_keys__, - frozenset({"year"})) + H8_2 = G8[T][int] + self.assertEqual(H8_2.__parameters__, ()) + with self.assertRaisesRegex(TypeError, "not a generic class"): + H8_2[str] - self.assertEqual(VeryAnnotated.__required_keys__, - frozenset()) - self.assertEqual(VeryAnnotated.__optional_keys__, - frozenset({"a"})) + G9 = klass[Concatenate[T, P_2]] + self.assertEqual(G9.__parameters__, (T, P_2)) - self.assertEqual(AnnotatedMovie.__required_keys__, - frozenset({"title"})) - self.assertEqual(AnnotatedMovie.__optional_keys__, - frozenset({"year"})) + with self.assertRaisesRegex(TypeError, + "The last parameter to Concatenate should be a ParamSpec variable or ellipsis." + if sys.version_info < (3, 10) else + # from __typing_subst__ + "Expected a list of types, an ellipsis, ParamSpec, or Concatenate" + ): + G9[int, int] - self.assertEqual(WeirdlyQuotedMovie.__required_keys__, - frozenset({"title"})) - self.assertEqual(WeirdlyQuotedMovie.__optional_keys__, - frozenset({"year"})) + with self.assertRaisesRegex(TypeError, f"Too few {things}"): + G9[int] - self.assertEqual(ChildTotalMovie.__required_keys__, - frozenset({"title"})) - self.assertEqual(ChildTotalMovie.__optional_keys__, - frozenset({"year"})) + with self.subTest("Check list as parameter expression", klass=klass.__name__): + if sys.version_info < (3, 10): + self.skipTest("Cannot pass non-types") + G5 = klass[[int, str, T]] + self.assertEqual(G5.__parameters__, (T,)) + self.assertEqual(G5.__args__, ((int, str, T),)) - self.assertEqual(ChildDeeplyAnnotatedMovie.__required_keys__, - frozenset({"title"})) - self.assertEqual(ChildDeeplyAnnotatedMovie.__optional_keys__, - frozenset({"year"})) + H9 = G9[int, [T]] + self.assertEqual(H9.__parameters__, (T,)) - def test_multiple_inheritance(self): - class One(TypedDict): - one: int - class Two(TypedDict): - two: str - class Untotal(TypedDict, total=False): - untotal: str - Inline = TypedDict('Inline', {'inline': bool}) - class Regular: + # This is an invalid parameter expression but useful for testing correct subsitution + G10 = klass[int, Concatenate[str, P]] + with self.subTest("Check invalid form substitution"): + self.assertEqual(G10.__parameters__, (P, )) + H10 = G10[int] + if (3, 10) <= sys.version_info < (3, 11, 3): + self.skipTest("3.10-3.11.2 does not substitute Concatenate here") + self.assertEqual(H10.__parameters__, ()) + H10args = H10.__args__[0] if sys.version_info >= (3, 10) else H10.__args__ + self.assertEqual(H10args, (int, (str, int))) + + @skipUnless(TYPING_3_10_0, "ParamSpec not present before 3.10") + def test_substitution_with_typing_variants(self): + # verifies substitution and typing._check_generic working with typing variants + P = ParamSpec("P") + typing_P = typing.ParamSpec("typing_P") + typing_Concatenate = typing.Concatenate[int, P] + + class Z(Generic[typing_P]): pass - class Child(One, Two): - child: bool - self.assertEqual( - Child.__required_keys__, - frozenset(['one', 'two', 'child']), - ) - self.assertEqual( - Child.__optional_keys__, - frozenset([]), - ) - self.assertEqual( - Child.__annotations__, - {'one': int, 'two': str, 'child': bool}, - ) + P1 = Z[typing_P] + self.assertEqual(P1.__parameters__, (typing_P,)) + self.assertEqual(P1.__args__, (typing_P,)) - class ChildWithOptional(One, Untotal): - child: bool - self.assertEqual( - ChildWithOptional.__required_keys__, - frozenset(['one', 'child']), - ) - self.assertEqual( - ChildWithOptional.__optional_keys__, - frozenset(['untotal']), - ) - self.assertEqual( - ChildWithOptional.__annotations__, - {'one': int, 'untotal': str, 'child': bool}, - ) + C1 = Z[typing_Concatenate] + self.assertEqual(C1.__parameters__, (P,)) + self.assertEqual(C1.__args__, (typing_Concatenate,)) - class ChildWithTotalFalse(One, Untotal, total=False): - child: bool - self.assertEqual( - ChildWithTotalFalse.__required_keys__, - frozenset(['one']), - ) - self.assertEqual( - ChildWithTotalFalse.__optional_keys__, - frozenset(['untotal', 'child']), - ) - self.assertEqual( - ChildWithTotalFalse.__annotations__, - {'one': int, 'untotal': str, 'child': bool}, - ) + def test_pickle(self): + global P, P_co, P_contra, P_default + P = ParamSpec('P') + P_co = ParamSpec('P_co', covariant=True) + P_contra = ParamSpec('P_contra', contravariant=True) + P_default = ParamSpec('P_default', default=[int]) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(f'Pickle protocol {proto}'): + for paramspec in (P, P_co, P_contra, P_default): + z = pickle.loads(pickle.dumps(paramspec, proto)) + self.assertEqual(z.__name__, paramspec.__name__) + self.assertEqual(z.__covariant__, paramspec.__covariant__) + self.assertEqual(z.__contravariant__, paramspec.__contravariant__) + self.assertEqual(z.__bound__, paramspec.__bound__) + self.assertEqual(z.__default__, paramspec.__default__) - class ChildWithInlineAndOptional(Untotal, Inline): - child: bool - self.assertEqual( - ChildWithInlineAndOptional.__required_keys__, - frozenset(['inline', 'child']), - ) - self.assertEqual( - ChildWithInlineAndOptional.__optional_keys__, - frozenset(['untotal']), - ) - self.assertEqual( - ChildWithInlineAndOptional.__annotations__, - {'inline': bool, 'untotal': str, 'child': bool}, - ) + def test_eq(self): + P = ParamSpec('P') + self.assertEqual(P, P) + self.assertEqual(hash(P), hash(P)) + # ParamSpec should compare by id similar to TypeVar in CPython + self.assertNotEqual(ParamSpec('P'), P) + self.assertIsNot(ParamSpec('P'), P) + # Note: normally you don't test this as it breaks when there's + # a hash collision. However, ParamSpec *must* guarantee that + # as long as two objects don't have the same ID, their hashes + # won't be the same. + self.assertNotEqual(hash(ParamSpec('P')), hash(P)) - wrong_bases = [ - (One, Regular), - (Regular, One), - (One, Two, Regular), - (Inline, Regular), - (Untotal, Regular), - ] - for bases in wrong_bases: - with self.subTest(bases=bases): - with self.assertRaisesRegex( - TypeError, - 'cannot inherit from both a TypedDict type and a non-TypedDict', - ): - class Wrong(*bases): - pass - - def test_closed_values(self): - class Implicit(TypedDict): ... - class ExplicitTrue(TypedDict, closed=True): ... - class ExplicitFalse(TypedDict, closed=False): ... + def test_isinstance_results_unaffected_by_presence_of_tracing_function(self): + # See https://github.com/python/typing_extensions/issues/318 - self.assertIsNone(Implicit.__closed__) - self.assertIs(ExplicitTrue.__closed__, True) - self.assertIs(ExplicitFalse.__closed__, False) + code = textwrap.dedent( + """\ + import sys, typing + def trace_call(*args): + return trace_call - @skipIf(TYPING_3_14_0, "only supported on older versions") - def test_closed_typeddict_compat(self): - class Closed(TypedDict, closed=True): - __extra_items__: None + def run(): + sys.modules.pop("typing_extensions", None) + from typing_extensions import ParamSpec + return isinstance(ParamSpec("P"), typing.TypeVar) - class Unclosed(TypedDict, closed=False): - ... + isinstance_result_1 = run() + sys.setprofile(trace_call) + isinstance_result_2 = run() + sys.stdout.write(f"{isinstance_result_1} {isinstance_result_2}") + """ + ) - class ChildUnclosed(Closed, Unclosed): - ... + # Run this in an isolated process or it pollutes the environment + # and makes other tests fail: + try: + proc = subprocess.run( + [sys.executable, "-c", code], check=True, capture_output=True, text=True, + ) + except subprocess.CalledProcessError as exc: + print("stdout", exc.stdout, sep="\n") + print("stderr", exc.stderr, sep="\n") + raise - self.assertIsNone(ChildUnclosed.__closed__) - self.assertEqual(ChildUnclosed.__extra_items__, NoExtraItems) + # Sanity checks that assert the test is working as expected + self.assertIsInstance(proc.stdout, str) + result1, result2 = proc.stdout.split(" ") + self.assertIn(result1, {"True", "False"}) + self.assertIn(result2, {"True", "False"}) - class ChildClosed(Unclosed, Closed): - ... + # The actual test: + self.assertEqual(result1, result2) - self.assertIsNone(ChildClosed.__closed__) - self.assertEqual(ChildClosed.__extra_items__, NoExtraItems) - def test_extra_items_class_arg(self): - class TD(TypedDict, extra_items=int): - a: str +class ConcatenateTests(BaseTestCase): + def test_basics(self): + P = ParamSpec('P') - self.assertIs(TD.__extra_items__, int) - self.assertEqual(TD.__annotations__, {'a': str}) - self.assertEqual(TD.__required_keys__, frozenset({'a'})) - self.assertEqual(TD.__optional_keys__, frozenset()) + class MyClass: ... - class NoExtra(TypedDict): - a: str + c = Concatenate[MyClass, P] + self.assertNotEqual(c, Concatenate) - self.assertIs(NoExtra.__extra_items__, NoExtraItems) - self.assertEqual(NoExtra.__annotations__, {'a': str}) - self.assertEqual(NoExtra.__required_keys__, frozenset({'a'})) - self.assertEqual(NoExtra.__optional_keys__, frozenset()) + # Test Ellipsis Concatenation + d = Concatenate[MyClass, ...] + self.assertNotEqual(d, c) + self.assertNotEqual(d, Concatenate) - def test_is_typeddict(self): - self.assertIs(is_typeddict(Point2D), True) - self.assertIs(is_typeddict(Point2Dor3D), True) - self.assertIs(is_typeddict(Union[str, int]), False) - # classes, not instances - self.assertIs(is_typeddict(Point2D()), False) - call_based = TypedDict('call_based', {'a': int}) - self.assertIs(is_typeddict(call_based), True) - self.assertIs(is_typeddict(call_based()), False) + @skipUnless(TYPING_3_10_0, "Concatenate not available in <3.10") + def test_typing_compatibility(self): + P = ParamSpec('P') + C1 = Concatenate[int, P][typing.Concatenate[int, P]] + self.assertEqual(C1, Concatenate[int, int, P]) + self.assertEqual(get_args(C1), (int, int, P)) - T = TypeVar("T") - class BarGeneric(TypedDict, Generic[T]): - a: T - self.assertIs(is_typeddict(BarGeneric), True) - self.assertIs(is_typeddict(BarGeneric[int]), False) - self.assertIs(is_typeddict(BarGeneric()), False) + C2 = typing.Concatenate[int, P][Concatenate[int, P]] + with self.subTest("typing compatibility with typing_extensions"): + if sys.version_info < (3, 10, 3): + self.skipTest("Unpacking not introduced until 3.10.3") + self.assertEqual(get_args(C2), (int, int, P)) - if hasattr(typing, "TypeAliasType"): - ns = {"TypedDict": TypedDict} - exec("""if True: - class NewGeneric[T](TypedDict): - a: T - """, ns) - NewGeneric = ns["NewGeneric"] - self.assertIs(is_typeddict(NewGeneric), True) - self.assertIs(is_typeddict(NewGeneric[int]), False) - self.assertIs(is_typeddict(NewGeneric()), False) + def test_valid_uses(self): + P = ParamSpec('P') + T = TypeVar('T') + for callable_variant in (Callable, collections.abc.Callable): + with self.subTest(callable_variant=callable_variant): + C1 = callable_variant[Concatenate[int, P], int] + C2 = callable_variant[Concatenate[int, T, P], T] + self.assertEqual(C1.__origin__, C2.__origin__) + self.assertNotEqual(C1, C2) - # The TypedDict constructor is not itself a TypedDict - self.assertIs(is_typeddict(TypedDict), False) - if hasattr(typing, "TypedDict"): - self.assertIs(is_typeddict(typing.TypedDict), False) + C3 = callable_variant[Concatenate[int, ...], int] + C4 = callable_variant[Concatenate[int, T, ...], T] + self.assertEqual(C3.__origin__, C4.__origin__) + self.assertNotEqual(C3, C4) - def test_is_typeddict_against_typeddict_from_typing(self): - Point = typing.TypedDict('Point', {'x': int, 'y': int}) + def test_invalid_uses(self): + P = ParamSpec('P') + T = TypeVar('T') - class PointDict2D(typing.TypedDict): - x: int - y: int + with self.assertRaisesRegex( + TypeError, + 'Cannot take a Concatenate of no types', + ): + Concatenate[()] - class PointDict3D(PointDict2D, total=False): - z: int + with self.assertRaisesRegex( + TypeError, + 'The last parameter to Concatenate should be a ParamSpec variable or ellipsis', + ): + Concatenate[P, T] - assert is_typeddict(Point) is True - assert is_typeddict(PointDict2D) is True - assert is_typeddict(PointDict3D) is True + # Test with tuple argument + with self.assertRaisesRegex( + TypeError, + "The last parameter to Concatenate should be a ParamSpec variable or ellipsis.", + ): + Concatenate[(P, T)] - @skipUnless(HAS_FORWARD_MODULE, "ForwardRef.__forward_module__ was added in 3.9.7") - def test_get_type_hints_cross_module_subclass(self): - self.assertNotIn("_DoNotImport", globals()) - self.assertEqual( - {k: v.__name__ for k, v in get_type_hints(Bar).items()}, - {'a': "_DoNotImport", 'b': "int"} - ) + with self.assertRaisesRegex( + TypeError, + 'is not a generic class', + ): + Callable[Concatenate[int, ...], Any][Any] - def test_get_type_hints_generic(self): - self.assertEqual( - get_type_hints(BarGeneric), - {'a': typing.Optional[T], 'b': int} - ) + # Assure that `_type_check` is called. + P = ParamSpec('P') + with self.assertRaisesRegex( + TypeError, + "each arg must be a type", + ): + Concatenate[(str,), P] - class FooBarGeneric(BarGeneric[int]): - c: str + @skipUnless(TYPING_3_10_0, "Missing backport to 3.9. See issue #48") + def test_alias_subscription_with_ellipsis(self): + P = ParamSpec('P') + X = Callable[Concatenate[int, P], Any] - self.assertEqual( - get_type_hints(FooBarGeneric), - {'a': typing.Optional[T], 'b': int, 'c': str} - ) + C1 = X[...] + self.assertEqual(C1.__parameters__, ()) + self.assertEqual(get_args(C1), (Concatenate[int, ...], Any)) - @skipUnless(TYPING_3_12_0, "PEP 695 required") - def test_pep695_generic_typeddict(self): - ns = {"TypedDict": TypedDict} - exec("""if True: - class A[T](TypedDict): - a: T - """, ns) - A = ns["A"] - T, = A.__type_params__ - self.assertIsInstance(T, TypeVar) - self.assertEqual(T.__name__, 'T') - self.assertEqual(A.__bases__, (Generic, dict)) - self.assertEqual(A.__orig_bases__, (TypedDict, Generic[T])) - self.assertEqual(A.__mro__, (A, Generic, dict, object)) - self.assertEqual(A.__parameters__, (T,)) - self.assertEqual(A[str].__parameters__, ()) - self.assertEqual(A[str].__args__, (str,)) + def test_basic_introspection(self): + P = ParamSpec('P') + C1 = Concatenate[int, P] + C2 = Concatenate[int, T, P] + C3 = Concatenate[int, ...] + C4 = Concatenate[int, T, ...] + self.assertEqual(C1.__origin__, Concatenate) + self.assertEqual(C1.__args__, (int, P)) + self.assertEqual(C2.__origin__, Concatenate) + self.assertEqual(C2.__args__, (int, T, P)) + self.assertEqual(C3.__origin__, Concatenate) + self.assertEqual(C3.__args__, (int, Ellipsis)) + self.assertEqual(C4.__origin__, Concatenate) + self.assertEqual(C4.__args__, (int, T, Ellipsis)) - def test_generic_inheritance(self): - class A(TypedDict, Generic[T]): - a: T + def test_eq(self): + P = ParamSpec('P') + C1 = Concatenate[int, P] + C2 = Concatenate[int, P] + C3 = Concatenate[int, T, P] + self.assertEqual(C1, C2) + self.assertEqual(hash(C1), hash(C2)) + self.assertNotEqual(C1, C3) - self.assertEqual(A.__bases__, (Generic, dict)) - self.assertEqual(A.__orig_bases__, (TypedDict, Generic[T])) - self.assertEqual(A.__mro__, (A, Generic, dict, object)) - self.assertEqual(A.__parameters__, (T,)) - self.assertEqual(A[str].__parameters__, ()) - self.assertEqual(A[str].__args__, (str,)) + C4 = Concatenate[int, ...] + C5 = Concatenate[int, ...] + C6 = Concatenate[int, T, ...] + self.assertEqual(C4, C5) + self.assertEqual(hash(C4), hash(C5)) + self.assertNotEqual(C4, C6) - class A2(Generic[T], TypedDict): - a: T + def test_substitution(self): + T = TypeVar('T') + P = ParamSpec('P') + Ts = TypeVarTuple("Ts") - self.assertEqual(A2.__bases__, (Generic, dict)) - self.assertEqual(A2.__orig_bases__, (Generic[T], TypedDict)) - self.assertEqual(A2.__mro__, (A2, Generic, dict, object)) - self.assertEqual(A2.__parameters__, (T,)) - self.assertEqual(A2[str].__parameters__, ()) - self.assertEqual(A2[str].__args__, (str,)) + C1 = Concatenate[str, T, ...] + self.assertEqual(C1[int], Concatenate[str, int, ...]) - class B(A[KT], total=False): - b: KT + C2 = Concatenate[str, P] + self.assertEqual(C2[...], Concatenate[str, ...]) + self.assertEqual(C2[int], (str, int)) + U1 = Unpack[Tuple[int, str]] + U2 = Unpack[Ts] + self.assertEqual(C2[U1], (str, int, str)) + self.assertEqual(C2[U2], (str, Unpack[Ts])) + self.assertEqual(C2["U2"], (str, EqualToForwardRef("U2"))) - self.assertEqual(B.__bases__, (Generic, dict)) - self.assertEqual(B.__orig_bases__, (A[KT],)) - self.assertEqual(B.__mro__, (B, Generic, dict, object)) - self.assertEqual(B.__parameters__, (KT,)) - self.assertEqual(B.__total__, False) - self.assertEqual(B.__optional_keys__, frozenset(['b'])) - self.assertEqual(B.__required_keys__, frozenset(['a'])) + if (3, 12, 0) <= sys.version_info < (3, 12, 4): + with self.assertRaises(AssertionError): + C2[Unpack[U2]] + else: + with self.assertRaisesRegex(TypeError, "must be used with a tuple type"): + C2[Unpack[U2]] - self.assertEqual(B[str].__parameters__, ()) - self.assertEqual(B[str].__args__, (str,)) - self.assertEqual(B[str].__origin__, B) + C3 = Concatenate[str, T, P] + self.assertEqual(C3[int, [bool]], (str, int, bool)) - class C(B[int]): - c: int + @skipUnless(TYPING_3_10_0, "Concatenate not present before 3.10") + def test_is_param_expr(self): + P = ParamSpec('P') + concat = Concatenate[str, P] + typing_concat = typing.Concatenate[str, P] + self.assertTrue(typing_extensions._is_param_expr(concat)) + self.assertTrue(typing_extensions._is_param_expr(typing_concat)) + if hasattr(typing, "_is_param_expr"): + self.assertTrue(typing._is_param_expr(concat)) + self.assertTrue(typing._is_param_expr(typing_concat)) - self.assertEqual(C.__bases__, (Generic, dict)) - self.assertEqual(C.__orig_bases__, (B[int],)) - self.assertEqual(C.__mro__, (C, Generic, dict, object)) - self.assertEqual(C.__parameters__, ()) - self.assertEqual(C.__total__, True) - self.assertEqual(C.__optional_keys__, frozenset(['b'])) - self.assertEqual(C.__required_keys__, frozenset(['a', 'c'])) - assert C.__annotations__ == { - 'a': T, - 'b': KT, - 'c': int, - } - with self.assertRaises(TypeError): - C[str] +class TypeGuardTests(BaseTestCase): + def test_basics(self): + TypeGuard[int] # OK + self.assertEqual(TypeGuard[int], TypeGuard[int]) - class Point3D(Point2DGeneric[T], Generic[T, KT]): - c: KT + def foo(arg) -> TypeGuard[int]: ... + self.assertEqual(gth(foo), {'return': TypeGuard[int]}) - self.assertEqual(Point3D.__bases__, (Generic, dict)) - self.assertEqual(Point3D.__orig_bases__, (Point2DGeneric[T], Generic[T, KT])) - self.assertEqual(Point3D.__mro__, (Point3D, Generic, dict, object)) - self.assertEqual(Point3D.__parameters__, (T, KT)) - self.assertEqual(Point3D.__total__, True) - self.assertEqual(Point3D.__optional_keys__, frozenset()) - self.assertEqual(Point3D.__required_keys__, frozenset(['a', 'b', 'c'])) - self.assertEqual(Point3D.__annotations__, { - 'a': T, - 'b': T, - 'c': KT, - }) - self.assertEqual(Point3D[int, str].__origin__, Point3D) + def test_repr(self): + if hasattr(typing, 'TypeGuard'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(TypeGuard), f'{mod_name}.TypeGuard') + cv = TypeGuard[int] + self.assertEqual(repr(cv), f'{mod_name}.TypeGuard[int]') + cv = TypeGuard[Employee] + self.assertEqual(repr(cv), f'{mod_name}.TypeGuard[{__name__}.Employee]') + cv = TypeGuard[Tuple[int]] + self.assertEqual(repr(cv), f'{mod_name}.TypeGuard[typing.Tuple[int]]') + def test_cannot_subclass(self): with self.assertRaises(TypeError): - Point3D[int] - + class C(type(TypeGuard)): + pass with self.assertRaises(TypeError): - class Point3D(Point2DGeneric[T], Generic[KT]): - c: KT - - def test_implicit_any_inheritance(self): - class A(TypedDict, Generic[T]): - a: T - - class B(A[KT], total=False): - b: KT + class D(type(TypeGuard[int])): + pass - class WithImplicitAny(B): - c: int + def test_cannot_init(self): + with self.assertRaises(TypeError): + TypeGuard() + with self.assertRaises(TypeError): + type(TypeGuard)() + with self.assertRaises(TypeError): + type(TypeGuard[Optional[int]])() - self.assertEqual(WithImplicitAny.__bases__, (Generic, dict,)) - self.assertEqual(WithImplicitAny.__mro__, (WithImplicitAny, Generic, dict, object)) - # Consistent with GenericTests.test_implicit_any - self.assertEqual(WithImplicitAny.__parameters__, ()) - self.assertEqual(WithImplicitAny.__total__, True) - self.assertEqual(WithImplicitAny.__optional_keys__, frozenset(['b'])) - self.assertEqual(WithImplicitAny.__required_keys__, frozenset(['a', 'c'])) - assert WithImplicitAny.__annotations__ == { - 'a': T, - 'b': KT, - 'c': int, - } + def test_no_isinstance(self): with self.assertRaises(TypeError): - WithImplicitAny[str] + isinstance(1, TypeGuard[int]) + with self.assertRaises(TypeError): + issubclass(int, TypeGuard) - def test_non_generic_subscript(self): - # For backward compatibility, subscription works - # on arbitrary TypedDict types. - class TD(TypedDict): - a: T - A = TD[int] - self.assertEqual(A.__origin__, TD) - self.assertEqual(A.__parameters__, ()) - self.assertEqual(A.__args__, (int,)) - a = A(a=1) - self.assertIs(type(a), dict) - self.assertEqual(a, {'a': 1}) - def test_orig_bases(self): - T = TypeVar('T') +class TypeIsTests(BaseTestCase): + def test_basics(self): + TypeIs[int] # OK + self.assertEqual(TypeIs[int], TypeIs[int]) - class Parent(TypedDict): - pass + def foo(arg) -> TypeIs[int]: ... + self.assertEqual(gth(foo), {'return': TypeIs[int]}) - class Child(Parent): - pass + def test_repr(self): + if hasattr(typing, 'TypeIs'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(TypeIs), f'{mod_name}.TypeIs') + cv = TypeIs[int] + self.assertEqual(repr(cv), f'{mod_name}.TypeIs[int]') + cv = TypeIs[Employee] + self.assertEqual(repr(cv), f'{mod_name}.TypeIs[{__name__}.Employee]') + cv = TypeIs[Tuple[int]] + self.assertEqual(repr(cv), f'{mod_name}.TypeIs[typing.Tuple[int]]') - class OtherChild(Parent): - pass + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(TypeIs)): + pass + with self.assertRaises(TypeError): + class D(type(TypeIs[int])): + pass - class MixedChild(Child, OtherChild, Parent): - pass + def test_cannot_init(self): + with self.assertRaises(TypeError): + TypeIs() + with self.assertRaises(TypeError): + type(TypeIs)() + with self.assertRaises(TypeError): + type(TypeIs[Optional[int]])() - class GenericParent(TypedDict, Generic[T]): - pass + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, TypeIs[int]) + with self.assertRaises(TypeError): + issubclass(int, TypeIs) - class GenericChild(GenericParent[int]): - pass - class OtherGenericChild(GenericParent[str]): - pass +class TypeFormTests(BaseTestCase): + def test_basics(self): + TypeForm[int] # OK + self.assertEqual(TypeForm[int], TypeForm[int]) - class MixedGenericChild(GenericChild, OtherGenericChild, GenericParent[float]): - pass + def foo(arg) -> TypeForm[int]: ... + self.assertEqual(gth(foo), {'return': TypeForm[int]}) - class MultipleGenericBases(GenericParent[int], GenericParent[float]): - pass + def test_repr(self): + if hasattr(typing, 'TypeForm'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(TypeForm), f'{mod_name}.TypeForm') + cv = TypeForm[int] + self.assertEqual(repr(cv), f'{mod_name}.TypeForm[int]') + cv = TypeForm[Employee] + self.assertEqual(repr(cv), f'{mod_name}.TypeForm[{__name__}.Employee]') + cv = TypeForm[Tuple[int]] + self.assertEqual(repr(cv), f'{mod_name}.TypeForm[typing.Tuple[int]]') - CallTypedDict = TypedDict('CallTypedDict', {}) + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(TypeForm)): + pass + with self.assertRaises(TypeError): + class D(type(TypeForm[int])): + pass - self.assertEqual(Parent.__orig_bases__, (TypedDict,)) - self.assertEqual(Child.__orig_bases__, (Parent,)) - self.assertEqual(OtherChild.__orig_bases__, (Parent,)) - self.assertEqual(MixedChild.__orig_bases__, (Child, OtherChild, Parent,)) - self.assertEqual(GenericParent.__orig_bases__, (TypedDict, Generic[T])) - self.assertEqual(GenericChild.__orig_bases__, (GenericParent[int],)) - self.assertEqual(OtherGenericChild.__orig_bases__, (GenericParent[str],)) - self.assertEqual(MixedGenericChild.__orig_bases__, (GenericChild, OtherGenericChild, GenericParent[float])) - self.assertEqual(MultipleGenericBases.__orig_bases__, (GenericParent[int], GenericParent[float])) - self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,)) - - def test_zero_fields_typeddicts(self): - T1 = TypedDict("T1", {}) - class T2(TypedDict): pass - try: - ns = {"TypedDict": TypedDict} - exec("class T3[tvar](TypedDict): pass", ns) - T3 = ns["T3"] - except SyntaxError: - class T3(TypedDict): pass - S = TypeVar("S") - class T4(TypedDict, Generic[S]): pass + def test_call(self): + objs = [ + 1, + "int", + int, + Tuple[int, str], + ] + for obj in objs: + with self.subTest(obj=obj): + self.assertIs(TypeForm(obj), obj) - expected_warning = re.escape( - "Failing to pass a value for the 'fields' parameter is deprecated " - "and will be disallowed in Python 3.15. " - "To create a TypedDict class with 0 fields " - "using the functional syntax, " - "pass an empty dictionary, e.g. `T5 = TypedDict('T5', {})`." - ) - with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"): - T5 = TypedDict('T5') + with self.assertRaises(TypeError): + TypeForm() + with self.assertRaises(TypeError): + TypeForm("too", "many") - expected_warning = re.escape( - "Passing `None` as the 'fields' parameter is deprecated " - "and will be disallowed in Python 3.15. " - "To create a TypedDict class with 0 fields " - "using the functional syntax, " - "pass an empty dictionary, e.g. `T6 = TypedDict('T6', {})`." - ) - with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"): - T6 = TypedDict('T6', None) + def test_cannot_init_type(self): + with self.assertRaises(TypeError): + type(TypeForm)() + with self.assertRaises(TypeError): + type(TypeForm[Optional[int]])() - for klass in T1, T2, T3, T4, T5, T6: - with self.subTest(klass=klass.__name__): - self.assertEqual(klass.__annotations__, {}) - self.assertEqual(klass.__required_keys__, set()) - self.assertEqual(klass.__optional_keys__, set()) - self.assertIsInstance(klass(), dict) + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, TypeForm[int]) + with self.assertRaises(TypeError): + issubclass(int, TypeForm) - def test_readonly_inheritance(self): - class Base1(TypedDict): - a: ReadOnly[int] - class Child1(Base1): - b: str +class LiteralStringTests(BaseTestCase): + def test_basics(self): + class Foo: + def bar(self) -> LiteralString: ... + def baz(self) -> "LiteralString": ... - self.assertEqual(Child1.__readonly_keys__, frozenset({'a'})) - self.assertEqual(Child1.__mutable_keys__, frozenset({'b'})) + self.assertEqual(gth(Foo.bar), {'return': LiteralString}) + self.assertEqual(gth(Foo.baz), {'return': LiteralString}) - class Base2(TypedDict): - a: int + def test_get_origin(self): + self.assertIsNone(get_origin(LiteralString)) - class Child2(Base2): - b: ReadOnly[str] + def test_repr(self): + if hasattr(typing, 'LiteralString'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(LiteralString), f'{mod_name}.LiteralString') - self.assertEqual(Child2.__readonly_keys__, frozenset({'b'})) - self.assertEqual(Child2.__mutable_keys__, frozenset({'a'})) + def test_cannot_subscript(self): + with self.assertRaises(TypeError): + LiteralString[int] - def test_make_mutable_key_readonly(self): - class Base(TypedDict): - a: int + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(LiteralString)): + pass + with self.assertRaises(TypeError): + class D(LiteralString): + pass - self.assertEqual(Base.__readonly_keys__, frozenset()) - self.assertEqual(Base.__mutable_keys__, frozenset({'a'})) + def test_cannot_init(self): + with self.assertRaises(TypeError): + LiteralString() + with self.assertRaises(TypeError): + type(LiteralString)() - class Child(Base): - a: ReadOnly[int] # type checker error, but allowed at runtime + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, LiteralString) + with self.assertRaises(TypeError): + issubclass(int, LiteralString) - self.assertEqual(Child.__readonly_keys__, frozenset({'a'})) - self.assertEqual(Child.__mutable_keys__, frozenset()) + def test_alias(self): + StringTuple = Tuple[LiteralString, LiteralString] + class Alias: + def return_tuple(self) -> StringTuple: + return ("foo", "pep" + "675") - def test_can_make_readonly_key_mutable(self): - class Base(TypedDict): - a: ReadOnly[int] + def test_typevar(self): + StrT = TypeVar("StrT", bound=LiteralString) + self.assertIs(StrT.__bound__, LiteralString) - class Child(Base): - a: int + def test_pickle(self): + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + pickled = pickle.dumps(LiteralString, protocol=proto) + self.assertIs(LiteralString, pickle.loads(pickled)) - self.assertEqual(Child.__readonly_keys__, frozenset()) - self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) - def test_combine_qualifiers(self): - class AllTheThings(TypedDict): - a: Annotated[Required[ReadOnly[int]], "why not"] - b: Required[Annotated[ReadOnly[int], "why not"]] - c: ReadOnly[NotRequired[Annotated[int, "why not"]]] - d: NotRequired[Annotated[int, "why not"]] +class SelfTests(BaseTestCase): + def test_basics(self): + class Foo: + def bar(self) -> Self: ... - self.assertEqual(AllTheThings.__required_keys__, frozenset({'a', 'b'})) - self.assertEqual(AllTheThings.__optional_keys__, frozenset({'c', 'd'})) - self.assertEqual(AllTheThings.__readonly_keys__, frozenset({'a', 'b', 'c'})) - self.assertEqual(AllTheThings.__mutable_keys__, frozenset({'d'})) + self.assertEqual(gth(Foo.bar), {'return': Self}) - self.assertEqual( - get_type_hints(AllTheThings, include_extras=False), - {'a': int, 'b': int, 'c': int, 'd': int}, - ) - self.assertEqual( - get_type_hints(AllTheThings, include_extras=True), - { - 'a': Annotated[Required[ReadOnly[int]], 'why not'], - 'b': Required[Annotated[ReadOnly[int], 'why not']], - 'c': ReadOnly[NotRequired[Annotated[int, 'why not']]], - 'd': NotRequired[Annotated[int, 'why not']], - }, - ) + def test_repr(self): + if hasattr(typing, 'Self'): + mod_name = 'typing' + else: + mod_name = 'typing_extensions' + self.assertEqual(repr(Self), f'{mod_name}.Self') - @skipIf(TYPING_3_14_0, "Old syntax only supported on <3.14") - def test_extra_keys_non_readonly_legacy(self): - class Base(TypedDict, closed=True): - __extra_items__: str + def test_cannot_subscript(self): + with self.assertRaises(TypeError): + Self[int] - class Child(Base): - a: NotRequired[int] + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(Self)): + pass - self.assertEqual(Child.__required_keys__, frozenset({})) - self.assertEqual(Child.__optional_keys__, frozenset({'a'})) - self.assertEqual(Child.__readonly_keys__, frozenset({})) - self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) + def test_cannot_init(self): + with self.assertRaises(TypeError): + Self() + with self.assertRaises(TypeError): + type(Self)() - @skipIf(TYPING_3_14_0, "Only supported on <3.14") - def test_extra_keys_readonly_legacy(self): - class Base(TypedDict, closed=True): - __extra_items__: ReadOnly[str] + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, Self) + with self.assertRaises(TypeError): + issubclass(int, Self) - class Child(Base): - a: NotRequired[str] + def test_alias(self): + TupleSelf = Tuple[Self, Self] + class Alias: + def return_tuple(self) -> TupleSelf: + return (self, self) - self.assertEqual(Child.__required_keys__, frozenset({})) - self.assertEqual(Child.__optional_keys__, frozenset({'a'})) - self.assertEqual(Child.__readonly_keys__, frozenset({})) - self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) + def test_pickle(self): + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + pickled = pickle.dumps(Self, protocol=proto) + self.assertIs(Self, pickle.loads(pickled)) - @skipIf(TYPING_3_14_0, "Only supported on <3.14") - def test_extra_keys_readonly_explicit_closed_legacy(self): - class Base(TypedDict, closed=True): - __extra_items__: ReadOnly[str] - class Child(Base, closed=True): - a: NotRequired[str] +class UnpackTests(BaseTestCase): + def test_basic_plain(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(Unpack[Ts], Unpack[Ts]) + with self.assertRaises(TypeError): + Unpack() - self.assertEqual(Child.__required_keys__, frozenset({})) - self.assertEqual(Child.__optional_keys__, frozenset({'a'})) - self.assertEqual(Child.__readonly_keys__, frozenset({})) - self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) + def test_repr(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(repr(Unpack[Ts]), f'{Unpack.__module__}.Unpack[Ts]') - @skipIf(TYPING_3_14_0, "Only supported on <3.14") - def test_extra_key_required_legacy(self): - with self.assertRaisesRegex( - TypeError, - "Special key __extra_items__ does not support Required" - ): - TypedDict("A", {"__extra_items__": Required[int]}, closed=True) + def test_cannot_subclass_vars(self): + with self.assertRaises(TypeError): + class V(Unpack[TypeVarTuple('Ts')]): + pass - with self.assertRaisesRegex( - TypeError, - "Special key __extra_items__ does not support NotRequired" - ): - TypedDict("A", {"__extra_items__": NotRequired[int]}, closed=True) + def test_tuple(self): + Ts = TypeVarTuple('Ts') + Tuple[Unpack[Ts]] - def test_regular_extra_items_legacy(self): - class ExtraReadOnly(TypedDict): - __extra_items__: ReadOnly[str] + def test_union(self): + Xs = TypeVarTuple('Xs') + Ys = TypeVarTuple('Ys') + self.assertEqual( + Union[Unpack[Xs]], + Unpack[Xs] + ) + self.assertNotEqual( + Union[Unpack[Xs]], + Union[Unpack[Xs], Unpack[Ys]] + ) + self.assertEqual( + Union[Unpack[Xs], Unpack[Xs]], + Unpack[Xs] + ) + self.assertNotEqual( + Union[Unpack[Xs], int], + Union[Unpack[Xs]] + ) + self.assertNotEqual( + Union[Unpack[Xs], int], + Union[int] + ) + self.assertEqual( + Union[Unpack[Xs], int].__args__, + (Unpack[Xs], int) + ) + self.assertEqual( + Union[Unpack[Xs], int].__parameters__, + (Xs,) + ) + self.assertIs( + Union[Unpack[Xs], int].__origin__, + Union + ) - self.assertEqual(ExtraReadOnly.__required_keys__, frozenset({'__extra_items__'})) - self.assertEqual(ExtraReadOnly.__optional_keys__, frozenset({})) - self.assertEqual(ExtraReadOnly.__readonly_keys__, frozenset({'__extra_items__'})) - self.assertEqual(ExtraReadOnly.__mutable_keys__, frozenset({})) - self.assertIs(ExtraReadOnly.__extra_items__, NoExtraItems) - self.assertIsNone(ExtraReadOnly.__closed__) + def test_concatenation(self): + Xs = TypeVarTuple('Xs') + self.assertEqual(Tuple[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) + self.assertEqual(Tuple[Unpack[Xs], int].__args__, (Unpack[Xs], int)) + self.assertEqual(Tuple[int, Unpack[Xs], str].__args__, + (int, Unpack[Xs], str)) + class C(Generic[Unpack[Xs]]): pass + class D(Protocol[Unpack[Xs]]): pass + for klass in C, D: + with self.subTest(klass=klass.__name__): + self.assertEqual(klass[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) + self.assertEqual(klass[Unpack[Xs], int].__args__, (Unpack[Xs], int)) + self.assertEqual(klass[int, Unpack[Xs], str].__args__, + (int, Unpack[Xs], str)) - class ExtraRequired(TypedDict): - __extra_items__: Required[str] + def test_class(self): + Ts = TypeVarTuple('Ts') - self.assertEqual(ExtraRequired.__required_keys__, frozenset({'__extra_items__'})) - self.assertEqual(ExtraRequired.__optional_keys__, frozenset({})) - self.assertEqual(ExtraRequired.__readonly_keys__, frozenset({})) - self.assertEqual(ExtraRequired.__mutable_keys__, frozenset({'__extra_items__'})) - self.assertIs(ExtraRequired.__extra_items__, NoExtraItems) - self.assertIsNone(ExtraRequired.__closed__) + class C(Generic[Unpack[Ts]]): pass + class D(Protocol[Unpack[Ts]]): pass - class ExtraNotRequired(TypedDict): - __extra_items__: NotRequired[str] + for klass in C, D: + with self.subTest(klass=klass.__name__): + self.assertEqual(klass[int].__args__, (int,)) + self.assertEqual(klass[int, str].__args__, (int, str)) - self.assertEqual(ExtraNotRequired.__required_keys__, frozenset({})) - self.assertEqual(ExtraNotRequired.__optional_keys__, frozenset({'__extra_items__'})) - self.assertEqual(ExtraNotRequired.__readonly_keys__, frozenset({})) - self.assertEqual(ExtraNotRequired.__mutable_keys__, frozenset({'__extra_items__'})) - self.assertIs(ExtraNotRequired.__extra_items__, NoExtraItems) - self.assertIsNone(ExtraNotRequired.__closed__) + with self.assertRaises(TypeError): + class C(Generic[Unpack[Ts], int]): pass - @skipIf(TYPING_3_14_0, "Only supported on <3.14") - def test_closed_inheritance_legacy(self): - class Base(TypedDict, closed=True): - __extra_items__: ReadOnly[Union[str, None]] + with self.assertRaises(TypeError): + class D(Protocol[Unpack[Ts], int]): pass - self.assertEqual(Base.__required_keys__, frozenset({})) - self.assertEqual(Base.__optional_keys__, frozenset({})) - self.assertEqual(Base.__readonly_keys__, frozenset({})) - self.assertEqual(Base.__mutable_keys__, frozenset({})) - self.assertEqual(Base.__annotations__, {}) - self.assertEqual(Base.__extra_items__, ReadOnly[Union[str, None]]) - self.assertIs(Base.__closed__, True) + T1 = TypeVar('T') + T2 = TypeVar('T') + class C(Generic[T1, T2, Unpack[Ts]]): pass + class D(Protocol[T1, T2, Unpack[Ts]]): pass + for klass in C, D: + with self.subTest(klass=klass.__name__): + self.assertEqual(klass[int, str].__args__, (int, str)) + self.assertEqual(klass[int, str, float].__args__, (int, str, float)) + self.assertEqual( + klass[int, str, float, bool].__args__, (int, str, float, bool) + ) + # A bug was fixed in 3.11.1 + # (https://github.com/python/cpython/commit/74920aa27d0c57443dd7f704d6272cca9c507ab3) + # That means this assertion doesn't pass on 3.11.0, + # but it passes on all other Python versions + if sys.version_info[:3] != (3, 11, 0): + with self.assertRaises(TypeError): + klass[int] - class Child(Base, closed=True): - a: int - __extra_items__: int + def test_substitution(self): + Ts = TypeVarTuple("Ts") + unpacked_str = Unpack[Ts][str] # This should not raise an error + self.assertIs(unpacked_str, str) - self.assertEqual(Child.__required_keys__, frozenset({'a'})) - self.assertEqual(Child.__optional_keys__, frozenset({})) - self.assertEqual(Child.__readonly_keys__, frozenset({})) - self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) - self.assertEqual(Child.__annotations__, {"a": int}) - self.assertIs(Child.__extra_items__, int) - self.assertIs(Child.__closed__, True) + @skipUnless(TYPING_3_11_0, "Needs Issue #103 for <3.11") + def test_nested_unpack(self): + Ts = TypeVarTuple("Ts") + Variadic = Tuple[int, Unpack[Ts]] + # Tuple[int, int, Tuple[str, int]] + direct_subscription = Variadic[int, Tuple[str, int]] + # Tuple[int, int, Tuple[*Ts, int]] + TupleAliasTs = Variadic[int, Tuple[Unpack[Ts], int]] - class GrandChild(Child, closed=True): - __extra_items__: str + # Tuple[int, int, Tuple[str, int]] + recursive_unpack = TupleAliasTs[str] + self.assertEqual(direct_subscription, recursive_unpack) + self.assertEqual(get_args(recursive_unpack), (int, int, Tuple[str, int])) - self.assertEqual(GrandChild.__required_keys__, frozenset({'a'})) - self.assertEqual(GrandChild.__optional_keys__, frozenset({})) - self.assertEqual(GrandChild.__readonly_keys__, frozenset({})) - self.assertEqual(GrandChild.__mutable_keys__, frozenset({'a'})) - self.assertEqual(GrandChild.__annotations__, {"a": int}) - self.assertIs(GrandChild.__extra_items__, str) - self.assertIs(GrandChild.__closed__, True) + # Test with Callable + T = TypeVar("T") + # Tuple[int, (*Ts) -> T] + CallableAliasTsT = Variadic[Callable[[Unpack[Ts]], T]] + # Tuple[int, (str, int) -> object] + callable_fully_subscripted = CallableAliasTsT[Unpack[Tuple[str, int]], object] + self.assertEqual(get_args(callable_fully_subscripted), (int, Callable[[str, int], object])) - def test_closed_inheritance(self): - class Base(TypedDict, extra_items=ReadOnly[Union[str, None]]): - a: int + @skipUnless(TYPING_3_11_0, "Needs Issue #103 for <3.11") + def test_equivalent_nested_variadics(self): + T = TypeVar("T") + Ts = TypeVarTuple("Ts") + Variadic = Tuple[int, Unpack[Ts]] + TupleAliasTsT = Variadic[Tuple[Unpack[Ts], T]] + nested_tuple_bare = TupleAliasTsT[str, int, object] - self.assertEqual(Base.__required_keys__, frozenset({"a"})) - self.assertEqual(Base.__optional_keys__, frozenset({})) - self.assertEqual(Base.__readonly_keys__, frozenset({})) - self.assertEqual(Base.__mutable_keys__, frozenset({"a"})) - self.assertEqual(Base.__annotations__, {"a": int}) - self.assertEqual(Base.__extra_items__, ReadOnly[Union[str, None]]) - self.assertIsNone(Base.__closed__) + self.assertEqual(get_args(nested_tuple_bare), (int, Tuple[str, int, object])) + # Variants + self.assertEqual(nested_tuple_bare, TupleAliasTsT[Unpack[Tuple[str, int, object]]]) + self.assertEqual(nested_tuple_bare, TupleAliasTsT[Unpack[Tuple[str, int]], object]) + self.assertEqual(nested_tuple_bare, TupleAliasTsT[Unpack[Tuple[str]], Unpack[Tuple[int]], object]) - class Child(Base, extra_items=int): - a: str + @skipUnless(TYPING_3_11_0, "Needed for backport") + def test_type_var_inheritance(self): + Ts = TypeVarTuple("Ts") + self.assertFalse(isinstance(Unpack[Ts], TypeVar)) + self.assertFalse(isinstance(Unpack[Ts], typing.TypeVar)) - self.assertEqual(Child.__required_keys__, frozenset({'a'})) - self.assertEqual(Child.__optional_keys__, frozenset({})) - self.assertEqual(Child.__readonly_keys__, frozenset({})) - self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) - self.assertEqual(Child.__annotations__, {"a": str}) - self.assertIs(Child.__extra_items__, int) - self.assertIsNone(Child.__closed__) - class GrandChild(Child, closed=True): - a: float +class TypeVarTupleTests(BaseTestCase): - self.assertEqual(GrandChild.__required_keys__, frozenset({'a'})) - self.assertEqual(GrandChild.__optional_keys__, frozenset({})) - self.assertEqual(GrandChild.__readonly_keys__, frozenset({})) - self.assertEqual(GrandChild.__mutable_keys__, frozenset({'a'})) - self.assertEqual(GrandChild.__annotations__, {"a": float}) - self.assertIs(GrandChild.__extra_items__, NoExtraItems) - self.assertIs(GrandChild.__closed__, True) + def test_basic_plain(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(Ts, Ts) + self.assertIsInstance(Ts, TypeVarTuple) + Xs = TypeVarTuple('Xs') + Ys = TypeVarTuple('Ys') + self.assertNotEqual(Xs, Ys) - class GrandGrandChild(GrandChild): - ... - self.assertEqual(GrandGrandChild.__required_keys__, frozenset({'a'})) - self.assertEqual(GrandGrandChild.__optional_keys__, frozenset({})) - self.assertEqual(GrandGrandChild.__readonly_keys__, frozenset({})) - self.assertEqual(GrandGrandChild.__mutable_keys__, frozenset({'a'})) - self.assertEqual(GrandGrandChild.__annotations__, {"a": float}) - self.assertIs(GrandGrandChild.__extra_items__, NoExtraItems) - self.assertIsNone(GrandGrandChild.__closed__) + def test_repr(self): + Ts = TypeVarTuple('Ts') + self.assertEqual(repr(Ts), 'Ts') - def test_implicit_extra_items(self): - class Base(TypedDict): - a: int + def test_no_redefinition(self): + self.assertNotEqual(TypeVarTuple('Ts'), TypeVarTuple('Ts')) - self.assertIs(Base.__extra_items__, NoExtraItems) - self.assertIsNone(Base.__closed__) + def test_cannot_subclass_vars(self): + with self.assertRaises(TypeError): + class V(TypeVarTuple('Ts')): + pass - class ChildA(Base, closed=True): - ... + def test_cannot_subclass_var_itself(self): + with self.assertRaises(TypeError): + class V(TypeVarTuple): + pass - self.assertEqual(ChildA.__extra_items__, NoExtraItems) - self.assertIs(ChildA.__closed__, True) + def test_cannot_instantiate_vars(self): + Ts = TypeVarTuple('Ts') + with self.assertRaises(TypeError): + Ts() - @skipIf(TYPING_3_14_0, "Backwards compatibility only for Python 3.13") - def test_implicit_extra_items_before_3_14(self): - class Base(TypedDict): - a: int - class ChildB(Base, closed=True): - __extra_items__: None + def test_tuple(self): + Ts = TypeVarTuple('Ts') + # Not legal at type checking time but we can't really check against it. + Tuple[Ts] - self.assertIs(ChildB.__extra_items__, type(None)) - self.assertIs(ChildB.__closed__, True) + def test_args_and_parameters(self): + Ts = TypeVarTuple('Ts') - @skipIf( - TYPING_3_13_0, - "The keyword argument alternative to define a " - "TypedDict type using the functional syntax is no longer supported" - ) - def test_backwards_compatibility(self): - with self.assertWarns(DeprecationWarning): - TD = TypedDict("TD", closed=int) - self.assertIs(TD.__closed__, None) - self.assertEqual(TD.__annotations__, {"closed": int}) + t = Tuple[tuple(Ts)] + if sys.version_info >= (3, 11): + self.assertEqual(t.__args__, (typing.Unpack[Ts],)) + else: + self.assertEqual(t.__args__, (Unpack[Ts],)) + self.assertEqual(t.__parameters__, (Ts,)) - with self.assertWarns(DeprecationWarning): - TD = TypedDict("TD", extra_items=int) - self.assertIs(TD.__extra_items__, NoExtraItems) - self.assertEqual(TD.__annotations__, {"extra_items": int}) + def test_pickle(self): + global Ts, Ts_default # pickle wants to reference the class by name + Ts = TypeVarTuple('Ts') + Ts_default = TypeVarTuple('Ts_default', default=Unpack[Tuple[int, str]]) - def test_cannot_combine_closed_and_extra_items(self): - with self.assertRaisesRegex( - TypeError, - "Cannot combine closed=True and extra_items" - ): - class TD(TypedDict, closed=True, extra_items=range): - x: str + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + for typevartuple in (Ts, Ts_default): + z = pickle.loads(pickle.dumps(typevartuple, proto)) + self.assertEqual(z.__name__, typevartuple.__name__) + self.assertEqual(z.__default__, typevartuple.__default__) - def test_typed_dict_signature(self): - self.assertListEqual( - list(inspect.signature(TypedDict).parameters), - ['typename', 'fields', 'total', 'closed', 'extra_items', 'kwargs'] - ) - def test_inline_too_many_arguments(self): - with self.assertRaises(TypeError): - TypedDict[{"a": int}, "extra"] +class FinalDecoratorTests(BaseTestCase): + def test_final_unmodified(self): + def func(x): ... + self.assertIs(func, final(func)) - def test_inline_not_a_dict(self): - with self.assertRaises(TypeError): - TypedDict["not_a_dict"] + def test_dunder_final(self): + @final + def func(): ... + @final + class Cls: ... + self.assertIs(True, func.__final__) + self.assertIs(True, Cls.__final__) - # a tuple of elements isn't allowed, even if the first element is a dict: - with self.assertRaises(TypeError): - TypedDict[({"key": int},)] + class Wrapper: + __slots__ = ("func",) + def __init__(self, func): + self.func = func + def __call__(self, *args, **kwargs): + return self.func(*args, **kwargs) - def test_inline_empty(self): - TD = TypedDict[{}] - self.assertIs(TD.__total__, True) - self.assertIs(TD.__closed__, True) - self.assertEqual(TD.__extra_items__, NoExtraItems) - self.assertEqual(TD.__required_keys__, set()) - self.assertEqual(TD.__optional_keys__, set()) - self.assertEqual(TD.__readonly_keys__, set()) - self.assertEqual(TD.__mutable_keys__, set()) + # Check that no error is thrown if the attribute + # is not writable. + @final + @Wrapper + def wrapped(): ... + self.assertIsInstance(wrapped, Wrapper) + self.assertIs(False, hasattr(wrapped, "__final__")) - def test_inline(self): - TD = TypedDict[{ - "a": int, - "b": Required[int], - "c": NotRequired[int], - "d": ReadOnly[int], - }] - self.assertIsSubclass(TD, dict) - self.assertIsSubclass(TD, typing.MutableMapping) - self.assertNotIsSubclass(TD, collections.abc.Sequence) - self.assertTrue(is_typeddict(TD)) - self.assertEqual(TD.__name__, "") - self.assertEqual( - TD.__annotations__, - {"a": int, "b": Required[int], "c": NotRequired[int], "d": ReadOnly[int]}, - ) - self.assertEqual(TD.__module__, __name__) - self.assertEqual(TD.__bases__, (dict,)) - self.assertIs(TD.__total__, True) - self.assertIs(TD.__closed__, True) - self.assertEqual(TD.__extra_items__, NoExtraItems) - self.assertEqual(TD.__required_keys__, {"a", "b", "d"}) - self.assertEqual(TD.__optional_keys__, {"c"}) - self.assertEqual(TD.__readonly_keys__, {"d"}) - self.assertEqual(TD.__mutable_keys__, {"a", "b", "c"}) + class Meta(type): + @property + def __final__(self): return "can't set me" + @final + class WithMeta(metaclass=Meta): ... + self.assertEqual(WithMeta.__final__, "can't set me") - inst = TD(a=1, b=2, d=3) - self.assertIs(type(inst), dict) - self.assertEqual(inst["a"], 1) + # Builtin classes throw TypeError if you try to set an + # attribute. + final(int) + self.assertIs(False, hasattr(int, "__final__")) - def test_annotations(self): - # _type_check is applied - with self.assertRaisesRegex(TypeError, "Plain typing.Optional is not valid as type argument"): - class X(TypedDict): - a: Optional + # Make sure it works with common builtin decorators + class Methods: + @final + @classmethod + def clsmethod(cls): ... - # _type_convert is applied - class Y(TypedDict): - a: None - b: "int" - if sys.version_info >= (3, 14): - import annotationlib + @final + @staticmethod + def stmethod(): ... - fwdref = EqualToForwardRef('int', module=__name__) - self.assertEqual(Y.__annotations__, {'a': type(None), 'b': fwdref}) - self.assertEqual(Y.__annotate__(annotationlib.Format.FORWARDREF), {'a': type(None), 'b': fwdref}) - else: - self.assertEqual(Y.__annotations__, {'a': type(None), 'b': typing.ForwardRef('int', module=__name__)}) + # The other order doesn't work because property objects + # don't allow attribute assignment. + @property + @final + def prop(self): ... - @skipUnless(TYPING_3_14_0, "Only supported on 3.14") - def test_delayed_type_check(self): - # _type_check is also applied later - class Z(TypedDict): - a: undefined # noqa: F821 + @final + @lru_cache # noqa: B019 + def cached(self): ... - with self.assertRaises(NameError): - Z.__annotations__ + # Use getattr_static because the descriptor returns the + # underlying function, which doesn't have __final__. + self.assertIs( + True, + inspect.getattr_static(Methods, "clsmethod").__final__ + ) + self.assertIs( + True, + inspect.getattr_static(Methods, "stmethod").__final__ + ) + self.assertIs(True, Methods.prop.fget.__final__) + self.assertIs(True, Methods.cached.__final__) - undefined = Final - with self.assertRaisesRegex(TypeError, "Plain typing.Final is not valid as type argument"): - Z.__annotations__ - undefined = None # noqa: F841 - self.assertEqual(Z.__annotations__, {'a': type(None)}) +class DisjointBaseTests(BaseTestCase): + def test_disjoint_base_unmodified(self): + class C: ... + self.assertIs(C, disjoint_base(C)) - @skipUnless(TYPING_3_14_0, "Only supported on 3.14") - def test_deferred_evaluation(self): - class A(TypedDict): - x: NotRequired[undefined] # noqa: F821 - y: ReadOnly[undefined] # noqa: F821 - z: Required[undefined] # noqa: F821 + def test_dunder_disjoint_base(self): + @disjoint_base + class C: ... - self.assertEqual(A.__required_keys__, frozenset({'y', 'z'})) - self.assertEqual(A.__optional_keys__, frozenset({'x'})) - self.assertEqual(A.__readonly_keys__, frozenset({'y'})) - self.assertEqual(A.__mutable_keys__, frozenset({'x', 'z'})) + self.assertIs(C.__disjoint_base__, True) - with self.assertRaises(NameError): - A.__annotations__ - import annotationlib - self.assertEqual( - A.__annotate__(annotationlib.Format.STRING), - {'x': 'NotRequired[undefined]', 'y': 'ReadOnly[undefined]', - 'z': 'Required[undefined]'}, - ) +class RevealTypeTests(BaseTestCase): + def test_reveal_type(self): + obj = object() - def test_dunder_dict(self): - self.assertIsInstance(TypedDict.__dict__, dict) + with contextlib.redirect_stderr(io.StringIO()) as stderr: + self.assertIs(obj, reveal_type(obj)) + self.assertEqual("Runtime type is 'object'", stderr.getvalue().strip()) -class AnnotatedTests(BaseTestCase): - def test_repr(self): - if hasattr(typing, 'Annotated'): - mod_name = 'typing' - else: - mod_name = "typing_extensions" +class DataclassTransformTests(BaseTestCase): + def test_decorator(self): + def create_model(*, frozen: bool = False, kw_only: bool = True): + return lambda cls: cls + + decorated = dataclass_transform(kw_only_default=True, order_default=False)(create_model) + + class CustomerModel: + id: int + + self.assertIs(decorated, create_model) self.assertEqual( - repr(Annotated[int, 4, 5]), - mod_name + ".Annotated[int, 4, 5]" + decorated.__dataclass_transform__, + { + "eq_default": True, + "order_default": False, + "kw_only_default": True, + "frozen_default": False, + "field_specifiers": (), + "kwargs": {}, + } ) - self.assertEqual( - repr(Annotated[List[int], 4, 5]), - mod_name + ".Annotated[typing.List[int], 4, 5]" + self.assertIs( + decorated(frozen=True, kw_only=False)(CustomerModel), + CustomerModel ) - def test_flatten(self): - A = Annotated[Annotated[int, 4], 5] - self.assertEqual(A, Annotated[int, 4, 5]) - self.assertEqual(A.__metadata__, (4, 5)) - self.assertEqual(A.__origin__, int) + def test_base_class(self): + class ModelBase: + def __init_subclass__(cls, *, frozen: bool = False): ... - def test_specialize(self): - L = Annotated[List[T], "my decoration"] - LI = Annotated[List[int], "my decoration"] - self.assertEqual(L[int], Annotated[List[int], "my decoration"]) - self.assertEqual(L[int].__metadata__, ("my decoration",)) - self.assertEqual(L[int].__origin__, List[int]) - with self.assertRaises(TypeError): - LI[int] - with self.assertRaises(TypeError): - L[int, float] + Decorated = dataclass_transform( + eq_default=True, + order_default=True, + # Arbitrary unrecognized kwargs are accepted at runtime. + make_everything_awesome=True, + )(ModelBase) - def test_hash_eq(self): - self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1) - self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4]) - self.assertNotEqual(Annotated[int, 4, 5], Annotated[str, 4, 5]) - self.assertNotEqual(Annotated[int, 4], Annotated[int, 4, 4]) + class CustomerModel(Decorated, frozen=True): + id: int + + self.assertIs(Decorated, ModelBase) self.assertEqual( - {Annotated[int, 4, 5], Annotated[int, 4, 5], Annotated[T, 4, 5]}, - {Annotated[int, 4, 5], Annotated[T, 4, 5]} + Decorated.__dataclass_transform__, + { + "eq_default": True, + "order_default": True, + "kw_only_default": False, + "frozen_default": False, + "field_specifiers": (), + "kwargs": {"make_everything_awesome": True}, + } ) + self.assertIsSubclass(CustomerModel, Decorated) - def test_instantiate(self): - class C: - classvar = 4 + def test_metaclass(self): + class Field: ... - def __init__(self, x): - self.x = x + class ModelMeta(type): + def __new__( + cls, name, bases, namespace, *, init: bool = True, + ): + return super().__new__(cls, name, bases, namespace) - def __eq__(self, other): - if not isinstance(other, C): - return NotImplemented - return other.x == self.x + Decorated = dataclass_transform( + order_default=True, field_specifiers=(Field,) + )(ModelMeta) - A = Annotated[C, "a decoration"] - a = A(5) - c = C(5) - self.assertEqual(a, c) - self.assertEqual(a.x, c.x) - self.assertEqual(a.classvar, c.classvar) + class ModelBase(metaclass=Decorated): ... - def test_instantiate_generic(self): - MyCount = Annotated[typing_extensions.Counter[T], "my decoration"] - self.assertEqual(MyCount([4, 4, 5]), {4: 2, 5: 1}) - self.assertEqual(MyCount[int]([4, 4, 5]), {4: 2, 5: 1}) + class CustomerModel(ModelBase, init=False): + id: int - def test_cannot_instantiate_forward(self): - A = Annotated["int", (5, 6)] - with self.assertRaises(TypeError): - A(5) + self.assertIs(Decorated, ModelMeta) + self.assertEqual( + Decorated.__dataclass_transform__, + { + "eq_default": True, + "order_default": True, + "kw_only_default": False, + "frozen_default": False, + "field_specifiers": (Field,), + "kwargs": {}, + } + ) + self.assertIsInstance(CustomerModel, Decorated) - def test_cannot_instantiate_type_var(self): - A = Annotated[T, (5, 6)] - with self.assertRaises(TypeError): - A(5) - def test_cannot_getattr_typevar(self): - with self.assertRaises(AttributeError): - Annotated[T, (5, 7)].x +class AllTests(BaseTestCase): - def test_attr_passthrough(self): - class C: - classvar = 4 + def test_drop_in_for_typing(self): + # Check that the typing_extensions.__all__ is a superset of + # typing.__all__. + t_all = set(typing.__all__) + te_all = set(typing_extensions.__all__) + exceptions = {"ByteString"} + self.assertGreaterEqual(te_all, t_all - exceptions) + # Deprecated, to be removed in 3.14 + self.assertFalse(hasattr(typing_extensions, "ByteString")) + # These were never included in `typing.__all__`, + # and have been removed in Python 3.13 + self.assertNotIn('re', te_all) + self.assertNotIn('io', te_all) - A = Annotated[C, "a decoration"] - self.assertEqual(A.classvar, 4) - A.x = 5 - self.assertEqual(C.x, 5) + def test_typing_extensions_includes_standard(self): + a = typing_extensions.__all__ + self.assertIn('ClassVar', a) + self.assertIn('Type', a) + self.assertIn('ChainMap', a) + self.assertIn('ContextManager', a) + self.assertIn('Counter', a) + self.assertIn('DefaultDict', a) + self.assertIn('Deque', a) + self.assertIn('NewType', a) + self.assertIn('overload', a) + self.assertIn('Text', a) + self.assertIn('TYPE_CHECKING', a) + self.assertIn('TypeAlias', a) + self.assertIn('ParamSpec', a) + self.assertIn("Concatenate", a) - @skipIf(sys.version_info[:2] == (3, 10), "Waiting for https://github.com/python/cpython/issues/90649 bugfix.") - def test_special_form_containment(self): - class C: - classvar: Annotated[ClassVar[int], "a decoration"] = 4 - const: Annotated[Final[int], "Const"] = 4 + self.assertIn('Annotated', a) + self.assertIn('get_type_hints', a) - self.assertEqual(get_type_hints(C, globals())["classvar"], ClassVar[int]) - self.assertEqual(get_type_hints(C, globals())["const"], Final[int]) + self.assertIn('Awaitable', a) + self.assertIn('AsyncIterator', a) + self.assertIn('AsyncIterable', a) + self.assertIn('Coroutine', a) + self.assertIn('AsyncContextManager', a) - def test_cannot_subclass(self): - with self.assertRaisesRegex(TypeError, "Cannot subclass .*Annotated"): - class C(Annotated): - pass + self.assertIn('AsyncGenerator', a) - def test_cannot_check_instance(self): - with self.assertRaises(TypeError): - isinstance(5, Annotated[int, "positive"]) + self.assertIn('Protocol', a) + self.assertIn('runtime', a) - def test_cannot_check_subclass(self): - with self.assertRaises(TypeError): - issubclass(int, Annotated[int, "positive"]) + # Check that all objects in `__all__` are present in the module + for name in a: + self.assertTrue(hasattr(typing_extensions, name)) - def test_pickle(self): - samples = [typing.Any, typing.Union[int, str], - typing.Optional[str], Tuple[int, ...], - typing.Callable[[str], bytes], - Self, LiteralString, Never] + def test_all_names_in___all__(self): + exclude = { + 'GenericMeta', + 'KT', + 'PEP_560', + 'T', + 'T_co', + 'T_contra', + 'VT', + } + actual_names = { + name for name in dir(typing_extensions) + if not name.startswith("_") + and not isinstance(getattr(typing_extensions, name), types.ModuleType) + } + # Make sure all public names are in __all__ + self.assertEqual({*exclude, *typing_extensions.__all__}, + actual_names) + # Make sure all excluded names actually exist + self.assertLessEqual(exclude, actual_names) - for t in samples: - x = Annotated[t, "a"] + def test_typing_extensions_defers_when_possible(self): + exclude = set() + if sys.version_info < (3, 10): + exclude |= {'get_args', 'get_origin'} + if sys.version_info < (3, 10, 1): + exclude |= {"Literal"} + if sys.version_info < (3, 11): + exclude |= {'final', 'Any', 'NewType', 'overload', 'Concatenate'} + if sys.version_info < (3, 12): + exclude |= { + 'SupportsAbs', 'SupportsBytes', + 'SupportsComplex', 'SupportsFloat', 'SupportsIndex', 'SupportsInt', + 'SupportsRound', 'Unpack', 'dataclass_transform', + } + if sys.version_info < (3, 13): + exclude |= { + 'NamedTuple', 'Protocol', 'runtime_checkable', 'Generator', + 'AsyncGenerator', 'ContextManager', 'AsyncContextManager', + 'ParamSpec', 'TypeVar', 'TypeVarTuple', 'get_type_hints', + } + if sys.version_info < (3, 14): + exclude |= { + 'TypeAliasType' + } + if not typing_extensions._PEP_728_IMPLEMENTED: + exclude |= {'TypedDict', 'is_typeddict'} + for item in typing_extensions.__all__: + if item not in exclude and hasattr(typing, item): + self.assertIs( + getattr(typing_extensions, item), + getattr(typing, item)) - for prot in range(pickle.HIGHEST_PROTOCOL + 1): - with self.subTest(protocol=prot, type=t): - pickled = pickle.dumps(x, prot) - restored = pickle.loads(pickled) - self.assertEqual(x, restored) + def test_alias_names_still_exist(self): + for name in typing_extensions._typing_names: + # If this fails, change _typing_names to conditionally add the name + # depending on the Python version. + self.assertTrue( + hasattr(typing_extensions, name), + f"{name} no longer exists in typing", + ) - global _Annotated_test_G + def test_typing_extensions_compiles_with_opt(self): + file_path = typing_extensions.__file__ + try: + subprocess.check_output(f'{sys.executable} -OO {file_path}', + stderr=subprocess.STDOUT, + shell=True) + except subprocess.CalledProcessError: + self.fail('Module does not compile with optimize=2 (-OO flag).') - class _Annotated_test_G(Generic[T]): - x = 1 - G = Annotated[_Annotated_test_G[int], "A decoration"] - G.foo = 42 - G.bar = 'abc' +class CoolEmployee(NamedTuple): + name: str + cool: int - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(G, proto) - x = pickle.loads(z) - self.assertEqual(x.foo, 42) - self.assertEqual(x.bar, 'abc') - self.assertEqual(x.x, 1) - def test_subst(self): - dec = "a decoration" - dec2 = "another decoration" - - S = Annotated[T, dec2] - self.assertEqual(S[int], Annotated[int, dec2]) +class CoolEmployeeWithDefault(NamedTuple): + name: str + cool: int = 0 - self.assertEqual(S[Annotated[int, dec]], Annotated[int, dec, dec2]) - L = Annotated[List[T], dec] - self.assertEqual(L[int], Annotated[List[int], dec]) - with self.assertRaises(TypeError): - L[int, int] +class XMeth(NamedTuple): + x: int - self.assertEqual(S[L[int]], Annotated[List[int], dec, dec2]) + def double(self): + return 2 * self.x - D = Annotated[Dict[KT, VT], dec] - self.assertEqual(D[str, int], Annotated[Dict[str, int], dec]) - with self.assertRaises(TypeError): - D[int] - It = Annotated[int, dec] - with self.assertRaises(TypeError): - It[None] +class NamedTupleTests(BaseTestCase): + class NestedEmployee(NamedTuple): + name: str + cool: int - LI = L[int] - with self.assertRaises(TypeError): - LI[None] + def test_basics(self): + Emp = NamedTuple('Emp', [('name', str), ('id', int)]) + self.assertIsSubclass(Emp, tuple) + joe = Emp('Joe', 42) + jim = Emp(name='Jim', id=1) + self.assertIsInstance(joe, Emp) + self.assertIsInstance(joe, tuple) + self.assertEqual(joe.name, 'Joe') + self.assertEqual(joe.id, 42) + self.assertEqual(jim.name, 'Jim') + self.assertEqual(jim.id, 1) + self.assertEqual(Emp.__name__, 'Emp') + self.assertEqual(Emp._fields, ('name', 'id')) + self.assertEqual(Emp.__annotations__, + collections.OrderedDict([('name', str), ('id', int)])) - def test_annotated_in_other_types(self): - X = List[Annotated[T, 5]] - self.assertEqual(X[int], List[Annotated[int, 5]]) + def test_annotation_usage(self): + tim = CoolEmployee('Tim', 9000) + self.assertIsInstance(tim, CoolEmployee) + self.assertIsInstance(tim, tuple) + self.assertEqual(tim.name, 'Tim') + self.assertEqual(tim.cool, 9000) + self.assertEqual(CoolEmployee.__name__, 'CoolEmployee') + self.assertEqual(CoolEmployee._fields, ('name', 'cool')) + self.assertEqual(CoolEmployee.__annotations__, + collections.OrderedDict(name=str, cool=int)) - def test_nested_annotated_with_unhashable_metadata(self): - X = Annotated[ - List[Annotated[str, {"unhashable_metadata"}]], - "metadata" - ] - self.assertEqual(X.__origin__, List[Annotated[str, {"unhashable_metadata"}]]) - self.assertEqual(X.__metadata__, ("metadata",)) + def test_annotation_usage_with_default(self): + jelle = CoolEmployeeWithDefault('Jelle') + self.assertIsInstance(jelle, CoolEmployeeWithDefault) + self.assertIsInstance(jelle, tuple) + self.assertEqual(jelle.name, 'Jelle') + self.assertEqual(jelle.cool, 0) + cooler_employee = CoolEmployeeWithDefault('Sjoerd', 1) + self.assertEqual(cooler_employee.cool, 1) - def test_compatibility(self): - # Test that the _AnnotatedAlias compatibility alias works - self.assertTrue(hasattr(typing_extensions, "_AnnotatedAlias")) - self.assertIs(typing_extensions._AnnotatedAlias, typing._AnnotatedAlias) + self.assertEqual(CoolEmployeeWithDefault.__name__, 'CoolEmployeeWithDefault') + self.assertEqual(CoolEmployeeWithDefault._fields, ('name', 'cool')) + self.assertEqual(CoolEmployeeWithDefault.__annotations__, + dict(name=str, cool=int)) + with self.assertRaisesRegex( + TypeError, + 'Non-default namedtuple field y cannot follow default field x' + ): + class NonDefaultAfterDefault(NamedTuple): + x: int = 3 + y: int -class GetTypeHintsTests(BaseTestCase): - def test_get_type_hints(self): - def foobar(x: List['X']): ... - X = Annotated[int, (1, 10)] - self.assertEqual( - get_type_hints(foobar, globals(), locals()), - {'x': List[int]} - ) - self.assertEqual( - get_type_hints(foobar, globals(), locals(), include_extras=True), - {'x': List[Annotated[int, (1, 10)]]} - ) - BA = Tuple[Annotated[T, (1, 0)], ...] - def barfoo(x: BA): ... - self.assertEqual(get_type_hints(barfoo, globals(), locals())['x'], Tuple[T, ...]) - self.assertIs( - get_type_hints(barfoo, globals(), locals(), include_extras=True)['x'], - BA - ) - def barfoo2(x: typing.Callable[..., Annotated[List[T], "const"]], - y: typing.Union[int, Annotated[T, "mutable"]]): ... - self.assertEqual( - get_type_hints(barfoo2, globals(), locals()), - {'x': typing.Callable[..., List[T]], 'y': typing.Union[int, T]} - ) - BA2 = typing.Callable[..., List[T]] - def barfoo3(x: BA2): ... - self.assertIs( - get_type_hints(barfoo3, globals(), locals(), include_extras=True)["x"], - BA2 - ) + def test_field_defaults(self): + self.assertEqual(CoolEmployeeWithDefault._field_defaults, dict(cool=0)) - def test_get_type_hints_refs(self): + def test_annotation_usage_with_methods(self): + self.assertEqual(XMeth(1).double(), 2) + self.assertEqual(XMeth(42).x, XMeth(42)[0]) + self.assertEqual(str(XRepr(42)), '42 -> 1') + self.assertEqual(XRepr(1, 2) + XRepr(3), 0) - Const = Annotated[T, "Const"] + bad_overwrite_error_message = 'Cannot overwrite NamedTuple attribute' - class MySet(Generic[T]): + with self.assertRaisesRegex(AttributeError, bad_overwrite_error_message): + class XMethBad(NamedTuple): + x: int + def _fields(self): + return 'no chance for this' - def __ior__(self, other: "Const[MySet[T]]") -> "MySet[T]": - ... + with self.assertRaisesRegex(AttributeError, bad_overwrite_error_message): + class XMethBad2(NamedTuple): + x: int + def _source(self): + return 'no chance for this as well' - def __iand__(self, other: Const["MySet[T]"]) -> "MySet[T]": - ... + def test_multiple_inheritance(self): + class A: + pass + with self.assertRaisesRegex( + TypeError, + 'can only inherit from a NamedTuple type and Generic' + ): + class X(NamedTuple, A): + x: int - self.assertEqual( - get_type_hints(MySet.__iand__, globals(), locals()), - {'other': MySet[T], 'return': MySet[T]} - ) + with self.assertRaisesRegex( + TypeError, + 'can only inherit from a NamedTuple type and Generic' + ): + class Y(NamedTuple, tuple): + x: int - self.assertEqual( - get_type_hints(MySet.__iand__, globals(), locals(), include_extras=True), - {'other': Const[MySet[T]], 'return': MySet[T]} - ) + with self.assertRaisesRegex(TypeError, 'duplicate base class'): + class Z(NamedTuple, NamedTuple): + x: int - self.assertEqual( - get_type_hints(MySet.__ior__, globals(), locals()), - {'other': MySet[T], 'return': MySet[T]} - ) + class A(NamedTuple): + x: int + with self.assertRaisesRegex( + TypeError, + 'can only inherit from a NamedTuple type and Generic' + ): + class XX(NamedTuple, A): + y: str - def test_get_type_hints_typeddict(self): - assert get_type_hints(TotalMovie) == {'title': str, 'year': int} - assert get_type_hints(TotalMovie, include_extras=True) == { - 'title': str, - 'year': NotRequired[int], - } + def test_generic(self): + class X(NamedTuple, Generic[T]): + x: T + self.assertEqual(X.__bases__, (tuple, Generic)) + self.assertEqual(X.__orig_bases__, (NamedTuple, Generic[T])) + self.assertEqual(X.__mro__, (X, tuple, Generic, object)) - assert get_type_hints(AnnotatedMovie) == {'title': str, 'year': int} - assert get_type_hints(AnnotatedMovie, include_extras=True) == { - 'title': Annotated[Required[str], "foobar"], - 'year': NotRequired[Annotated[int, 2000]], - } + class Y(Generic[T], NamedTuple): + x: T + self.assertEqual(Y.__bases__, (Generic, tuple)) + self.assertEqual(Y.__orig_bases__, (Generic[T], NamedTuple)) + self.assertEqual(Y.__mro__, (Y, Generic, tuple, object)) - def test_orig_bases(self): - T = TypeVar('T') + for G in X, Y: + with self.subTest(type=G): + self.assertEqual(G.__parameters__, (T,)) + A = G[int] + self.assertIs(A.__origin__, G) + self.assertEqual(A.__args__, (int,)) + self.assertEqual(A.__parameters__, ()) - class Parent(TypedDict): - pass + a = A(3) + self.assertIs(type(a), G) + self.assertIsInstance(a, G) + self.assertEqual(a.x, 3) - class Child(Parent): - pass + things = "arguments" if sys.version_info >= (3, 10) else "parameters" + with self.assertRaisesRegex(TypeError, f'Too many {things}'): + G[int, str] - class OtherChild(Parent): - pass + def test_non_generic_subscript_py39_plus(self): + # For backward compatibility, subscription works + # on arbitrary NamedTuple types. + class Group(NamedTuple): + key: T + group: list[T] + A = Group[int] + self.assertEqual(A.__origin__, Group) + self.assertEqual(A.__parameters__, ()) + self.assertEqual(A.__args__, (int,)) + a = A(1, [2]) + self.assertIs(type(a), Group) + self.assertEqual(a, (1, [2])) - class MixedChild(Child, OtherChild, Parent): - pass + @skipUnless(sys.version_info <= (3, 15), "Behavior removed in 3.15") + def test_namedtuple_keyword_usage(self): + with self.assertWarnsRegex( + DeprecationWarning, + "Creating NamedTuple classes using keyword arguments is deprecated" + ): + LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int) - class GenericParent(TypedDict, Generic[T]): - pass + nick = LocalEmployee('Nick', 25) + self.assertIsInstance(nick, tuple) + self.assertEqual(nick.name, 'Nick') + self.assertEqual(LocalEmployee.__name__, 'LocalEmployee') + self.assertEqual(LocalEmployee._fields, ('name', 'age')) + self.assertEqual(LocalEmployee.__annotations__, dict(name=str, age=int)) - class GenericChild(GenericParent[int]): - pass + with self.assertRaisesRegex( + TypeError, + "Either list of fields or keywords can be provided to NamedTuple, not both" + ): + NamedTuple('Name', [('x', int)], y=str) - class OtherGenericChild(GenericParent[str]): - pass + with self.assertRaisesRegex( + TypeError, + "Either list of fields or keywords can be provided to NamedTuple, not both" + ): + NamedTuple('Name', [], y=str) - class MixedGenericChild(GenericChild, OtherGenericChild, GenericParent[float]): - pass + with self.assertRaisesRegex( + TypeError, + ( + r"Cannot pass `None` as the 'fields' parameter " + r"and also specify fields using keyword arguments" + ) + ): + NamedTuple('Name', None, x=int) - class MultipleGenericBases(GenericParent[int], GenericParent[float]): - pass + @skipUnless(sys.version_info <= (3, 15), "Behavior removed in 3.15") + def test_namedtuple_special_keyword_names(self): + with self.assertWarnsRegex( + DeprecationWarning, + "Creating NamedTuple classes using keyword arguments is deprecated" + ): + NT = NamedTuple("NT", cls=type, self=object, typename=str, fields=list) - CallTypedDict = TypedDict('CallTypedDict', {}) + self.assertEqual(NT.__name__, 'NT') + self.assertEqual(NT._fields, ('cls', 'self', 'typename', 'fields')) + a = NT(cls=str, self=42, typename='foo', fields=[('bar', tuple)]) + self.assertEqual(a.cls, str) + self.assertEqual(a.self, 42) + self.assertEqual(a.typename, 'foo') + self.assertEqual(a.fields, [('bar', tuple)]) - self.assertEqual(Parent.__orig_bases__, (TypedDict,)) - self.assertEqual(Child.__orig_bases__, (Parent,)) - self.assertEqual(OtherChild.__orig_bases__, (Parent,)) - self.assertEqual(MixedChild.__orig_bases__, (Child, OtherChild, Parent,)) - self.assertEqual(GenericParent.__orig_bases__, (TypedDict, Generic[T])) - self.assertEqual(GenericChild.__orig_bases__, (GenericParent[int],)) - self.assertEqual(OtherGenericChild.__orig_bases__, (GenericParent[str],)) - self.assertEqual(MixedGenericChild.__orig_bases__, (GenericChild, OtherGenericChild, GenericParent[float])) - self.assertEqual(MultipleGenericBases.__orig_bases__, (GenericParent[int], GenericParent[float])) - self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,)) + @skipUnless(sys.version_info <= (3, 15), "Behavior removed in 3.15") + def test_empty_namedtuple(self): + expected_warning = re.escape( + "Failing to pass a value for the 'fields' parameter is deprecated " + "and will be disallowed in Python 3.15. " + "To create a NamedTuple class with 0 fields " + "using the functional syntax, " + "pass an empty list, e.g. `NT1 = NamedTuple('NT1', [])`." + ) + with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"): + NT1 = NamedTuple('NT1') + expected_warning = re.escape( + "Passing `None` as the 'fields' parameter is deprecated " + "and will be disallowed in Python 3.15. " + "To create a NamedTuple class with 0 fields " + "using the functional syntax, " + "pass an empty list, e.g. `NT2 = NamedTuple('NT2', [])`." + ) + with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"): + NT2 = NamedTuple('NT2', None) -class TypeAliasTests(BaseTestCase): - def test_canonical_usage_with_variable_annotation(self): - ns = {} - exec('Alias: TypeAlias = Employee', globals(), ns) + NT3 = NamedTuple('NT2', []) - def test_canonical_usage_with_type_comment(self): - Alias: TypeAlias = Employee # noqa: F841 + class CNT(NamedTuple): + pass # empty body - def test_cannot_instantiate(self): - with self.assertRaises(TypeError): - TypeAlias() + for struct in NT1, NT2, NT3, CNT: + with self.subTest(struct=struct): + self.assertEqual(struct._fields, ()) + self.assertEqual(struct.__annotations__, {}) + self.assertIsInstance(struct(), struct) + self.assertEqual(struct._field_defaults, {}) - def test_no_isinstance(self): + def test_namedtuple_errors(self): with self.assertRaises(TypeError): - isinstance(42, TypeAlias) - - def test_no_issubclass(self): + NamedTuple.__new__() with self.assertRaises(TypeError): - issubclass(Employee, TypeAlias) - + NamedTuple() with self.assertRaises(TypeError): - issubclass(TypeAlias, Employee) - - def test_cannot_subclass(self): + NamedTuple('Emp', [('name', str)], None) + with self.assertRaisesRegex(ValueError, 'cannot start with an underscore'): + NamedTuple('Emp', [('_name', str)]) with self.assertRaises(TypeError): - class C(TypeAlias): - pass + NamedTuple(typename='Emp', name=str, id=int) - with self.assertRaises(TypeError): - class D(type(TypeAlias)): - pass + def test_copy_and_pickle(self): + global Emp # pickle wants to reference the class by name + Emp = NamedTuple('Emp', [('name', str), ('cool', int)]) + for cls in Emp, CoolEmployee, self.NestedEmployee: + with self.subTest(cls=cls): + jane = cls('jane', 37) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + z = pickle.dumps(jane, proto) + jane2 = pickle.loads(z) + self.assertEqual(jane2, jane) + self.assertIsInstance(jane2, cls) - def test_repr(self): - if hasattr(typing, 'TypeAlias'): - self.assertEqual(repr(TypeAlias), 'typing.TypeAlias') - else: - self.assertEqual(repr(TypeAlias), 'typing_extensions.TypeAlias') + jane2 = copy.copy(jane) + self.assertEqual(jane2, jane) + self.assertIsInstance(jane2, cls) - def test_cannot_subscript(self): - with self.assertRaises(TypeError): - TypeAlias[int] + jane2 = copy.deepcopy(jane) + self.assertEqual(jane2, jane) + self.assertIsInstance(jane2, cls) -class ParamSpecTests(BaseTestCase): + def test_docstring(self): + self.assertIsInstance(NamedTuple.__doc__, str) - def test_basic_plain(self): - P = ParamSpec('P') - self.assertEqual(P, P) - self.assertIsInstance(P, ParamSpec) - self.assertEqual(P.__name__, 'P') - # Should be hashable - hash(P) + def test_same_as_typing_NamedTuple(self): + self.assertEqual( + set(dir(NamedTuple)) - {"__text_signature__"}, + set(dir(typing.NamedTuple)) + ) + self.assertIs(type(NamedTuple), type(typing.NamedTuple)) - def test_repr(self): - P = ParamSpec('P') - P_co = ParamSpec('P_co', covariant=True) - P_contra = ParamSpec('P_contra', contravariant=True) - P_infer = ParamSpec('P_infer', infer_variance=True) - P_2 = ParamSpec('P_2') - self.assertEqual(repr(P), '~P') - self.assertEqual(repr(P_2), '~P_2') + def test_orig_bases(self): + T = TypeVar('T') - # Note: PEP 612 doesn't require these to be repr-ed correctly, but - # just follow CPython. - self.assertEqual(repr(P_co), '+P_co') - self.assertEqual(repr(P_contra), '-P_contra') - # On other versions we use typing.ParamSpec, but it is not aware of - # infer_variance=. Not worth creating our own version of ParamSpec - # for this. - if hasattr(typing, 'TypeAliasType') or not hasattr(typing, 'ParamSpec'): - self.assertEqual(repr(P_infer), 'P_infer') - else: - self.assertEqual(repr(P_infer), '~P_infer') + class SimpleNamedTuple(NamedTuple): + pass - def test_variance(self): - P_co = ParamSpec('P_co', covariant=True) - P_contra = ParamSpec('P_contra', contravariant=True) - P_infer = ParamSpec('P_infer', infer_variance=True) + class GenericNamedTuple(NamedTuple, Generic[T]): + pass - self.assertIs(P_co.__covariant__, True) - self.assertIs(P_co.__contravariant__, False) - self.assertIs(P_co.__infer_variance__, False) + self.assertEqual(SimpleNamedTuple.__orig_bases__, (NamedTuple,)) + self.assertEqual(GenericNamedTuple.__orig_bases__, (NamedTuple, Generic[T])) - self.assertIs(P_contra.__covariant__, False) - self.assertIs(P_contra.__contravariant__, True) - self.assertIs(P_contra.__infer_variance__, False) + CallNamedTuple = NamedTuple('CallNamedTuple', []) - self.assertIs(P_infer.__covariant__, False) - self.assertIs(P_infer.__contravariant__, False) - self.assertIs(P_infer.__infer_variance__, True) + self.assertEqual(CallNamedTuple.__orig_bases__, (NamedTuple,)) - def test_valid_uses(self): - P = ParamSpec('P') - T = TypeVar('T') - C1 = typing.Callable[P, int] - self.assertEqual(C1.__args__, (P, int)) - self.assertEqual(C1.__parameters__, (P,)) - C2 = typing.Callable[P, T] - self.assertEqual(C2.__args__, (P, T)) - self.assertEqual(C2.__parameters__, (P, T)) + def test_setname_called_on_values_in_class_dictionary(self): + class Vanilla: + def __set_name__(self, owner, name): + self.name = name - # Test collections.abc.Callable too. - # Note: no tests for Callable.__parameters__ here - # because types.GenericAlias Callable is hardcoded to search - # for tp_name "TypeVar" in C. This was changed in 3.10. - C3 = collections.abc.Callable[P, int] - self.assertEqual(C3.__args__, (P, int)) - C4 = collections.abc.Callable[P, T] - self.assertEqual(C4.__args__, (P, T)) + class Foo(NamedTuple): + attr = Vanilla() - # ParamSpec instances should also have args and kwargs attributes. - # Note: not in dir(P) because of __class__ hacks - self.assertTrue(hasattr(P, 'args')) - self.assertTrue(hasattr(P, 'kwargs')) + foo = Foo() + self.assertEqual(len(foo), 0) + self.assertNotIn('attr', Foo._fields) + self.assertIsInstance(foo.attr, Vanilla) + self.assertEqual(foo.attr.name, "attr") - @skipIf((3, 10, 0) <= sys.version_info[:3] <= (3, 10, 2), "Needs https://github.com/python/cpython/issues/90834.") - def test_args_kwargs(self): - P = ParamSpec('P') - P_2 = ParamSpec('P_2') - # Note: not in dir(P) because of __class__ hacks - self.assertTrue(hasattr(P, 'args')) - self.assertTrue(hasattr(P, 'kwargs')) - self.assertIsInstance(P.args, ParamSpecArgs) - self.assertIsInstance(P.kwargs, ParamSpecKwargs) - self.assertIs(P.args.__origin__, P) - self.assertIs(P.kwargs.__origin__, P) - self.assertEqual(P.args, P.args) - self.assertEqual(P.kwargs, P.kwargs) - self.assertNotEqual(P.args, P_2.args) - self.assertNotEqual(P.kwargs, P_2.kwargs) - self.assertNotEqual(P.args, P.kwargs) - self.assertNotEqual(P.kwargs, P.args) - self.assertNotEqual(P.args, P_2.kwargs) - self.assertEqual(repr(P.args), "P.args") - self.assertEqual(repr(P.kwargs), "P.kwargs") + class Bar(NamedTuple): + attr: Vanilla = Vanilla() - def test_user_generics(self): - T = TypeVar("T") - P = ParamSpec("P") - P_2 = ParamSpec("P_2") + bar = Bar() + self.assertEqual(len(bar), 1) + self.assertIn('attr', Bar._fields) + self.assertIsInstance(bar.attr, Vanilla) + self.assertEqual(bar.attr.name, "attr") - class X(Generic[T, P]): - pass + @skipIf( + TYPING_3_12_0, + "__set_name__ behaviour changed on py312+ to use BaseException.add_note()" + ) + def test_setname_raises_the_same_as_on_other_classes_py311_minus(self): + class CustomException(BaseException): pass - class Y(Protocol[T, P]): - pass + class Annoying: + def __set_name__(self, owner, name): + raise CustomException - things = "arguments" if sys.version_info >= (3, 10) else "parameters" - for klass in X, Y: - with self.subTest(klass=klass.__name__): - G1 = klass[int, P_2] - self.assertEqual(G1.__args__, (int, P_2)) - self.assertEqual(G1.__parameters__, (P_2,)) + annoying = Annoying() - G2 = klass[int, Concatenate[int, P_2]] - self.assertEqual(G2.__args__, (int, Concatenate[int, P_2])) - self.assertEqual(G2.__parameters__, (P_2,)) + with self.assertRaises(RuntimeError) as cm: + class NormalClass: + attr = annoying + normal_exception = cm.exception - G3 = klass[int, Concatenate[int, ...]] - self.assertEqual(G3.__args__, (int, Concatenate[int, ...])) - self.assertEqual(G3.__parameters__, ()) + with self.assertRaises(RuntimeError) as cm: + class NamedTupleClass(NamedTuple): + attr = annoying + namedtuple_exception = cm.exception - with self.assertRaisesRegex( - TypeError, - f"Too few {things} for {klass}" - ): - klass[int] + self.assertIs(type(namedtuple_exception), RuntimeError) + self.assertIs(type(namedtuple_exception), type(normal_exception)) + self.assertEqual(len(namedtuple_exception.args), len(normal_exception.args)) + self.assertEqual( + namedtuple_exception.args[0], + normal_exception.args[0].replace("NormalClass", "NamedTupleClass") + ) - # The following are some valid uses cases in PEP 612 that don't work: - # These do not work in 3.9, _type_check blocks the list and ellipsis. - # G3 = X[int, [int, bool]] - # G4 = X[int, ...] - # G5 = Z[[int, str, bool]] + self.assertIs(type(namedtuple_exception.__cause__), CustomException) + self.assertIs( + type(namedtuple_exception.__cause__), type(normal_exception.__cause__) + ) + self.assertEqual( + namedtuple_exception.__cause__.args, normal_exception.__cause__.args + ) - def test_single_argument_generic(self): - P = ParamSpec("P") - T = TypeVar("T") - P_2 = ParamSpec("P_2") + @skipUnless( + TYPING_3_12_0, + "__set_name__ behaviour changed on py312+ to use BaseException.add_note()" + ) + def test_setname_raises_the_same_as_on_other_classes_py312_plus(self): + class CustomException(BaseException): pass - class Z(Generic[P]): - pass + class Annoying: + def __set_name__(self, owner, name): + raise CustomException - class ProtoZ(Protocol[P]): - pass + annoying = Annoying() - for klass in Z, ProtoZ: - with self.subTest(klass=klass.__name__): - # Note: For 3.10+ __args__ are nested tuples here ((int, ),) instead of (int, ) - G6 = klass[int, str, T] - G6args = G6.__args__[0] if sys.version_info >= (3, 10) else G6.__args__ - self.assertEqual(G6args, (int, str, T)) - self.assertEqual(G6.__parameters__, (T,)) + with self.assertRaises(CustomException) as cm: + class NormalClass: + attr = annoying + normal_exception = cm.exception - # P = [int] - G7 = klass[int] - G7args = G7.__args__[0] if sys.version_info >= (3, 10) else G7.__args__ - self.assertEqual(G7args, (int,)) - self.assertEqual(G7.__parameters__, ()) + with self.assertRaises(CustomException) as cm: + class NamedTupleClass(NamedTuple): + attr = annoying + namedtuple_exception = cm.exception - G8 = klass[Concatenate[T, ...]] - self.assertEqual(G8.__args__, (Concatenate[T, ...], )) - self.assertEqual(G8.__parameters__, (T,)) + expected_note = ( + "Error calling __set_name__ on 'Annoying' instance " + "'attr' in 'NamedTupleClass'" + ) - G9 = klass[Concatenate[T, P_2]] - self.assertEqual(G9.__args__, (Concatenate[T, P_2], )) + self.assertIs(type(namedtuple_exception), CustomException) + self.assertIs(type(namedtuple_exception), type(normal_exception)) + self.assertEqual(namedtuple_exception.args, normal_exception.args) - # This is an invalid form but useful for testing correct subsitution - G10 = klass[int, Concatenate[str, P]] - G10args = G10.__args__[0] if sys.version_info >= (3, 10) else G10.__args__ - self.assertEqual(G10args, (int, Concatenate[str, P], )) + self.assertEqual(len(namedtuple_exception.__notes__), 1) + self.assertEqual( + len(namedtuple_exception.__notes__), len(normal_exception.__notes__) + ) - @skipUnless(TYPING_3_10_0, "ParamSpec not present before 3.10") - def test_is_param_expr(self): - P = ParamSpec("P") - P_typing = typing.ParamSpec("P_typing") - self.assertTrue(typing_extensions._is_param_expr(P)) - self.assertTrue(typing_extensions._is_param_expr(P_typing)) - if hasattr(typing, "_is_param_expr"): - self.assertTrue(typing._is_param_expr(P)) - self.assertTrue(typing._is_param_expr(P_typing)) + self.assertEqual(namedtuple_exception.__notes__[0], expected_note) + self.assertEqual( + namedtuple_exception.__notes__[0], + normal_exception.__notes__[0].replace("NormalClass", "NamedTupleClass") + ) - def test_single_argument_generic_with_parameter_expressions(self): - P = ParamSpec("P") - T = TypeVar("T") - P_2 = ParamSpec("P_2") + def test_strange_errors_when_accessing_set_name_itself(self): + class CustomException(Exception): pass - class Z(Generic[P]): - pass + class Meta(type): + def __getattribute__(self, attr): + if attr == "__set_name__": + raise CustomException + return object.__getattribute__(self, attr) - class ProtoZ(Protocol[P]): - pass + class VeryAnnoying(metaclass=Meta): pass - things = "arguments" if sys.version_info >= (3, 10) else "parameters" - for klass in Z, ProtoZ: - with self.subTest(klass=klass.__name__): - G8 = klass[Concatenate[T, ...]] + very_annoying = VeryAnnoying() - H8_1 = G8[int] - self.assertEqual(H8_1.__parameters__, ()) - with self.assertRaisesRegex(TypeError, "not a generic class"): - H8_1[str] + with self.assertRaises(CustomException): + class Foo(NamedTuple): + attr = very_annoying - H8_2 = G8[T][int] - self.assertEqual(H8_2.__parameters__, ()) - with self.assertRaisesRegex(TypeError, "not a generic class"): - H8_2[str] - G9 = klass[Concatenate[T, P_2]] - self.assertEqual(G9.__parameters__, (T, P_2)) +class TypeVarTests(BaseTestCase): + def test_basic_plain(self): + T = TypeVar('T') + # T equals itself. + self.assertEqual(T, T) + # T is an instance of TypeVar + self.assertIsInstance(T, TypeVar) + self.assertEqual(T.__name__, 'T') + self.assertEqual(T.__constraints__, ()) + self.assertIs(T.__bound__, None) + self.assertIs(T.__covariant__, False) + self.assertIs(T.__contravariant__, False) + self.assertIs(T.__infer_variance__, False) - with self.assertRaisesRegex(TypeError, - "The last parameter to Concatenate should be a ParamSpec variable or ellipsis." - if sys.version_info < (3, 10) else - # from __typing_subst__ - "Expected a list of types, an ellipsis, ParamSpec, or Concatenate" - ): - G9[int, int] + def test_attributes(self): + T_bound = TypeVar('T_bound', bound=int) + self.assertEqual(T_bound.__name__, 'T_bound') + self.assertEqual(T_bound.__constraints__, ()) + self.assertIs(T_bound.__bound__, int) - with self.assertRaisesRegex(TypeError, f"Too few {things}"): - G9[int] + T_constraints = TypeVar('T_constraints', int, str) + self.assertEqual(T_constraints.__name__, 'T_constraints') + self.assertEqual(T_constraints.__constraints__, (int, str)) + self.assertIs(T_constraints.__bound__, None) - with self.subTest("Check list as parameter expression", klass=klass.__name__): - if sys.version_info < (3, 10): - self.skipTest("Cannot pass non-types") - G5 = klass[[int, str, T]] - self.assertEqual(G5.__parameters__, (T,)) - self.assertEqual(G5.__args__, ((int, str, T),)) + T_co = TypeVar('T_co', covariant=True) + self.assertEqual(T_co.__name__, 'T_co') + self.assertIs(T_co.__covariant__, True) + self.assertIs(T_co.__contravariant__, False) + self.assertIs(T_co.__infer_variance__, False) - H9 = G9[int, [T]] - self.assertEqual(H9.__parameters__, (T,)) + T_contra = TypeVar('T_contra', contravariant=True) + self.assertEqual(T_contra.__name__, 'T_contra') + self.assertIs(T_contra.__covariant__, False) + self.assertIs(T_contra.__contravariant__, True) + self.assertIs(T_contra.__infer_variance__, False) - # This is an invalid parameter expression but useful for testing correct subsitution - G10 = klass[int, Concatenate[str, P]] - with self.subTest("Check invalid form substitution"): - self.assertEqual(G10.__parameters__, (P, )) - H10 = G10[int] - if (3, 10) <= sys.version_info < (3, 11, 3): - self.skipTest("3.10-3.11.2 does not substitute Concatenate here") - self.assertEqual(H10.__parameters__, ()) - H10args = H10.__args__[0] if sys.version_info >= (3, 10) else H10.__args__ - self.assertEqual(H10args, (int, (str, int))) + T_infer = TypeVar('T_infer', infer_variance=True) + self.assertEqual(T_infer.__name__, 'T_infer') + self.assertIs(T_infer.__covariant__, False) + self.assertIs(T_infer.__contravariant__, False) + self.assertIs(T_infer.__infer_variance__, True) - @skipUnless(TYPING_3_10_0, "ParamSpec not present before 3.10") - def test_substitution_with_typing_variants(self): - # verifies substitution and typing._check_generic working with typing variants - P = ParamSpec("P") - typing_P = typing.ParamSpec("typing_P") - typing_Concatenate = typing.Concatenate[int, P] + def test_typevar_instance_type_error(self): + T = TypeVar('T') + with self.assertRaises(TypeError): + isinstance(42, T) - class Z(Generic[typing_P]): - pass + def test_typevar_subclass_type_error(self): + T = TypeVar('T') + with self.assertRaises(TypeError): + issubclass(int, T) + with self.assertRaises(TypeError): + issubclass(T, int) - P1 = Z[typing_P] - self.assertEqual(P1.__parameters__, (typing_P,)) - self.assertEqual(P1.__args__, (typing_P,)) + def test_constrained_error(self): + with self.assertRaises(TypeError): + X = TypeVar('X', int) + X - C1 = Z[typing_Concatenate] - self.assertEqual(C1.__parameters__, (P,)) - self.assertEqual(C1.__args__, (typing_Concatenate,)) - - def test_pickle(self): - global P, P_co, P_contra, P_default - P = ParamSpec('P') - P_co = ParamSpec('P_co', covariant=True) - P_contra = ParamSpec('P_contra', contravariant=True) - P_default = ParamSpec('P_default', default=[int]) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - with self.subTest(f'Pickle protocol {proto}'): - for paramspec in (P, P_co, P_contra, P_default): - z = pickle.loads(pickle.dumps(paramspec, proto)) - self.assertEqual(z.__name__, paramspec.__name__) - self.assertEqual(z.__covariant__, paramspec.__covariant__) - self.assertEqual(z.__contravariant__, paramspec.__contravariant__) - self.assertEqual(z.__bound__, paramspec.__bound__) - self.assertEqual(z.__default__, paramspec.__default__) - - def test_eq(self): - P = ParamSpec('P') - self.assertEqual(P, P) - self.assertEqual(hash(P), hash(P)) - # ParamSpec should compare by id similar to TypeVar in CPython - self.assertNotEqual(ParamSpec('P'), P) - self.assertIsNot(ParamSpec('P'), P) - # Note: normally you don't test this as it breaks when there's - # a hash collision. However, ParamSpec *must* guarantee that - # as long as two objects don't have the same ID, their hashes - # won't be the same. - self.assertNotEqual(hash(ParamSpec('P')), hash(P)) - - def test_isinstance_results_unaffected_by_presence_of_tracing_function(self): - # See https://github.com/python/typing_extensions/issues/318 - - code = textwrap.dedent( - """\ - import sys, typing - - def trace_call(*args): - return trace_call - - def run(): - sys.modules.pop("typing_extensions", None) - from typing_extensions import ParamSpec - return isinstance(ParamSpec("P"), typing.TypeVar) - - isinstance_result_1 = run() - sys.setprofile(trace_call) - isinstance_result_2 = run() - sys.stdout.write(f"{isinstance_result_1} {isinstance_result_2}") - """ - ) - - # Run this in an isolated process or it pollutes the environment - # and makes other tests fail: - try: - proc = subprocess.run( - [sys.executable, "-c", code], check=True, capture_output=True, text=True, - ) - except subprocess.CalledProcessError as exc: - print("stdout", exc.stdout, sep="\n") - print("stderr", exc.stderr, sep="\n") - raise - - # Sanity checks that assert the test is working as expected - self.assertIsInstance(proc.stdout, str) - result1, result2 = proc.stdout.split(" ") - self.assertIn(result1, {"True", "False"}) - self.assertIn(result2, {"True", "False"}) - - # The actual test: - self.assertEqual(result1, result2) - - -class ConcatenateTests(BaseTestCase): - def test_basics(self): - P = ParamSpec('P') - - class MyClass: ... - - c = Concatenate[MyClass, P] - self.assertNotEqual(c, Concatenate) - - # Test Ellipsis Concatenation - d = Concatenate[MyClass, ...] - self.assertNotEqual(d, c) - self.assertNotEqual(d, Concatenate) - - @skipUnless(TYPING_3_10_0, "Concatenate not available in <3.10") - def test_typing_compatibility(self): - P = ParamSpec('P') - C1 = Concatenate[int, P][typing.Concatenate[int, P]] - self.assertEqual(C1, Concatenate[int, int, P]) - self.assertEqual(get_args(C1), (int, int, P)) - - C2 = typing.Concatenate[int, P][Concatenate[int, P]] - with self.subTest("typing compatibility with typing_extensions"): - if sys.version_info < (3, 10, 3): - self.skipTest("Unpacking not introduced until 3.10.3") - self.assertEqual(get_args(C2), (int, int, P)) - - def test_valid_uses(self): - P = ParamSpec('P') - T = TypeVar('T') - for callable_variant in (Callable, collections.abc.Callable): - with self.subTest(callable_variant=callable_variant): - C1 = callable_variant[Concatenate[int, P], int] - C2 = callable_variant[Concatenate[int, T, P], T] - self.assertEqual(C1.__origin__, C2.__origin__) - self.assertNotEqual(C1, C2) - - C3 = callable_variant[Concatenate[int, ...], int] - C4 = callable_variant[Concatenate[int, T, ...], T] - self.assertEqual(C3.__origin__, C4.__origin__) - self.assertNotEqual(C3, C4) - - def test_invalid_uses(self): - P = ParamSpec('P') - T = TypeVar('T') - - with self.assertRaisesRegex( - TypeError, - 'Cannot take a Concatenate of no types', - ): - Concatenate[()] - - with self.assertRaisesRegex( - TypeError, - 'The last parameter to Concatenate should be a ParamSpec variable or ellipsis', - ): - Concatenate[P, T] - - # Test with tuple argument - with self.assertRaisesRegex( - TypeError, - "The last parameter to Concatenate should be a ParamSpec variable or ellipsis.", - ): - Concatenate[(P, T)] - - with self.assertRaisesRegex( - TypeError, - 'is not a generic class', - ): - Callable[Concatenate[int, ...], Any][Any] - - # Assure that `_type_check` is called. - P = ParamSpec('P') - with self.assertRaisesRegex( - TypeError, - "each arg must be a type", - ): - Concatenate[(str,), P] - - @skipUnless(TYPING_3_10_0, "Missing backport to 3.9. See issue #48") - def test_alias_subscription_with_ellipsis(self): - P = ParamSpec('P') - X = Callable[Concatenate[int, P], Any] - - C1 = X[...] - self.assertEqual(C1.__parameters__, ()) - self.assertEqual(get_args(C1), (Concatenate[int, ...], Any)) - - def test_basic_introspection(self): - P = ParamSpec('P') - C1 = Concatenate[int, P] - C2 = Concatenate[int, T, P] - C3 = Concatenate[int, ...] - C4 = Concatenate[int, T, ...] - self.assertEqual(C1.__origin__, Concatenate) - self.assertEqual(C1.__args__, (int, P)) - self.assertEqual(C2.__origin__, Concatenate) - self.assertEqual(C2.__args__, (int, T, P)) - self.assertEqual(C3.__origin__, Concatenate) - self.assertEqual(C3.__args__, (int, Ellipsis)) - self.assertEqual(C4.__origin__, Concatenate) - self.assertEqual(C4.__args__, (int, T, Ellipsis)) - - def test_eq(self): - P = ParamSpec('P') - C1 = Concatenate[int, P] - C2 = Concatenate[int, P] - C3 = Concatenate[int, T, P] - self.assertEqual(C1, C2) - self.assertEqual(hash(C1), hash(C2)) - self.assertNotEqual(C1, C3) - - C4 = Concatenate[int, ...] - C5 = Concatenate[int, ...] - C6 = Concatenate[int, T, ...] - self.assertEqual(C4, C5) - self.assertEqual(hash(C4), hash(C5)) - self.assertNotEqual(C4, C6) - - def test_substitution(self): - T = TypeVar('T') - P = ParamSpec('P') - Ts = TypeVarTuple("Ts") - - C1 = Concatenate[str, T, ...] - self.assertEqual(C1[int], Concatenate[str, int, ...]) - - C2 = Concatenate[str, P] - self.assertEqual(C2[...], Concatenate[str, ...]) - self.assertEqual(C2[int], (str, int)) - U1 = Unpack[Tuple[int, str]] - U2 = Unpack[Ts] - self.assertEqual(C2[U1], (str, int, str)) - self.assertEqual(C2[U2], (str, Unpack[Ts])) - self.assertEqual(C2["U2"], (str, EqualToForwardRef("U2"))) - - if (3, 12, 0) <= sys.version_info < (3, 12, 4): - with self.assertRaises(AssertionError): - C2[Unpack[U2]] - else: - with self.assertRaisesRegex(TypeError, "must be used with a tuple type"): - C2[Unpack[U2]] - - C3 = Concatenate[str, T, P] - self.assertEqual(C3[int, [bool]], (str, int, bool)) - - @skipUnless(TYPING_3_10_0, "Concatenate not present before 3.10") - def test_is_param_expr(self): - P = ParamSpec('P') - concat = Concatenate[str, P] - typing_concat = typing.Concatenate[str, P] - self.assertTrue(typing_extensions._is_param_expr(concat)) - self.assertTrue(typing_extensions._is_param_expr(typing_concat)) - if hasattr(typing, "_is_param_expr"): - self.assertTrue(typing._is_param_expr(concat)) - self.assertTrue(typing._is_param_expr(typing_concat)) - -class TypeGuardTests(BaseTestCase): - def test_basics(self): - TypeGuard[int] # OK - self.assertEqual(TypeGuard[int], TypeGuard[int]) - - def foo(arg) -> TypeGuard[int]: ... - self.assertEqual(gth(foo), {'return': TypeGuard[int]}) - - def test_repr(self): - if hasattr(typing, 'TypeGuard'): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(TypeGuard), f'{mod_name}.TypeGuard') - cv = TypeGuard[int] - self.assertEqual(repr(cv), f'{mod_name}.TypeGuard[int]') - cv = TypeGuard[Employee] - self.assertEqual(repr(cv), f'{mod_name}.TypeGuard[{__name__}.Employee]') - cv = TypeGuard[Tuple[int]] - self.assertEqual(repr(cv), f'{mod_name}.TypeGuard[typing.Tuple[int]]') - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(TypeGuard)): - pass - with self.assertRaises(TypeError): - class D(type(TypeGuard[int])): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - TypeGuard() - with self.assertRaises(TypeError): - type(TypeGuard)() - with self.assertRaises(TypeError): - type(TypeGuard[Optional[int]])() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, TypeGuard[int]) - with self.assertRaises(TypeError): - issubclass(int, TypeGuard) - - -class TypeIsTests(BaseTestCase): - def test_basics(self): - TypeIs[int] # OK - self.assertEqual(TypeIs[int], TypeIs[int]) - - def foo(arg) -> TypeIs[int]: ... - self.assertEqual(gth(foo), {'return': TypeIs[int]}) - - def test_repr(self): - if hasattr(typing, 'TypeIs'): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(TypeIs), f'{mod_name}.TypeIs') - cv = TypeIs[int] - self.assertEqual(repr(cv), f'{mod_name}.TypeIs[int]') - cv = TypeIs[Employee] - self.assertEqual(repr(cv), f'{mod_name}.TypeIs[{__name__}.Employee]') - cv = TypeIs[Tuple[int]] - self.assertEqual(repr(cv), f'{mod_name}.TypeIs[typing.Tuple[int]]') - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(TypeIs)): - pass - with self.assertRaises(TypeError): - class D(type(TypeIs[int])): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - TypeIs() - with self.assertRaises(TypeError): - type(TypeIs)() - with self.assertRaises(TypeError): - type(TypeIs[Optional[int]])() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, TypeIs[int]) - with self.assertRaises(TypeError): - issubclass(int, TypeIs) - - -class TypeFormTests(BaseTestCase): - def test_basics(self): - TypeForm[int] # OK - self.assertEqual(TypeForm[int], TypeForm[int]) - - def foo(arg) -> TypeForm[int]: ... - self.assertEqual(gth(foo), {'return': TypeForm[int]}) - - def test_repr(self): - if hasattr(typing, 'TypeForm'): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(TypeForm), f'{mod_name}.TypeForm') - cv = TypeForm[int] - self.assertEqual(repr(cv), f'{mod_name}.TypeForm[int]') - cv = TypeForm[Employee] - self.assertEqual(repr(cv), f'{mod_name}.TypeForm[{__name__}.Employee]') - cv = TypeForm[Tuple[int]] - self.assertEqual(repr(cv), f'{mod_name}.TypeForm[typing.Tuple[int]]') - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(TypeForm)): - pass - with self.assertRaises(TypeError): - class D(type(TypeForm[int])): - pass - - def test_call(self): - objs = [ - 1, - "int", - int, - Tuple[int, str], - ] - for obj in objs: - with self.subTest(obj=obj): - self.assertIs(TypeForm(obj), obj) - - with self.assertRaises(TypeError): - TypeForm() - with self.assertRaises(TypeError): - TypeForm("too", "many") - - def test_cannot_init_type(self): - with self.assertRaises(TypeError): - type(TypeForm)() - with self.assertRaises(TypeError): - type(TypeForm[Optional[int]])() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, TypeForm[int]) - with self.assertRaises(TypeError): - issubclass(int, TypeForm) - - -class LiteralStringTests(BaseTestCase): - def test_basics(self): - class Foo: - def bar(self) -> LiteralString: ... - def baz(self) -> "LiteralString": ... - - self.assertEqual(gth(Foo.bar), {'return': LiteralString}) - self.assertEqual(gth(Foo.baz), {'return': LiteralString}) - - def test_get_origin(self): - self.assertIsNone(get_origin(LiteralString)) - - def test_repr(self): - if hasattr(typing, 'LiteralString'): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(LiteralString), f'{mod_name}.LiteralString') - - def test_cannot_subscript(self): - with self.assertRaises(TypeError): - LiteralString[int] - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(LiteralString)): - pass - with self.assertRaises(TypeError): - class D(LiteralString): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - LiteralString() - with self.assertRaises(TypeError): - type(LiteralString)() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, LiteralString) - with self.assertRaises(TypeError): - issubclass(int, LiteralString) - - def test_alias(self): - StringTuple = Tuple[LiteralString, LiteralString] - class Alias: - def return_tuple(self) -> StringTuple: - return ("foo", "pep" + "675") - - def test_typevar(self): - StrT = TypeVar("StrT", bound=LiteralString) - self.assertIs(StrT.__bound__, LiteralString) - - def test_pickle(self): - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - pickled = pickle.dumps(LiteralString, protocol=proto) - self.assertIs(LiteralString, pickle.loads(pickled)) - - -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), f'{mod_name}.Self') - - 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) - - def test_alias(self): - TupleSelf = Tuple[Self, Self] - class Alias: - def return_tuple(self) -> TupleSelf: - return (self, self) - - def test_pickle(self): - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - pickled = pickle.dumps(Self, protocol=proto) - self.assertIs(Self, pickle.loads(pickled)) - - -class UnpackTests(BaseTestCase): - def test_basic_plain(self): - Ts = TypeVarTuple('Ts') - self.assertEqual(Unpack[Ts], Unpack[Ts]) - with self.assertRaises(TypeError): - Unpack() - - def test_repr(self): - Ts = TypeVarTuple('Ts') - self.assertEqual(repr(Unpack[Ts]), f'{Unpack.__module__}.Unpack[Ts]') - - def test_cannot_subclass_vars(self): - with self.assertRaises(TypeError): - class V(Unpack[TypeVarTuple('Ts')]): - pass - - def test_tuple(self): - Ts = TypeVarTuple('Ts') - Tuple[Unpack[Ts]] - - def test_union(self): - Xs = TypeVarTuple('Xs') - Ys = TypeVarTuple('Ys') - self.assertEqual( - Union[Unpack[Xs]], - Unpack[Xs] - ) - self.assertNotEqual( - Union[Unpack[Xs]], - Union[Unpack[Xs], Unpack[Ys]] - ) - self.assertEqual( - Union[Unpack[Xs], Unpack[Xs]], - Unpack[Xs] - ) - self.assertNotEqual( - Union[Unpack[Xs], int], - Union[Unpack[Xs]] - ) - self.assertNotEqual( - Union[Unpack[Xs], int], - Union[int] - ) - self.assertEqual( - Union[Unpack[Xs], int].__args__, - (Unpack[Xs], int) - ) - self.assertEqual( - Union[Unpack[Xs], int].__parameters__, - (Xs,) - ) - self.assertIs( - Union[Unpack[Xs], int].__origin__, - Union - ) - - def test_concatenation(self): - Xs = TypeVarTuple('Xs') - self.assertEqual(Tuple[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) - self.assertEqual(Tuple[Unpack[Xs], int].__args__, (Unpack[Xs], int)) - self.assertEqual(Tuple[int, Unpack[Xs], str].__args__, - (int, Unpack[Xs], str)) - class C(Generic[Unpack[Xs]]): pass - class D(Protocol[Unpack[Xs]]): pass - for klass in C, D: - with self.subTest(klass=klass.__name__): - self.assertEqual(klass[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) - self.assertEqual(klass[Unpack[Xs], int].__args__, (Unpack[Xs], int)) - self.assertEqual(klass[int, Unpack[Xs], str].__args__, - (int, Unpack[Xs], str)) - - def test_class(self): - Ts = TypeVarTuple('Ts') - - class C(Generic[Unpack[Ts]]): pass - class D(Protocol[Unpack[Ts]]): pass - - for klass in C, D: - with self.subTest(klass=klass.__name__): - self.assertEqual(klass[int].__args__, (int,)) - self.assertEqual(klass[int, str].__args__, (int, str)) - - with self.assertRaises(TypeError): - class C(Generic[Unpack[Ts], int]): pass - - with self.assertRaises(TypeError): - class D(Protocol[Unpack[Ts], int]): pass - - T1 = TypeVar('T') - T2 = TypeVar('T') - class C(Generic[T1, T2, Unpack[Ts]]): pass - class D(Protocol[T1, T2, Unpack[Ts]]): pass - for klass in C, D: - with self.subTest(klass=klass.__name__): - self.assertEqual(klass[int, str].__args__, (int, str)) - self.assertEqual(klass[int, str, float].__args__, (int, str, float)) - self.assertEqual( - klass[int, str, float, bool].__args__, (int, str, float, bool) - ) - # A bug was fixed in 3.11.1 - # (https://github.com/python/cpython/commit/74920aa27d0c57443dd7f704d6272cca9c507ab3) - # That means this assertion doesn't pass on 3.11.0, - # but it passes on all other Python versions - if sys.version_info[:3] != (3, 11, 0): - with self.assertRaises(TypeError): - klass[int] - - def test_substitution(self): - Ts = TypeVarTuple("Ts") - unpacked_str = Unpack[Ts][str] # This should not raise an error - self.assertIs(unpacked_str, str) - - @skipUnless(TYPING_3_11_0, "Needs Issue #103 for <3.11") - def test_nested_unpack(self): - Ts = TypeVarTuple("Ts") - Variadic = Tuple[int, Unpack[Ts]] - # Tuple[int, int, Tuple[str, int]] - direct_subscription = Variadic[int, Tuple[str, int]] - # Tuple[int, int, Tuple[*Ts, int]] - TupleAliasTs = Variadic[int, Tuple[Unpack[Ts], int]] - - # Tuple[int, int, Tuple[str, int]] - recursive_unpack = TupleAliasTs[str] - self.assertEqual(direct_subscription, recursive_unpack) - self.assertEqual(get_args(recursive_unpack), (int, int, Tuple[str, int])) - - # Test with Callable - T = TypeVar("T") - # Tuple[int, (*Ts) -> T] - CallableAliasTsT = Variadic[Callable[[Unpack[Ts]], T]] - # Tuple[int, (str, int) -> object] - callable_fully_subscripted = CallableAliasTsT[Unpack[Tuple[str, int]], object] - self.assertEqual(get_args(callable_fully_subscripted), (int, Callable[[str, int], object])) - - @skipUnless(TYPING_3_11_0, "Needs Issue #103 for <3.11") - def test_equivalent_nested_variadics(self): - T = TypeVar("T") - Ts = TypeVarTuple("Ts") - Variadic = Tuple[int, Unpack[Ts]] - TupleAliasTsT = Variadic[Tuple[Unpack[Ts], T]] - nested_tuple_bare = TupleAliasTsT[str, int, object] - - self.assertEqual(get_args(nested_tuple_bare), (int, Tuple[str, int, object])) - # Variants - self.assertEqual(nested_tuple_bare, TupleAliasTsT[Unpack[Tuple[str, int, object]]]) - self.assertEqual(nested_tuple_bare, TupleAliasTsT[Unpack[Tuple[str, int]], object]) - self.assertEqual(nested_tuple_bare, TupleAliasTsT[Unpack[Tuple[str]], Unpack[Tuple[int]], object]) - - @skipUnless(TYPING_3_11_0, "Needed for backport") - def test_type_var_inheritance(self): - Ts = TypeVarTuple("Ts") - self.assertFalse(isinstance(Unpack[Ts], TypeVar)) - self.assertFalse(isinstance(Unpack[Ts], typing.TypeVar)) - - -class TypeVarTupleTests(BaseTestCase): - - def test_basic_plain(self): - Ts = TypeVarTuple('Ts') - self.assertEqual(Ts, Ts) - self.assertIsInstance(Ts, TypeVarTuple) - Xs = TypeVarTuple('Xs') - Ys = TypeVarTuple('Ys') - self.assertNotEqual(Xs, Ys) - - def test_repr(self): - Ts = TypeVarTuple('Ts') - self.assertEqual(repr(Ts), 'Ts') - - def test_no_redefinition(self): - self.assertNotEqual(TypeVarTuple('Ts'), TypeVarTuple('Ts')) - - def test_cannot_subclass_vars(self): - with self.assertRaises(TypeError): - class V(TypeVarTuple('Ts')): - pass - - def test_cannot_subclass_var_itself(self): - with self.assertRaises(TypeError): - class V(TypeVarTuple): - pass - - def test_cannot_instantiate_vars(self): - Ts = TypeVarTuple('Ts') - with self.assertRaises(TypeError): - Ts() - - def test_tuple(self): - Ts = TypeVarTuple('Ts') - # Not legal at type checking time but we can't really check against it. - Tuple[Ts] - - def test_args_and_parameters(self): - Ts = TypeVarTuple('Ts') - - t = Tuple[tuple(Ts)] - if sys.version_info >= (3, 11): - self.assertEqual(t.__args__, (typing.Unpack[Ts],)) - else: - self.assertEqual(t.__args__, (Unpack[Ts],)) - self.assertEqual(t.__parameters__, (Ts,)) - - def test_pickle(self): - global Ts, Ts_default # pickle wants to reference the class by name - Ts = TypeVarTuple('Ts') - Ts_default = TypeVarTuple('Ts_default', default=Unpack[Tuple[int, str]]) - - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - for typevartuple in (Ts, Ts_default): - z = pickle.loads(pickle.dumps(typevartuple, proto)) - self.assertEqual(z.__name__, typevartuple.__name__) - self.assertEqual(z.__default__, typevartuple.__default__) - - -class FinalDecoratorTests(BaseTestCase): - def test_final_unmodified(self): - def func(x): ... - self.assertIs(func, final(func)) - - def test_dunder_final(self): - @final - def func(): ... - @final - class Cls: ... - self.assertIs(True, func.__final__) - self.assertIs(True, Cls.__final__) - - class Wrapper: - __slots__ = ("func",) - def __init__(self, func): - self.func = func - def __call__(self, *args, **kwargs): - return self.func(*args, **kwargs) - - # Check that no error is thrown if the attribute - # is not writable. - @final - @Wrapper - def wrapped(): ... - self.assertIsInstance(wrapped, Wrapper) - self.assertIs(False, hasattr(wrapped, "__final__")) - - class Meta(type): - @property - def __final__(self): return "can't set me" - @final - class WithMeta(metaclass=Meta): ... - self.assertEqual(WithMeta.__final__, "can't set me") - - # Builtin classes throw TypeError if you try to set an - # attribute. - final(int) - self.assertIs(False, hasattr(int, "__final__")) - - # Make sure it works with common builtin decorators - class Methods: - @final - @classmethod - def clsmethod(cls): ... - - @final - @staticmethod - def stmethod(): ... - - # The other order doesn't work because property objects - # don't allow attribute assignment. - @property - @final - def prop(self): ... - - @final - @lru_cache # noqa: B019 - def cached(self): ... - - # Use getattr_static because the descriptor returns the - # underlying function, which doesn't have __final__. - self.assertIs( - True, - inspect.getattr_static(Methods, "clsmethod").__final__ - ) - self.assertIs( - True, - inspect.getattr_static(Methods, "stmethod").__final__ - ) - self.assertIs(True, Methods.prop.fget.__final__) - self.assertIs(True, Methods.cached.__final__) - - -class DisjointBaseTests(BaseTestCase): - def test_disjoint_base_unmodified(self): - class C: ... - self.assertIs(C, disjoint_base(C)) - - def test_dunder_disjoint_base(self): - @disjoint_base - class C: ... - - self.assertIs(C.__disjoint_base__, True) - - -class RevealTypeTests(BaseTestCase): - def test_reveal_type(self): - obj = object() - - with contextlib.redirect_stderr(io.StringIO()) as stderr: - self.assertIs(obj, reveal_type(obj)) - self.assertEqual("Runtime type is 'object'", stderr.getvalue().strip()) - - -class DataclassTransformTests(BaseTestCase): - def test_decorator(self): - def create_model(*, frozen: bool = False, kw_only: bool = True): - return lambda cls: cls - - decorated = dataclass_transform(kw_only_default=True, order_default=False)(create_model) - - class CustomerModel: - id: int - - self.assertIs(decorated, create_model) - self.assertEqual( - decorated.__dataclass_transform__, - { - "eq_default": True, - "order_default": False, - "kw_only_default": True, - "frozen_default": False, - "field_specifiers": (), - "kwargs": {}, - } - ) - self.assertIs( - decorated(frozen=True, kw_only=False)(CustomerModel), - CustomerModel - ) - - def test_base_class(self): - class ModelBase: - def __init_subclass__(cls, *, frozen: bool = False): ... - - Decorated = dataclass_transform( - eq_default=True, - order_default=True, - # Arbitrary unrecognized kwargs are accepted at runtime. - make_everything_awesome=True, - )(ModelBase) - - class CustomerModel(Decorated, frozen=True): - id: int - - self.assertIs(Decorated, ModelBase) - self.assertEqual( - Decorated.__dataclass_transform__, - { - "eq_default": True, - "order_default": True, - "kw_only_default": False, - "frozen_default": False, - "field_specifiers": (), - "kwargs": {"make_everything_awesome": True}, - } - ) - self.assertIsSubclass(CustomerModel, Decorated) - - def test_metaclass(self): - class Field: ... - - class ModelMeta(type): - def __new__( - cls, name, bases, namespace, *, init: bool = True, - ): - return super().__new__(cls, name, bases, namespace) - - Decorated = dataclass_transform( - order_default=True, field_specifiers=(Field,) - )(ModelMeta) - - class ModelBase(metaclass=Decorated): ... - - class CustomerModel(ModelBase, init=False): - id: int - - self.assertIs(Decorated, ModelMeta) - self.assertEqual( - Decorated.__dataclass_transform__, - { - "eq_default": True, - "order_default": True, - "kw_only_default": False, - "frozen_default": False, - "field_specifiers": (Field,), - "kwargs": {}, - } - ) - self.assertIsInstance(CustomerModel, Decorated) - - -class AllTests(BaseTestCase): - - def test_drop_in_for_typing(self): - # Check that the typing_extensions.__all__ is a superset of - # typing.__all__. - t_all = set(typing.__all__) - te_all = set(typing_extensions.__all__) - exceptions = {"ByteString"} - self.assertGreaterEqual(te_all, t_all - exceptions) - # Deprecated, to be removed in 3.14 - self.assertFalse(hasattr(typing_extensions, "ByteString")) - # These were never included in `typing.__all__`, - # and have been removed in Python 3.13 - self.assertNotIn('re', te_all) - self.assertNotIn('io', te_all) - - def test_typing_extensions_includes_standard(self): - a = typing_extensions.__all__ - self.assertIn('ClassVar', a) - self.assertIn('Type', a) - self.assertIn('ChainMap', a) - self.assertIn('ContextManager', a) - self.assertIn('Counter', a) - self.assertIn('DefaultDict', a) - self.assertIn('Deque', a) - self.assertIn('NewType', a) - self.assertIn('overload', a) - self.assertIn('Text', a) - self.assertIn('TYPE_CHECKING', a) - self.assertIn('TypeAlias', a) - self.assertIn('ParamSpec', a) - self.assertIn("Concatenate", a) - - self.assertIn('Annotated', a) - self.assertIn('get_type_hints', a) - - self.assertIn('Awaitable', a) - self.assertIn('AsyncIterator', a) - self.assertIn('AsyncIterable', a) - self.assertIn('Coroutine', a) - self.assertIn('AsyncContextManager', a) - - self.assertIn('AsyncGenerator', a) - - self.assertIn('Protocol', a) - self.assertIn('runtime', a) - - # Check that all objects in `__all__` are present in the module - for name in a: - self.assertTrue(hasattr(typing_extensions, name)) - - def test_all_names_in___all__(self): - exclude = { - 'GenericMeta', - 'KT', - 'PEP_560', - 'T', - 'T_co', - 'T_contra', - 'VT', - } - actual_names = { - name for name in dir(typing_extensions) - if not name.startswith("_") - and not isinstance(getattr(typing_extensions, name), types.ModuleType) - } - # Make sure all public names are in __all__ - self.assertEqual({*exclude, *typing_extensions.__all__}, - actual_names) - # Make sure all excluded names actually exist - self.assertLessEqual(exclude, actual_names) - - def test_typing_extensions_defers_when_possible(self): - exclude = set() - if sys.version_info < (3, 10): - exclude |= {'get_args', 'get_origin'} - if sys.version_info < (3, 10, 1): - exclude |= {"Literal"} - if sys.version_info < (3, 11): - exclude |= {'final', 'Any', 'NewType', 'overload', 'Concatenate'} - if sys.version_info < (3, 12): - exclude |= { - 'SupportsAbs', 'SupportsBytes', - 'SupportsComplex', 'SupportsFloat', 'SupportsIndex', 'SupportsInt', - 'SupportsRound', 'Unpack', 'dataclass_transform', - } - if sys.version_info < (3, 13): - exclude |= { - 'NamedTuple', 'Protocol', 'runtime_checkable', 'Generator', - 'AsyncGenerator', 'ContextManager', 'AsyncContextManager', - 'ParamSpec', 'TypeVar', 'TypeVarTuple', 'get_type_hints', - } - if sys.version_info < (3, 14): - exclude |= { - 'TypeAliasType' - } - if not typing_extensions._PEP_728_IMPLEMENTED: - exclude |= {'TypedDict', 'is_typeddict'} - for item in typing_extensions.__all__: - if item not in exclude and hasattr(typing, item): - self.assertIs( - getattr(typing_extensions, item), - getattr(typing, item)) - - def test_alias_names_still_exist(self): - for name in typing_extensions._typing_names: - # If this fails, change _typing_names to conditionally add the name - # depending on the Python version. - self.assertTrue( - hasattr(typing_extensions, name), - f"{name} no longer exists in typing", - ) - - def test_typing_extensions_compiles_with_opt(self): - file_path = typing_extensions.__file__ - try: - subprocess.check_output(f'{sys.executable} -OO {file_path}', - stderr=subprocess.STDOUT, - shell=True) - except subprocess.CalledProcessError: - self.fail('Module does not compile with optimize=2 (-OO flag).') - - -class CoolEmployee(NamedTuple): - name: str - cool: int - - -class CoolEmployeeWithDefault(NamedTuple): - name: str - cool: int = 0 - - -class XMeth(NamedTuple): - x: int - - def double(self): - return 2 * self.x - - -class NamedTupleTests(BaseTestCase): - class NestedEmployee(NamedTuple): - name: str - cool: int - - def test_basics(self): - Emp = NamedTuple('Emp', [('name', str), ('id', int)]) - self.assertIsSubclass(Emp, tuple) - joe = Emp('Joe', 42) - jim = Emp(name='Jim', id=1) - self.assertIsInstance(joe, Emp) - self.assertIsInstance(joe, tuple) - self.assertEqual(joe.name, 'Joe') - self.assertEqual(joe.id, 42) - self.assertEqual(jim.name, 'Jim') - self.assertEqual(jim.id, 1) - self.assertEqual(Emp.__name__, 'Emp') - self.assertEqual(Emp._fields, ('name', 'id')) - self.assertEqual(Emp.__annotations__, - collections.OrderedDict([('name', str), ('id', int)])) - - def test_annotation_usage(self): - tim = CoolEmployee('Tim', 9000) - self.assertIsInstance(tim, CoolEmployee) - self.assertIsInstance(tim, tuple) - self.assertEqual(tim.name, 'Tim') - self.assertEqual(tim.cool, 9000) - self.assertEqual(CoolEmployee.__name__, 'CoolEmployee') - self.assertEqual(CoolEmployee._fields, ('name', 'cool')) - self.assertEqual(CoolEmployee.__annotations__, - collections.OrderedDict(name=str, cool=int)) - - def test_annotation_usage_with_default(self): - jelle = CoolEmployeeWithDefault('Jelle') - self.assertIsInstance(jelle, CoolEmployeeWithDefault) - self.assertIsInstance(jelle, tuple) - self.assertEqual(jelle.name, 'Jelle') - self.assertEqual(jelle.cool, 0) - cooler_employee = CoolEmployeeWithDefault('Sjoerd', 1) - self.assertEqual(cooler_employee.cool, 1) - - self.assertEqual(CoolEmployeeWithDefault.__name__, 'CoolEmployeeWithDefault') - self.assertEqual(CoolEmployeeWithDefault._fields, ('name', 'cool')) - self.assertEqual(CoolEmployeeWithDefault.__annotations__, - dict(name=str, cool=int)) - - with self.assertRaisesRegex( - TypeError, - 'Non-default namedtuple field y cannot follow default field x' - ): - class NonDefaultAfterDefault(NamedTuple): - x: int = 3 - y: int - - def test_field_defaults(self): - self.assertEqual(CoolEmployeeWithDefault._field_defaults, dict(cool=0)) - - def test_annotation_usage_with_methods(self): - self.assertEqual(XMeth(1).double(), 2) - self.assertEqual(XMeth(42).x, XMeth(42)[0]) - self.assertEqual(str(XRepr(42)), '42 -> 1') - self.assertEqual(XRepr(1, 2) + XRepr(3), 0) - - bad_overwrite_error_message = 'Cannot overwrite NamedTuple attribute' - - with self.assertRaisesRegex(AttributeError, bad_overwrite_error_message): - class XMethBad(NamedTuple): - x: int - def _fields(self): - return 'no chance for this' - - with self.assertRaisesRegex(AttributeError, bad_overwrite_error_message): - class XMethBad2(NamedTuple): - x: int - def _source(self): - return 'no chance for this as well' - - def test_multiple_inheritance(self): - class A: - pass - with self.assertRaisesRegex( - TypeError, - 'can only inherit from a NamedTuple type and Generic' - ): - class X(NamedTuple, A): - x: int - - with self.assertRaisesRegex( - TypeError, - 'can only inherit from a NamedTuple type and Generic' - ): - class Y(NamedTuple, tuple): - x: int - - with self.assertRaisesRegex(TypeError, 'duplicate base class'): - class Z(NamedTuple, NamedTuple): - x: int - - class A(NamedTuple): - x: int - with self.assertRaisesRegex( - TypeError, - 'can only inherit from a NamedTuple type and Generic' - ): - class XX(NamedTuple, A): - y: str - - def test_generic(self): - class X(NamedTuple, Generic[T]): - x: T - self.assertEqual(X.__bases__, (tuple, Generic)) - self.assertEqual(X.__orig_bases__, (NamedTuple, Generic[T])) - self.assertEqual(X.__mro__, (X, tuple, Generic, object)) - - class Y(Generic[T], NamedTuple): - x: T - self.assertEqual(Y.__bases__, (Generic, tuple)) - self.assertEqual(Y.__orig_bases__, (Generic[T], NamedTuple)) - self.assertEqual(Y.__mro__, (Y, Generic, tuple, object)) - - for G in X, Y: - with self.subTest(type=G): - self.assertEqual(G.__parameters__, (T,)) - A = G[int] - self.assertIs(A.__origin__, G) - self.assertEqual(A.__args__, (int,)) - self.assertEqual(A.__parameters__, ()) - - a = A(3) - self.assertIs(type(a), G) - self.assertIsInstance(a, G) - self.assertEqual(a.x, 3) - - things = "arguments" if sys.version_info >= (3, 10) else "parameters" - with self.assertRaisesRegex(TypeError, f'Too many {things}'): - G[int, str] - - def test_non_generic_subscript_py39_plus(self): - # For backward compatibility, subscription works - # on arbitrary NamedTuple types. - class Group(NamedTuple): - key: T - group: list[T] - A = Group[int] - self.assertEqual(A.__origin__, Group) - self.assertEqual(A.__parameters__, ()) - self.assertEqual(A.__args__, (int,)) - a = A(1, [2]) - self.assertIs(type(a), Group) - self.assertEqual(a, (1, [2])) - - @skipUnless(sys.version_info <= (3, 15), "Behavior removed in 3.15") - def test_namedtuple_keyword_usage(self): - with self.assertWarnsRegex( - DeprecationWarning, - "Creating NamedTuple classes using keyword arguments is deprecated" - ): - LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int) - - nick = LocalEmployee('Nick', 25) - self.assertIsInstance(nick, tuple) - self.assertEqual(nick.name, 'Nick') - self.assertEqual(LocalEmployee.__name__, 'LocalEmployee') - self.assertEqual(LocalEmployee._fields, ('name', 'age')) - self.assertEqual(LocalEmployee.__annotations__, dict(name=str, age=int)) - - with self.assertRaisesRegex( - TypeError, - "Either list of fields or keywords can be provided to NamedTuple, not both" - ): - NamedTuple('Name', [('x', int)], y=str) - - with self.assertRaisesRegex( - TypeError, - "Either list of fields or keywords can be provided to NamedTuple, not both" - ): - NamedTuple('Name', [], y=str) - - with self.assertRaisesRegex( - TypeError, - ( - r"Cannot pass `None` as the 'fields' parameter " - r"and also specify fields using keyword arguments" - ) - ): - NamedTuple('Name', None, x=int) - - @skipUnless(sys.version_info <= (3, 15), "Behavior removed in 3.15") - def test_namedtuple_special_keyword_names(self): - with self.assertWarnsRegex( - DeprecationWarning, - "Creating NamedTuple classes using keyword arguments is deprecated" - ): - NT = NamedTuple("NT", cls=type, self=object, typename=str, fields=list) - - self.assertEqual(NT.__name__, 'NT') - self.assertEqual(NT._fields, ('cls', 'self', 'typename', 'fields')) - a = NT(cls=str, self=42, typename='foo', fields=[('bar', tuple)]) - self.assertEqual(a.cls, str) - self.assertEqual(a.self, 42) - self.assertEqual(a.typename, 'foo') - self.assertEqual(a.fields, [('bar', tuple)]) - - @skipUnless(sys.version_info <= (3, 15), "Behavior removed in 3.15") - def test_empty_namedtuple(self): - expected_warning = re.escape( - "Failing to pass a value for the 'fields' parameter is deprecated " - "and will be disallowed in Python 3.15. " - "To create a NamedTuple class with 0 fields " - "using the functional syntax, " - "pass an empty list, e.g. `NT1 = NamedTuple('NT1', [])`." - ) - with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"): - NT1 = NamedTuple('NT1') - - expected_warning = re.escape( - "Passing `None` as the 'fields' parameter is deprecated " - "and will be disallowed in Python 3.15. " - "To create a NamedTuple class with 0 fields " - "using the functional syntax, " - "pass an empty list, e.g. `NT2 = NamedTuple('NT2', [])`." - ) - with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"): - NT2 = NamedTuple('NT2', None) - - NT3 = NamedTuple('NT2', []) - - class CNT(NamedTuple): - pass # empty body - - for struct in NT1, NT2, NT3, CNT: - with self.subTest(struct=struct): - self.assertEqual(struct._fields, ()) - self.assertEqual(struct.__annotations__, {}) - self.assertIsInstance(struct(), struct) - self.assertEqual(struct._field_defaults, {}) - - def test_namedtuple_errors(self): - with self.assertRaises(TypeError): - NamedTuple.__new__() - with self.assertRaises(TypeError): - NamedTuple() - with self.assertRaises(TypeError): - NamedTuple('Emp', [('name', str)], None) - with self.assertRaisesRegex(ValueError, 'cannot start with an underscore'): - NamedTuple('Emp', [('_name', str)]) - with self.assertRaises(TypeError): - NamedTuple(typename='Emp', name=str, id=int) - - def test_copy_and_pickle(self): - global Emp # pickle wants to reference the class by name - Emp = NamedTuple('Emp', [('name', str), ('cool', int)]) - for cls in Emp, CoolEmployee, self.NestedEmployee: - with self.subTest(cls=cls): - jane = cls('jane', 37) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - z = pickle.dumps(jane, proto) - jane2 = pickle.loads(z) - self.assertEqual(jane2, jane) - self.assertIsInstance(jane2, cls) - - jane2 = copy.copy(jane) - self.assertEqual(jane2, jane) - self.assertIsInstance(jane2, cls) - - jane2 = copy.deepcopy(jane) - self.assertEqual(jane2, jane) - self.assertIsInstance(jane2, cls) - - def test_docstring(self): - self.assertIsInstance(NamedTuple.__doc__, str) - - def test_same_as_typing_NamedTuple(self): - self.assertEqual( - set(dir(NamedTuple)) - {"__text_signature__"}, - set(dir(typing.NamedTuple)) - ) - self.assertIs(type(NamedTuple), type(typing.NamedTuple)) - - def test_orig_bases(self): - T = TypeVar('T') - - class SimpleNamedTuple(NamedTuple): - pass - - class GenericNamedTuple(NamedTuple, Generic[T]): - pass - - self.assertEqual(SimpleNamedTuple.__orig_bases__, (NamedTuple,)) - self.assertEqual(GenericNamedTuple.__orig_bases__, (NamedTuple, Generic[T])) - - CallNamedTuple = NamedTuple('CallNamedTuple', []) - - self.assertEqual(CallNamedTuple.__orig_bases__, (NamedTuple,)) - - def test_setname_called_on_values_in_class_dictionary(self): - class Vanilla: - def __set_name__(self, owner, name): - self.name = name - - class Foo(NamedTuple): - attr = Vanilla() - - foo = Foo() - self.assertEqual(len(foo), 0) - self.assertNotIn('attr', Foo._fields) - self.assertIsInstance(foo.attr, Vanilla) - self.assertEqual(foo.attr.name, "attr") - - class Bar(NamedTuple): - attr: Vanilla = Vanilla() - - bar = Bar() - self.assertEqual(len(bar), 1) - self.assertIn('attr', Bar._fields) - self.assertIsInstance(bar.attr, Vanilla) - self.assertEqual(bar.attr.name, "attr") - - @skipIf( - TYPING_3_12_0, - "__set_name__ behaviour changed on py312+ to use BaseException.add_note()" - ) - def test_setname_raises_the_same_as_on_other_classes_py311_minus(self): - class CustomException(BaseException): pass - - class Annoying: - def __set_name__(self, owner, name): - raise CustomException - - annoying = Annoying() - - with self.assertRaises(RuntimeError) as cm: - class NormalClass: - attr = annoying - normal_exception = cm.exception - - with self.assertRaises(RuntimeError) as cm: - class NamedTupleClass(NamedTuple): - attr = annoying - namedtuple_exception = cm.exception - - self.assertIs(type(namedtuple_exception), RuntimeError) - self.assertIs(type(namedtuple_exception), type(normal_exception)) - self.assertEqual(len(namedtuple_exception.args), len(normal_exception.args)) - self.assertEqual( - namedtuple_exception.args[0], - normal_exception.args[0].replace("NormalClass", "NamedTupleClass") - ) - - self.assertIs(type(namedtuple_exception.__cause__), CustomException) - self.assertIs( - type(namedtuple_exception.__cause__), type(normal_exception.__cause__) - ) - self.assertEqual( - namedtuple_exception.__cause__.args, normal_exception.__cause__.args - ) - - @skipUnless( - TYPING_3_12_0, - "__set_name__ behaviour changed on py312+ to use BaseException.add_note()" - ) - def test_setname_raises_the_same_as_on_other_classes_py312_plus(self): - class CustomException(BaseException): pass - - class Annoying: - def __set_name__(self, owner, name): - raise CustomException - - annoying = Annoying() - - with self.assertRaises(CustomException) as cm: - class NormalClass: - attr = annoying - normal_exception = cm.exception - - with self.assertRaises(CustomException) as cm: - class NamedTupleClass(NamedTuple): - attr = annoying - namedtuple_exception = cm.exception - - expected_note = ( - "Error calling __set_name__ on 'Annoying' instance " - "'attr' in 'NamedTupleClass'" - ) - - self.assertIs(type(namedtuple_exception), CustomException) - self.assertIs(type(namedtuple_exception), type(normal_exception)) - self.assertEqual(namedtuple_exception.args, normal_exception.args) - - self.assertEqual(len(namedtuple_exception.__notes__), 1) - self.assertEqual( - len(namedtuple_exception.__notes__), len(normal_exception.__notes__) - ) - - self.assertEqual(namedtuple_exception.__notes__[0], expected_note) - self.assertEqual( - namedtuple_exception.__notes__[0], - normal_exception.__notes__[0].replace("NormalClass", "NamedTupleClass") - ) - - def test_strange_errors_when_accessing_set_name_itself(self): - class CustomException(Exception): pass - - class Meta(type): - def __getattribute__(self, attr): - if attr == "__set_name__": - raise CustomException - return object.__getattribute__(self, attr) - - class VeryAnnoying(metaclass=Meta): pass - - very_annoying = VeryAnnoying() - - with self.assertRaises(CustomException): - class Foo(NamedTuple): - attr = very_annoying - - -class TypeVarTests(BaseTestCase): - def test_basic_plain(self): - T = TypeVar('T') - # T equals itself. - self.assertEqual(T, T) - # T is an instance of TypeVar - self.assertIsInstance(T, TypeVar) - self.assertEqual(T.__name__, 'T') - self.assertEqual(T.__constraints__, ()) - self.assertIs(T.__bound__, None) - self.assertIs(T.__covariant__, False) - self.assertIs(T.__contravariant__, False) - self.assertIs(T.__infer_variance__, False) - - def test_attributes(self): - T_bound = TypeVar('T_bound', bound=int) - self.assertEqual(T_bound.__name__, 'T_bound') - self.assertEqual(T_bound.__constraints__, ()) - self.assertIs(T_bound.__bound__, int) - - T_constraints = TypeVar('T_constraints', int, str) - self.assertEqual(T_constraints.__name__, 'T_constraints') - self.assertEqual(T_constraints.__constraints__, (int, str)) - self.assertIs(T_constraints.__bound__, None) - - T_co = TypeVar('T_co', covariant=True) - self.assertEqual(T_co.__name__, 'T_co') - self.assertIs(T_co.__covariant__, True) - self.assertIs(T_co.__contravariant__, False) - self.assertIs(T_co.__infer_variance__, False) - - T_contra = TypeVar('T_contra', contravariant=True) - self.assertEqual(T_contra.__name__, 'T_contra') - self.assertIs(T_contra.__covariant__, False) - self.assertIs(T_contra.__contravariant__, True) - self.assertIs(T_contra.__infer_variance__, False) - - T_infer = TypeVar('T_infer', infer_variance=True) - self.assertEqual(T_infer.__name__, 'T_infer') - self.assertIs(T_infer.__covariant__, False) - self.assertIs(T_infer.__contravariant__, False) - self.assertIs(T_infer.__infer_variance__, True) - - def test_typevar_instance_type_error(self): - T = TypeVar('T') - with self.assertRaises(TypeError): - isinstance(42, T) - - def test_typevar_subclass_type_error(self): - T = TypeVar('T') - with self.assertRaises(TypeError): - issubclass(int, T) - with self.assertRaises(TypeError): - issubclass(T, int) - - def test_constrained_error(self): - with self.assertRaises(TypeError): - X = TypeVar('X', int) - X - - def test_union_unique(self): - X = TypeVar('X') - Y = TypeVar('Y') - self.assertNotEqual(X, Y) - self.assertEqual(Union[X], X) - self.assertNotEqual(Union[X], Union[X, Y]) - self.assertEqual(Union[X, X], X) - self.assertNotEqual(Union[X, int], Union[X]) - self.assertNotEqual(Union[X, int], Union[int]) - self.assertEqual(Union[X, int].__args__, (X, int)) - self.assertEqual(Union[X, int].__parameters__, (X,)) - self.assertIs(Union[X, int].__origin__, Union) - - if hasattr(types, "UnionType"): - def test_or(self): - X = TypeVar('X') - # use a string because str doesn't implement - # __or__/__ror__ itself - self.assertEqual(X | "x", Union[X, "x"]) - self.assertEqual("x" | X, Union["x", X]) - # make sure the order is correct - self.assertEqual(get_args(X | "x"), (X, EqualToForwardRef("x"))) - self.assertEqual(get_args("x" | X), (EqualToForwardRef("x"), X)) - - def test_union_constrained(self): - A = TypeVar('A', str, bytes) - self.assertNotEqual(Union[A, str], Union[A]) - - def test_repr(self): - self.assertEqual(repr(T), '~T') - self.assertEqual(repr(KT), '~KT') - self.assertEqual(repr(VT), '~VT') - self.assertEqual(repr(AnyStr), '~AnyStr') - T_co = TypeVar('T_co', covariant=True) - self.assertEqual(repr(T_co), '+T_co') - T_contra = TypeVar('T_contra', contravariant=True) - self.assertEqual(repr(T_contra), '-T_contra') - - def test_no_redefinition(self): - self.assertNotEqual(TypeVar('T'), TypeVar('T')) - self.assertNotEqual(TypeVar('T', int, str), TypeVar('T', int, str)) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class V(TypeVar): pass - T = TypeVar("T") - with self.assertRaises(TypeError): - class W(T): pass - - def test_cannot_instantiate_vars(self): - with self.assertRaises(TypeError): - TypeVar('A')() - - def test_bound_errors(self): - with self.assertRaises(TypeError): - TypeVar('X', bound=Optional) - with self.assertRaises(TypeError): - TypeVar('X', str, float, bound=Employee) - with self.assertRaisesRegex(TypeError, - r"Bound must be a type\. Got \(1, 2\)\."): - TypeVar('X', bound=(1, 2)) - - def test_missing__name__(self): - # See https://github.com/python/cpython/issues/84123 - code = ("import typing\n" - "T = typing.TypeVar('T')\n" - ) - exec(code, {}) - - def test_no_bivariant(self): - with self.assertRaises(ValueError): - TypeVar('T', covariant=True, contravariant=True) - - def test_cannot_combine_explicit_and_infer(self): - with self.assertRaises(ValueError): - TypeVar('T', covariant=True, infer_variance=True) - with self.assertRaises(ValueError): - TypeVar('T', contravariant=True, infer_variance=True) - - -class TypeVarLikeDefaultsTests(BaseTestCase): - def test_typevar(self): - T = typing_extensions.TypeVar('T', default=int) - typing_T = typing.TypeVar('T') - self.assertEqual(T.__default__, int) - self.assertIsInstance(T, typing_extensions.TypeVar) - self.assertIsInstance(T, typing.TypeVar) - self.assertIsInstance(typing_T, typing.TypeVar) - self.assertIsInstance(typing_T, typing_extensions.TypeVar) - - class A(Generic[T]): ... - self.assertEqual(Optional[T].__args__, (T, type(None))) - - def test_typevar_none(self): - U = typing_extensions.TypeVar('U') - U_None = typing_extensions.TypeVar('U_None', default=None) - self.assertIs(U.__default__, NoDefault) - self.assertFalse(U.has_default()) - self.assertEqual(U_None.__default__, None) - self.assertTrue(U_None.has_default()) - - def test_paramspec(self): - P = ParamSpec('P', default=[str, int]) - self.assertEqual(P.__default__, [str, int]) - self.assertTrue(P.has_default()) - self.assertIsInstance(P, ParamSpec) - if hasattr(typing, "ParamSpec"): - self.assertIsInstance(P, typing.ParamSpec) - typing_P = typing.ParamSpec('P') - self.assertIsInstance(typing_P, typing.ParamSpec) - self.assertIsInstance(typing_P, ParamSpec) - - class A(Generic[P]): ... - self.assertEqual(typing.Callable[P, None].__args__, (P, type(None))) - - P_default = ParamSpec('P_default', default=...) - self.assertIs(P_default.__default__, ...) - self.assertTrue(P_default.has_default()) - - def test_paramspec_none(self): - U = ParamSpec('U') - U_None = ParamSpec('U_None', default=None) - self.assertIs(U.__default__, NoDefault) - self.assertFalse(U.has_default()) - self.assertIs(U_None.__default__, None) - self.assertTrue(U_None.has_default()) - - def test_typevartuple(self): - Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]]) - self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]]) - self.assertIsInstance(Ts, TypeVarTuple) - self.assertTrue(Ts.has_default()) - if hasattr(typing, "TypeVarTuple"): - self.assertIsInstance(Ts, typing.TypeVarTuple) - typing_Ts = typing.TypeVarTuple('Ts') - self.assertIsInstance(typing_Ts, typing.TypeVarTuple) - self.assertIsInstance(typing_Ts, TypeVarTuple) - - class A(Generic[Unpack[Ts]]): ... - self.assertEqual(Optional[Unpack[Ts]].__args__, (Unpack[Ts], type(None))) - - @skipIf( - sys.version_info < (3, 11, 1), - "Not yet backported for older versions of Python" - ) - def test_typevartuple_specialization(self): - T = TypeVar("T") - Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]]) - self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]]) - class A(Generic[T, Unpack[Ts]]): ... - self.assertEqual(A[float].__args__, (float, str, int)) - self.assertEqual(A[float, range].__args__, (float, range)) - self.assertEqual(A[float, Unpack[tuple[int, ...]]].__args__, (float, Unpack[tuple[int, ...]])) - - @skipIf( - sys.version_info < (3, 11, 1), - "Not yet backported for older versions of Python" - ) - def test_typevar_and_typevartuple_specialization(self): - T = TypeVar("T") - U = TypeVar("U", default=float) - Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]]) - self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]]) - class A(Generic[T, U, Unpack[Ts]]): ... - self.assertEqual(A[int].__args__, (int, float, str, int)) - self.assertEqual(A[int, str].__args__, (int, str, str, int)) - self.assertEqual(A[int, str, range].__args__, (int, str, range)) - self.assertEqual(A[int, str, Unpack[tuple[int, ...]]].__args__, (int, str, Unpack[tuple[int, ...]])) - - def test_no_default_after_typevar_tuple(self): - T = TypeVar("T", default=int) - Ts = TypeVarTuple("Ts") - Ts_default = TypeVarTuple("Ts_default", default=Unpack[Tuple[str, int]]) - - with self.assertRaises(TypeError): - class X(Generic[Unpack[Ts], T]): ... - - with self.assertRaises(TypeError): - class Y(Generic[Unpack[Ts_default], T]): ... - - def test_typevartuple_none(self): - U = TypeVarTuple('U') - U_None = TypeVarTuple('U_None', default=None) - self.assertIs(U.__default__, NoDefault) - self.assertFalse(U.has_default()) - self.assertIs(U_None.__default__, None) - self.assertTrue(U_None.has_default()) - - def test_no_default_after_non_default(self): - DefaultStrT = typing_extensions.TypeVar('DefaultStrT', default=str) - T = TypeVar('T') - - with self.assertRaises(TypeError): - Generic[DefaultStrT, T] - - def test_need_more_params(self): - DefaultStrT = typing_extensions.TypeVar('DefaultStrT', default=str) - T = typing_extensions.TypeVar('T') - U = typing_extensions.TypeVar('U') - - class A(Generic[T, U, DefaultStrT]): ... - A[int, bool] - A[int, bool, str] - - with self.assertRaises( - TypeError, msg="Too few arguments for .+; actual 1, expected at least 2" - ): - A[int] - - def test_pickle(self): - global U, U_co, U_contra, U_default # pickle wants to reference the class by name - U = typing_extensions.TypeVar('U') - U_co = typing_extensions.TypeVar('U_co', covariant=True) - U_contra = typing_extensions.TypeVar('U_contra', contravariant=True) - U_default = typing_extensions.TypeVar('U_default', default=int) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - for typevar in (U, U_co, U_contra, U_default): - z = pickle.loads(pickle.dumps(typevar, proto)) - self.assertEqual(z.__name__, typevar.__name__) - self.assertEqual(z.__covariant__, typevar.__covariant__) - self.assertEqual(z.__contravariant__, typevar.__contravariant__) - self.assertEqual(z.__bound__, typevar.__bound__) - self.assertEqual(z.__default__, typevar.__default__) - - def test_strange_defaults_are_allowed(self): - # Leave it to type checkers to check whether strange default values - # should be allowed or disallowed - def not_a_type(): ... - - for typevarlike_cls in TypeVar, ParamSpec, TypeVarTuple: - for default in not_a_type, 42, bytearray(), (int, not_a_type, 42): - with self.subTest(typevarlike_cls=typevarlike_cls, default=default): - T = typevarlike_cls("T", default=default) - self.assertEqual(T.__default__, default) - - @skip_if_py313_beta_1 - def test_allow_default_after_non_default_in_alias(self): - T_default = TypeVar('T_default', default=int) - T = TypeVar('T') - Ts = TypeVarTuple('Ts') - - a1 = Callable[[T_default], T] - self.assertEqual(a1.__args__, (T_default, T)) - - a2 = dict[T_default, T] - self.assertEqual(a2.__args__, (T_default, T)) - - a3 = typing.Dict[T_default, T] - self.assertEqual(a3.__args__, (T_default, T)) - - a4 = Callable[[Unpack[Ts]], T] - self.assertEqual(a4.__args__, (Unpack[Ts], T)) - - @skipIf( - typing_extensions.Protocol is typing.Protocol, - "Test currently fails with the CPython version of Protocol and that's not our fault" - ) - def test_generic_with_broken_eq(self): - # See https://github.com/python/typing_extensions/pull/422 for context - class BrokenEq(type): - def __eq__(self, other): - if other is typing_extensions.Protocol: - raise TypeError("I'm broken") - return False - - class G(Generic[T], metaclass=BrokenEq): - pass - - alias = G[int] - self.assertIs(get_origin(alias), G) - self.assertEqual(get_args(alias), (int,)) - - @skipIf( - sys.version_info < (3, 11, 1), - "Not yet backported for older versions of Python" - ) - def test_paramspec_specialization(self): - T = TypeVar("T") - P = ParamSpec('P', default=[str, int]) - self.assertEqual(P.__default__, [str, int]) - class A(Generic[T, P]): ... - self.assertEqual(A[float].__args__, (float, (str, int))) - self.assertEqual(A[float, [range]].__args__, (float, (range,))) - - @skipIf( - sys.version_info < (3, 11, 1), - "Not yet backported for older versions of Python" - ) - def test_typevar_and_paramspec_specialization(self): - T = TypeVar("T") - U = TypeVar("U", default=float) - P = ParamSpec('P', default=[str, int]) - self.assertEqual(P.__default__, [str, int]) - class A(Generic[T, U, P]): ... - self.assertEqual(A[float].__args__, (float, float, (str, int))) - self.assertEqual(A[float, int].__args__, (float, int, (str, int))) - self.assertEqual(A[float, int, [range]].__args__, (float, int, (range,))) - - @skipIf( - sys.version_info < (3, 11, 1), - "Not yet backported for older versions of Python" - ) - def test_paramspec_and_typevar_specialization(self): - T = TypeVar("T") - P = ParamSpec('P', default=[str, int]) - U = TypeVar("U", default=float) - self.assertEqual(P.__default__, [str, int]) - class A(Generic[T, P, U]): ... - self.assertEqual(A[float].__args__, (float, (str, int), float)) - self.assertEqual(A[float, [range]].__args__, (float, (range,), float)) - self.assertEqual(A[float, [range], int].__args__, (float, (range,), int)) - - -class NoDefaultTests(BaseTestCase): - @skip_if_py313_beta_1 - def test_pickling(self): - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - s = pickle.dumps(NoDefault, proto) - loaded = pickle.loads(s) - self.assertIs(NoDefault, loaded) - - @skip_if_py313_beta_1 - def test_doc(self): - self.assertIsInstance(NoDefault.__doc__, str) - - def test_constructor(self): - self.assertIs(NoDefault, type(NoDefault)()) - with self.assertRaises(TypeError): - type(NoDefault)(1) - - def test_repr(self): - self.assertRegex(repr(NoDefault), r'typing(_extensions)?\.NoDefault') - - def test_no_call(self): - with self.assertRaises(TypeError): - NoDefault() - - @skip_if_py313_beta_1 - def test_immutable(self): - with self.assertRaises(AttributeError): - NoDefault.foo = 'bar' - with self.assertRaises(AttributeError): - NoDefault.foo - - # TypeError is consistent with the behavior of NoneType - with self.assertRaises(TypeError): - type(NoDefault).foo = 3 - with self.assertRaises(AttributeError): - type(NoDefault).foo - - -class TypeVarInferVarianceTests(BaseTestCase): - def test_typevar(self): - T = typing_extensions.TypeVar('T') - self.assertFalse(T.__infer_variance__) - T_infer = typing_extensions.TypeVar('T_infer', infer_variance=True) - self.assertTrue(T_infer.__infer_variance__) - T_noinfer = typing_extensions.TypeVar('T_noinfer', infer_variance=False) - self.assertFalse(T_noinfer.__infer_variance__) - - def test_pickle(self): - global U, U_infer # pickle wants to reference the class by name - U = typing_extensions.TypeVar('U') - U_infer = typing_extensions.TypeVar('U_infer', infer_variance=True) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - for typevar in (U, U_infer): - z = pickle.loads(pickle.dumps(typevar, proto)) - self.assertEqual(z.__name__, typevar.__name__) - self.assertEqual(z.__covariant__, typevar.__covariant__) - self.assertEqual(z.__contravariant__, typevar.__contravariant__) - self.assertEqual(z.__bound__, typevar.__bound__) - self.assertEqual(z.__infer_variance__, typevar.__infer_variance__) - - -class BufferTests(BaseTestCase): - def test(self): - self.assertIsInstance(memoryview(b''), Buffer) - self.assertIsInstance(bytearray(), Buffer) - self.assertIsInstance(b"x", Buffer) - self.assertNotIsInstance(1, Buffer) - - self.assertIsSubclass(bytearray, Buffer) - self.assertIsSubclass(memoryview, Buffer) - self.assertIsSubclass(bytes, Buffer) - self.assertNotIsSubclass(int, Buffer) - - class MyRegisteredBuffer: - def __buffer__(self, flags: int) -> memoryview: - return memoryview(b'') - - # On 3.12, collections.abc.Buffer does a structural compatibility check - if TYPING_3_12_0: - self.assertIsInstance(MyRegisteredBuffer(), Buffer) - self.assertIsSubclass(MyRegisteredBuffer, Buffer) - else: - self.assertNotIsInstance(MyRegisteredBuffer(), Buffer) - self.assertNotIsSubclass(MyRegisteredBuffer, Buffer) - Buffer.register(MyRegisteredBuffer) - self.assertIsInstance(MyRegisteredBuffer(), Buffer) - self.assertIsSubclass(MyRegisteredBuffer, Buffer) - - class MySubclassedBuffer(Buffer): - def __buffer__(self, flags: int) -> memoryview: - return memoryview(b'') - - self.assertIsInstance(MySubclassedBuffer(), Buffer) - self.assertIsSubclass(MySubclassedBuffer, Buffer) - - -class GetOriginalBasesTests(BaseTestCase): - def test_basics(self): - T = TypeVar('T') - class A: pass - class B(Generic[T]): pass - class C(B[int]): pass - class D(B[str], float): pass - self.assertEqual(get_original_bases(A), (object,)) - self.assertEqual(get_original_bases(B), (Generic[T],)) - self.assertEqual(get_original_bases(C), (B[int],)) - self.assertEqual(get_original_bases(int), (object,)) - self.assertEqual(get_original_bases(D), (B[str], float)) - - with self.assertRaisesRegex(TypeError, "Expected an instance of type"): - get_original_bases(object()) - - def test_builtin_generics(self): - class E(list[T]): pass - class F(list[int]): pass - - self.assertEqual(get_original_bases(E), (list[T],)) - self.assertEqual(get_original_bases(F), (list[int],)) - - @skipIf( - sys.version_info[:3] == (3, 12, 0) and sys.version_info[3] in {"alpha", "beta"}, - "Early versions of py312 had a bug" - ) - def test_concrete_subclasses_of_generic_classes(self): - T = TypeVar("T") - - class FirstBase(Generic[T]): pass - class SecondBase(Generic[T]): pass - class First(FirstBase[int]): pass - class Second(SecondBase[int]): pass - class G(First, Second): pass - self.assertEqual(get_original_bases(G), (First, Second)) - - class First_(Generic[T]): pass - class Second_(Generic[T]): pass - class H(First_, Second_): pass - self.assertEqual(get_original_bases(H), (First_, Second_)) - - def test_namedtuples(self): - # On 3.12, this should work well with typing.NamedTuple and typing_extensions.NamedTuple - # On lower versions, it will only work fully with typing_extensions.NamedTuple - if sys.version_info >= (3, 12): - namedtuple_classes = (typing.NamedTuple, typing_extensions.NamedTuple) - else: - namedtuple_classes = (typing_extensions.NamedTuple,) - - for NamedTuple in namedtuple_classes: # noqa: F402 - with self.subTest(cls=NamedTuple): - class ClassBasedNamedTuple(NamedTuple): - x: int - - class GenericNamedTuple(NamedTuple, Generic[T]): - x: T - - CallBasedNamedTuple = NamedTuple("CallBasedNamedTuple", [("x", int)]) - - self.assertIs( - get_original_bases(ClassBasedNamedTuple)[0], NamedTuple - ) - self.assertEqual( - get_original_bases(GenericNamedTuple), - (NamedTuple, Generic[T]) - ) - self.assertIs( - get_original_bases(CallBasedNamedTuple)[0], NamedTuple - ) - - def test_typeddicts(self): - # On 3.12, this should work well with typing.TypedDict and typing_extensions.TypedDict - # On lower versions, it will only work fully with typing_extensions.TypedDict - if sys.version_info >= (3, 12): - typeddict_classes = (typing.TypedDict, typing_extensions.TypedDict) - else: - typeddict_classes = (typing_extensions.TypedDict,) - - for TypedDict in typeddict_classes: # noqa: F402 - with self.subTest(cls=TypedDict): - class ClassBasedTypedDict(TypedDict): - x: int - - class GenericTypedDict(TypedDict, Generic[T]): - x: T - - CallBasedTypedDict = TypedDict("CallBasedTypedDict", {"x": int}) - - self.assertIs( - get_original_bases(ClassBasedTypedDict)[0], - TypedDict - ) - self.assertEqual( - get_original_bases(GenericTypedDict), - (TypedDict, Generic[T]) - ) - self.assertIs( - get_original_bases(CallBasedTypedDict)[0], - TypedDict - ) - - -class TypeAliasTypeTests(BaseTestCase): - def test_attributes(self): - Simple = TypeAliasType("Simple", int) - self.assertEqual(Simple.__name__, "Simple") - self.assertIs(Simple.__value__, int) - self.assertEqual(Simple.__type_params__, ()) - self.assertEqual(Simple.__parameters__, ()) - - T = TypeVar("T") - ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) - self.assertEqual(ListOrSetT.__name__, "ListOrSetT") - self.assertEqual(ListOrSetT.__value__, Union[List[T], Set[T]]) - self.assertEqual(ListOrSetT.__type_params__, (T,)) - self.assertEqual(ListOrSetT.__parameters__, (T,)) - - Ts = TypeVarTuple("Ts") - Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) - self.assertEqual(Variadic.__name__, "Variadic") - self.assertEqual(Variadic.__value__, Tuple[int, Unpack[Ts]]) - self.assertEqual(Variadic.__type_params__, (Ts,)) - self.assertEqual(Variadic.__parameters__, tuple(iter(Ts))) - - P = ParamSpec('P') - CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P, )) - self.assertEqual(CallableP.__name__, "CallableP") - self.assertEqual(CallableP.__value__, Callable[P, Any]) - self.assertEqual(CallableP.__type_params__, (P,)) - self.assertEqual(CallableP.__parameters__, (P,)) - - def test_alias_types_and_substitutions(self): - T = TypeVar('T') - T2 = TypeVar('T2') - T_default = TypeVar("T_default", default=int) - Ts = TypeVarTuple("Ts") - P = ParamSpec('P') - - test_argument_cases = { - # arguments : expected parameters - int : (), - ... : (), - None : (), - T2 : (T2,), - Union[int, List[T2]] : (T2,), - Tuple[int, str] : (), - Tuple[T, T_default, T2] : (T, T_default, T2), - Tuple[Unpack[Ts]] : (Ts,), - Callable[[Unpack[Ts]], T2] : (Ts, T2), - Callable[P, T2] : (P, T2), - Callable[Concatenate[T2, P], T_default] : (T2, P, T_default), - TypeAliasType("NestedAlias", List[T], type_params=(T,))[T2] : (T2,), - Unpack[Ts] : (Ts,), - Unpack[Tuple[int, T2]] : (T2,), - Concatenate[int, P] : (P,), - # Not tested usage of bare TypeVarTuple, would need 3.11+ - # Ts : (Ts,), # invalid case - } + def test_union_unique(self): + X = TypeVar('X') + Y = TypeVar('Y') + self.assertNotEqual(X, Y) + self.assertEqual(Union[X], X) + self.assertNotEqual(Union[X], Union[X, Y]) + self.assertEqual(Union[X, X], X) + self.assertNotEqual(Union[X, int], Union[X]) + self.assertNotEqual(Union[X, int], Union[int]) + self.assertEqual(Union[X, int].__args__, (X, int)) + self.assertEqual(Union[X, int].__parameters__, (X,)) + self.assertIs(Union[X, int].__origin__, Union) - test_alias_cases = [ - # Simple cases - TypeAliasType("ListT", List[T], type_params=(T,)), - TypeAliasType("UnionT", Union[int, List[T]], type_params=(T,)), - # Value has no parameter but in type_param - TypeAliasType("ValueWithoutT", int, type_params=(T,)), - # Callable - TypeAliasType("CallableP", Callable[P, Any], type_params=(P, )), - TypeAliasType("CallableT", Callable[..., T], type_params=(T, )), - TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, )), - # TypeVarTuple - TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)), - # TypeVar with default - TypeAliasType("TupleT_default", Tuple[T_default, T], type_params=(T, T_default)), - TypeAliasType("CallableT_default", Callable[[T], T_default], type_params=(T, T_default)), - ] + if hasattr(types, "UnionType"): + def test_or(self): + X = TypeVar('X') + # use a string because str doesn't implement + # __or__/__ror__ itself + self.assertEqual(X | "x", Union[X, "x"]) + self.assertEqual("x" | X, Union["x", X]) + # make sure the order is correct + self.assertEqual(get_args(X | "x"), (X, EqualToForwardRef("x"))) + self.assertEqual(get_args("x" | X), (EqualToForwardRef("x"), X)) - for alias in test_alias_cases: - with self.subTest(alias=alias, args=[]): - subscripted = alias[[]] - self.assertEqual(get_args(subscripted), ([],)) - self.assertEqual(subscripted.__parameters__, ()) - with self.subTest(alias=alias, args=()): - subscripted = alias[()] - self.assertEqual(get_args(subscripted), ()) - self.assertEqual(subscripted.__parameters__, ()) - with self.subTest(alias=alias, args=(int, float)): - subscripted = alias[int, float] - self.assertEqual(get_args(subscripted), (int, float)) - self.assertEqual(subscripted.__parameters__, ()) - with self.subTest(alias=alias, args=[int, float]): - subscripted = alias[[int, float]] - self.assertEqual(get_args(subscripted), ([int, float],)) - self.assertEqual(subscripted.__parameters__, ()) - for expected_args, expected_parameters in test_argument_cases.items(): - with self.subTest(alias=alias, args=expected_args): - self.assertEqual(get_args(alias[expected_args]), (expected_args,)) - self.assertEqual(alias[expected_args].__parameters__, expected_parameters) + def test_union_constrained(self): + A = TypeVar('A', str, bytes) + self.assertNotEqual(Union[A, str], Union[A]) - def test_cannot_set_attributes(self): - Simple = TypeAliasType("Simple", int) - with self.assertRaisesRegex(AttributeError, "readonly attribute"): - Simple.__name__ = "NewName" - with self.assertRaisesRegex( - AttributeError, - "attribute '__value__' of 'typing.TypeAliasType' objects is not writable", - ): - Simple.__value__ = str - with self.assertRaisesRegex( - AttributeError, - "attribute '__type_params__' of 'typing.TypeAliasType' objects is not writable", - ): - Simple.__type_params__ = (T,) - with self.assertRaisesRegex( - AttributeError, - "attribute '__parameters__' of 'typing.TypeAliasType' objects is not writable", - ): - Simple.__parameters__ = (T,) - with self.assertRaisesRegex( - AttributeError, - "attribute '__module__' of 'typing.TypeAliasType' objects is not writable", - ): - Simple.__module__ = 42 - with self.assertRaisesRegex( - AttributeError, - "'typing.TypeAliasType' object has no attribute 'some_attribute'", - ): - Simple.some_attribute = "not allowed" + def test_repr(self): + self.assertEqual(repr(T), '~T') + self.assertEqual(repr(KT), '~KT') + self.assertEqual(repr(VT), '~VT') + self.assertEqual(repr(AnyStr), '~AnyStr') + T_co = TypeVar('T_co', covariant=True) + self.assertEqual(repr(T_co), '+T_co') + T_contra = TypeVar('T_contra', contravariant=True) + self.assertEqual(repr(T_contra), '-T_contra') - def test_cannot_delete_attributes(self): - Simple = TypeAliasType("Simple", int) - with self.assertRaisesRegex(AttributeError, "readonly attribute"): - del Simple.__name__ - with self.assertRaisesRegex( - AttributeError, - "attribute '__value__' of 'typing.TypeAliasType' objects is not writable", - ): - del Simple.__value__ - with self.assertRaisesRegex( - AttributeError, - "'typing.TypeAliasType' object has no attribute 'some_attribute'", - ): - del Simple.some_attribute + def test_no_redefinition(self): + self.assertNotEqual(TypeVar('T'), TypeVar('T')) + self.assertNotEqual(TypeVar('T', int, str), TypeVar('T', int, str)) - def test_or(self): - Alias = TypeAliasType("Alias", int) - if sys.version_info >= (3, 10): - self.assertEqual(Alias | int, Union[Alias, int]) - self.assertEqual(Alias | None, Union[Alias, None]) - self.assertEqual(Alias | (int | str), Union[Alias, int | str]) - self.assertEqual(Alias | list[float], Union[Alias, list[float]]) + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class V(TypeVar): pass + T = TypeVar("T") + with self.assertRaises(TypeError): + class W(T): pass - if sys.version_info >= (3, 12): - Alias2 = typing.TypeAliasType("Alias2", str) - self.assertEqual(Alias | Alias2, Union[Alias, Alias2]) - else: - with self.assertRaises(TypeError): - Alias | int - # Rejected on all versions + def test_cannot_instantiate_vars(self): with self.assertRaises(TypeError): - Alias | "Ref" + TypeVar('A')() - def test_getitem(self): - T = TypeVar('T') - ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) - subscripted = ListOrSetT[int] - self.assertEqual(get_args(subscripted), (int,)) - self.assertIs(get_origin(subscripted), ListOrSetT) + def test_bound_errors(self): + with self.assertRaises(TypeError): + TypeVar('X', bound=Optional) + with self.assertRaises(TypeError): + TypeVar('X', str, float, bound=Employee) with self.assertRaisesRegex(TypeError, - "not a generic class" - # types.GenericAlias raises a different error in 3.10 - if sys.version_info[:2] != (3, 10) - else "There are no type variables left in ListOrSetT" - ): - subscripted[int] - - - still_generic = ListOrSetT[Iterable[T]] - self.assertEqual(get_args(still_generic), (Iterable[T],)) - self.assertIs(get_origin(still_generic), ListOrSetT) - fully_subscripted = still_generic[float] - self.assertEqual(get_args(fully_subscripted), (Iterable[float],)) - self.assertIs(get_origin(fully_subscripted), ListOrSetT) - - ValueWithoutTypeVar = TypeAliasType("ValueWithoutTypeVar", int, type_params=(T,)) - still_subscripted = ValueWithoutTypeVar[str] - self.assertEqual(get_args(still_subscripted), (str,)) - - def test_callable_without_concatenate(self): - P = ParamSpec('P') - CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) - get_args_test_cases = [ - # List of (alias, expected_args) - # () -> Any - (CallableP[()], ()), - (CallableP[[]], ([],)), - # (int) -> Any - (CallableP[int], (int,)), - (CallableP[[int]], ([int],)), - # (int, int) -> Any - (CallableP[int, int], (int, int)), - (CallableP[[int, int]], ([int, int],)), - # (...) -> Any - (CallableP[...], (...,)), - # (int, ...) -> Any - (CallableP[[int, ...]], ([int, ...],)), - ] - - for index, (expression, expected_args) in enumerate(get_args_test_cases): - with self.subTest(index=index, expression=expression): - self.assertEqual(get_args(expression), expected_args) + r"Bound must be a type\. Got \(1, 2\)\."): + TypeVar('X', bound=(1, 2)) - self.assertEqual(CallableP[...], CallableP[(...,)]) - # (T) -> Any - CallableT = CallableP[T] - self.assertEqual(get_args(CallableT), (T,)) - self.assertEqual(CallableT.__parameters__, (T,)) + def test_missing__name__(self): + # See https://github.com/python/cpython/issues/84123 + code = ("import typing\n" + "T = typing.TypeVar('T')\n" + ) + exec(code, {}) - def test_callable_with_concatenate(self): - P = ParamSpec('P') - P2 = ParamSpec('P2') - CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) + def test_no_bivariant(self): + with self.assertRaises(ValueError): + TypeVar('T', covariant=True, contravariant=True) - callable_concat = CallableP[Concatenate[int, P2]] - self.assertEqual(callable_concat.__parameters__, (P2,)) - concat_usage = callable_concat[str] - with self.subTest("get_args of Concatenate in TypeAliasType"): - if not TYPING_3_10_0: - # args are: ([, ~P2],) - self.skipTest("Nested ParamSpec is not substituted") - self.assertEqual(get_args(concat_usage), ((int, str),)) - with self.subTest("Equality of parameter_expression without []"): - if not TYPING_3_10_0: - self.skipTest("Nested list is invalid type form") - self.assertEqual(concat_usage, callable_concat[[str]]) + def test_cannot_combine_explicit_and_infer(self): + with self.assertRaises(ValueError): + TypeVar('T', covariant=True, infer_variance=True) + with self.assertRaises(ValueError): + TypeVar('T', contravariant=True, infer_variance=True) - def test_substitution(self): - T = TypeVar('T') - Ts = TypeVarTuple("Ts") - CallableTs = TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, )) - unpack_callable = CallableTs[Unpack[Tuple[int, T]]] - self.assertEqual(get_args(unpack_callable), (Unpack[Tuple[int, T]],)) +class TypeVarLikeDefaultsTests(BaseTestCase): + def test_typevar(self): + T = typing_extensions.TypeVar('T', default=int) + typing_T = typing.TypeVar('T') + self.assertEqual(T.__default__, int) + self.assertIsInstance(T, typing_extensions.TypeVar) + self.assertIsInstance(T, typing.TypeVar) + self.assertIsInstance(typing_T, typing.TypeVar) + self.assertIsInstance(typing_T, typing_extensions.TypeVar) - P = ParamSpec('P') - CallableP = TypeAliasType("CallableP", Callable[P, T], type_params=(P, T)) - callable_concat = CallableP[Concatenate[int, P], Any] - self.assertEqual(get_args(callable_concat), (Concatenate[int, P], Any)) + class A(Generic[T]): ... + self.assertEqual(Optional[T].__args__, (T, type(None))) - def test_wrong_amount_of_parameters(self): - T = TypeVar('T') - T2 = TypeVar("T2") - P = ParamSpec('P') - ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) - TwoT = TypeAliasType("TwoT", Union[List[T], Set[T2]], type_params=(T, T2)) - CallablePT = TypeAliasType("CallablePT", Callable[P, T], type_params=(P, T)) + def test_typevar_none(self): + U = typing_extensions.TypeVar('U') + U_None = typing_extensions.TypeVar('U_None', default=None) + self.assertIs(U.__default__, NoDefault) + self.assertFalse(U.has_default()) + self.assertEqual(U_None.__default__, None) + self.assertTrue(U_None.has_default()) - # Not enough parameters - test_cases = [ - # not_enough - (TwoT[int], [(int,), ()]), - (TwoT[T], [(T,), (T,)]), - # callable and not enough - (CallablePT[int], [(int,), ()]), - # too many - (ListOrSetT[int, bool], [(int, bool), ()]), - # callable and too many - (CallablePT[str, float, int], [(str, float, int), ()]), - # Check if TypeVar is still present even if over substituted - (ListOrSetT[int, T], [(int, T), (T,)]), - # With and without list for ParamSpec - (CallablePT[str, float, T], [(str, float, T), (T,)]), - (CallablePT[[str], float, int, T2], [([str], float, int, T2), (T2,)]), - ] + def test_paramspec(self): + P = ParamSpec('P', default=[str, int]) + self.assertEqual(P.__default__, [str, int]) + self.assertTrue(P.has_default()) + self.assertIsInstance(P, ParamSpec) + if hasattr(typing, "ParamSpec"): + self.assertIsInstance(P, typing.ParamSpec) + typing_P = typing.ParamSpec('P') + self.assertIsInstance(typing_P, typing.ParamSpec) + self.assertIsInstance(typing_P, ParamSpec) - for index, (alias, [expected_args, expected_params]) in enumerate(test_cases): - with self.subTest(index=index, alias=alias): - self.assertEqual(get_args(alias), expected_args) - self.assertEqual(alias.__parameters__, expected_params) + class A(Generic[P]): ... + self.assertEqual(typing.Callable[P, None].__args__, (P, type(None))) - # The condition should align with the version of GeneriAlias usage in __getitem__ or be 3.11+ - @skipIf(TYPING_3_10_0, "Most arguments are allowed in 3.11+ or with GenericAlias") - def test_invalid_cases_before_3_10(self): - T = TypeVar('T') - ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) - with self.assertRaises(TypeError): - ListOrSetT[Generic[T]] - with self.assertRaises(TypeError): - ListOrSetT[(Generic[T], )] + P_default = ParamSpec('P_default', default=...) + self.assertIs(P_default.__default__, ...) + self.assertTrue(P_default.has_default()) - def test_unpack_parameter_collection(self): - Ts = TypeVarTuple("Ts") + def test_paramspec_none(self): + U = ParamSpec('U') + U_None = ParamSpec('U_None', default=None) + self.assertIs(U.__default__, NoDefault) + self.assertFalse(U.has_default()) + self.assertIs(U_None.__default__, None) + self.assertTrue(U_None.has_default()) - class Foo(Generic[Unpack[Ts]]): - bar: Tuple[Unpack[Ts]] + def test_typevartuple(self): + Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]]) + self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]]) + self.assertIsInstance(Ts, TypeVarTuple) + self.assertTrue(Ts.has_default()) + if hasattr(typing, "TypeVarTuple"): + self.assertIsInstance(Ts, typing.TypeVarTuple) + typing_Ts = typing.TypeVarTuple('Ts') + self.assertIsInstance(typing_Ts, typing.TypeVarTuple) + self.assertIsInstance(typing_Ts, TypeVarTuple) - FooAlias = TypeAliasType("FooAlias", Foo[Unpack[Ts]], type_params=(Ts,)) - self.assertEqual(FooAlias[Unpack[Tuple[str]]].__parameters__, ()) - self.assertEqual(FooAlias[Unpack[Tuple[T]]].__parameters__, (T,)) + class A(Generic[Unpack[Ts]]): ... + self.assertEqual(Optional[Unpack[Ts]].__args__, (Unpack[Ts], type(None))) - P = ParamSpec("P") - CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) - call_int_T = CallableP[Unpack[Tuple[int, T]]] - self.assertEqual(call_int_T.__parameters__, (T,)) + @skipIf( + sys.version_info < (3, 11, 1), + "Not yet backported for older versions of Python" + ) + def test_typevartuple_specialization(self): + T = TypeVar("T") + Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]]) + self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]]) + class A(Generic[T, Unpack[Ts]]): ... + self.assertEqual(A[float].__args__, (float, str, int)) + self.assertEqual(A[float, range].__args__, (float, range)) + self.assertEqual(A[float, Unpack[tuple[int, ...]]].__args__, (float, Unpack[tuple[int, ...]])) - def test_alias_attributes(self): - T = TypeVar('T') - T2 = TypeVar('T2') - ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) + @skipIf( + sys.version_info < (3, 11, 1), + "Not yet backported for older versions of Python" + ) + def test_typevar_and_typevartuple_specialization(self): + T = TypeVar("T") + U = TypeVar("U", default=float) + Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]]) + self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]]) + class A(Generic[T, U, Unpack[Ts]]): ... + self.assertEqual(A[int].__args__, (int, float, str, int)) + self.assertEqual(A[int, str].__args__, (int, str, str, int)) + self.assertEqual(A[int, str, range].__args__, (int, str, range)) + self.assertEqual(A[int, str, Unpack[tuple[int, ...]]].__args__, (int, str, Unpack[tuple[int, ...]])) - subscripted = ListOrSetT[int] - self.assertEqual(subscripted.__module__, ListOrSetT.__module__) - self.assertEqual(subscripted.__name__, "ListOrSetT") - self.assertEqual(subscripted.__value__, Union[List[T], Set[T]]) - self.assertEqual(subscripted.__type_params__, (T,)) + def test_no_default_after_typevar_tuple(self): + T = TypeVar("T", default=int) + Ts = TypeVarTuple("Ts") + Ts_default = TypeVarTuple("Ts_default", default=Unpack[Tuple[str, int]]) - still_generic = ListOrSetT[Iterable[T2]] - self.assertEqual(still_generic.__module__, ListOrSetT.__module__) - self.assertEqual(still_generic.__name__, "ListOrSetT") - self.assertEqual(still_generic.__value__, Union[List[T], Set[T]]) - self.assertEqual(still_generic.__type_params__, (T,)) + with self.assertRaises(TypeError): + class X(Generic[Unpack[Ts], T]): ... - fully_subscripted = still_generic[float] - self.assertEqual(fully_subscripted.__module__, ListOrSetT.__module__) - self.assertEqual(fully_subscripted.__name__, "ListOrSetT") - self.assertEqual(fully_subscripted.__value__, Union[List[T], Set[T]]) - self.assertEqual(fully_subscripted.__type_params__, (T,)) + with self.assertRaises(TypeError): + class Y(Generic[Unpack[Ts_default], T]): ... - def test_subscription_without_type_params(self): - Simple = TypeAliasType("Simple", int) - with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): - Simple[int] + def test_typevartuple_none(self): + U = TypeVarTuple('U') + U_None = TypeVarTuple('U_None', default=None) + self.assertIs(U.__default__, NoDefault) + self.assertFalse(U.has_default()) + self.assertIs(U_None.__default__, None) + self.assertTrue(U_None.has_default()) - # A TypeVar in the value does not allow subscription + def test_no_default_after_non_default(self): + DefaultStrT = typing_extensions.TypeVar('DefaultStrT', default=str) T = TypeVar('T') - MissingTypeParamsErr = TypeAliasType("MissingTypeParamsErr", List[T]) - self.assertEqual(MissingTypeParamsErr.__type_params__, ()) - self.assertEqual(MissingTypeParamsErr.__parameters__, ()) - with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): - MissingTypeParamsErr[int] - def test_pickle(self): - global Alias - Alias = TypeAliasType("Alias", int) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - with self.subTest(proto=proto): - pickled = pickle.dumps(Alias, proto) - unpickled = pickle.loads(pickled) - self.assertIs(unpickled, Alias) - - def test_no_instance_subclassing(self): with self.assertRaises(TypeError): - class MyAlias(TypeAliasType): - pass - - def test_type_var_compatibility(self): - # Regression test to assure compatibility with typing variants - typingT = typing.TypeVar('typingT') - T1 = TypeAliasType("TypingTypeVar", ..., type_params=(typingT,)) - self.assertEqual(T1.__type_params__, (typingT,)) + Generic[DefaultStrT, T] - # Test typing_extensions backports - textT = TypeVar('textT') - T2 = TypeAliasType("TypingExtTypeVar", ..., type_params=(textT,)) - self.assertEqual(T2.__type_params__, (textT,)) + def test_need_more_params(self): + DefaultStrT = typing_extensions.TypeVar('DefaultStrT', default=str) + T = typing_extensions.TypeVar('T') + U = typing_extensions.TypeVar('U') - textP = ParamSpec("textP") - T3 = TypeAliasType("TypingExtParamSpec", ..., type_params=(textP,)) - self.assertEqual(T3.__type_params__, (textP,)) + class A(Generic[T, U, DefaultStrT]): ... + A[int, bool] + A[int, bool, str] - textTs = TypeVarTuple("textTs") - T4 = TypeAliasType("TypingExtTypeVarTuple", ..., type_params=(textTs,)) - self.assertEqual(T4.__type_params__, (textTs,)) + with self.assertRaises( + TypeError, msg="Too few arguments for .+; actual 1, expected at least 2" + ): + A[int] - @skipUnless(TYPING_3_10_0, "typing.ParamSpec is not available before 3.10") - def test_param_spec_compatibility(self): - # Regression test to assure compatibility with typing variant - typingP = typing.ParamSpec("typingP") - T5 = TypeAliasType("TypingParamSpec", ..., type_params=(typingP,)) - self.assertEqual(T5.__type_params__, (typingP,)) + def test_pickle(self): + global U, U_co, U_contra, U_default # pickle wants to reference the class by name + U = typing_extensions.TypeVar('U') + U_co = typing_extensions.TypeVar('U_co', covariant=True) + U_contra = typing_extensions.TypeVar('U_contra', contravariant=True) + U_default = typing_extensions.TypeVar('U_default', default=int) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + for typevar in (U, U_co, U_contra, U_default): + z = pickle.loads(pickle.dumps(typevar, proto)) + self.assertEqual(z.__name__, typevar.__name__) + self.assertEqual(z.__covariant__, typevar.__covariant__) + self.assertEqual(z.__contravariant__, typevar.__contravariant__) + self.assertEqual(z.__bound__, typevar.__bound__) + self.assertEqual(z.__default__, typevar.__default__) - @skipUnless(TYPING_3_12_0, "typing.TypeVarTuple is not available before 3.12") - def test_type_var_tuple_compatibility(self): - # Regression test to assure compatibility with typing variant - typingTs = typing.TypeVarTuple("typingTs") - T6 = TypeAliasType("TypingTypeVarTuple", ..., type_params=(typingTs,)) - self.assertEqual(T6.__type_params__, (typingTs,)) + def test_strange_defaults_are_allowed(self): + # Leave it to type checkers to check whether strange default values + # should be allowed or disallowed + def not_a_type(): ... - def test_type_params_possibilities(self): - T = TypeVar('T') - # Test not a tuple - with self.assertRaisesRegex(TypeError, "type_params must be a tuple"): - TypeAliasType("InvalidTypeParams", List[T], type_params=[T]) + for typevarlike_cls in TypeVar, ParamSpec, TypeVarTuple: + for default in not_a_type, 42, bytearray(), (int, not_a_type, 42): + with self.subTest(typevarlike_cls=typevarlike_cls, default=default): + T = typevarlike_cls("T", default=default) + self.assertEqual(T.__default__, default) - # Test default order and other invalid inputs + @skip_if_py313_beta_1 + def test_allow_default_after_non_default_in_alias(self): T_default = TypeVar('T_default', default=int) + T = TypeVar('T') Ts = TypeVarTuple('Ts') - Ts_default = TypeVarTuple('Ts_default', default=Unpack[Tuple[str, int]]) - P = ParamSpec('P') - P_default = ParamSpec('P_default', default=[str, int]) - - # NOTE: PEP 696 states: "TypeVars with defaults cannot immediately follow TypeVarTuples" - # this is currently not enforced for the type statement and is not tested. - # PEP 695: Double usage of the same name is also not enforced and not tested. - valid_cases = [ - (T, P, Ts), - (T, Ts_default), - (P_default, T_default), - (P, T_default, Ts_default), - (T_default, P_default, Ts_default), - ] - invalid_cases = [ - ((T_default, T), f"non-default type parameter '{T!r}' follows default"), - ((P_default, P), f"non-default type parameter '{P!r}' follows default"), - ((Ts_default, T), f"non-default type parameter '{T!r}' follows default"), - # Only type params are accepted - ((1,), "Expected a type param, got 1"), - ((str,), f"Expected a type param, got {str!r}"), - # Unpack is not a TypeVar but isinstance(Unpack[Ts], TypeVar) is True in Python < 3.12 - ((Unpack[Ts],), f"Expected a type param, got {re.escape(repr(Unpack[Ts]))}"), - ] - - for case in valid_cases: - with self.subTest(type_params=case): - TypeAliasType("OkCase", List[T], type_params=case) - for case, msg in invalid_cases: - with self.subTest(type_params=case): - with self.assertRaisesRegex(TypeError, msg): - TypeAliasType("InvalidCase", List[T], type_params=case) -class DocTests(BaseTestCase): - def test_annotation(self): + a1 = Callable[[T_default], T] + self.assertEqual(a1.__args__, (T_default, T)) - def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: pass + a2 = dict[T_default, T] + self.assertEqual(a2.__args__, (T_default, T)) - hints = get_type_hints(hi, include_extras=True) - doc_info = hints["to"].__metadata__[0] - self.assertEqual(doc_info.documentation, "Who to say hi to") - self.assertIsInstance(doc_info, Doc) + a3 = typing.Dict[T_default, T] + self.assertEqual(a3.__args__, (T_default, T)) - def test_repr(self): - doc_info = Doc("Who to say hi to") - self.assertEqual(repr(doc_info), "Doc('Who to say hi to')") + a4 = Callable[[Unpack[Ts]], T] + self.assertEqual(a4.__args__, (Unpack[Ts], T)) - def test_hashability(self): - doc_info = Doc("Who to say hi to") - self.assertIsInstance(hash(doc_info), int) - self.assertNotEqual(hash(doc_info), hash(Doc("Who not to say hi to"))) + @skipIf( + typing_extensions.Protocol is typing.Protocol, + "Test currently fails with the CPython version of Protocol and that's not our fault" + ) + def test_generic_with_broken_eq(self): + # See https://github.com/python/typing_extensions/pull/422 for context + class BrokenEq(type): + def __eq__(self, other): + if other is typing_extensions.Protocol: + raise TypeError("I'm broken") + return False - def test_equality(self): - doc_info = Doc("Who to say hi to") - # Equal to itself - self.assertEqual(doc_info, doc_info) - # Equal to another instance with the same string - self.assertEqual(doc_info, Doc("Who to say hi to")) - # Not equal to another instance with a different string - self.assertNotEqual(doc_info, Doc("Who not to say hi to")) + class G(Generic[T], metaclass=BrokenEq): + pass - def test_pickle(self): - doc_info = Doc("Who to say hi to") - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - pickled = pickle.dumps(doc_info, protocol=proto) - self.assertEqual(doc_info, pickle.loads(pickled)) + alias = G[int] + self.assertIs(get_origin(alias), G) + self.assertEqual(get_args(alias), (int,)) + @skipIf( + sys.version_info < (3, 11, 1), + "Not yet backported for older versions of Python" + ) + def test_paramspec_specialization(self): + T = TypeVar("T") + P = ParamSpec('P', default=[str, int]) + self.assertEqual(P.__default__, [str, int]) + class A(Generic[T, P]): ... + self.assertEqual(A[float].__args__, (float, (str, int))) + self.assertEqual(A[float, [range]].__args__, (float, (range,))) -@skipUnless( - hasattr(typing_extensions, "CapsuleType"), - "CapsuleType is not available on all Python implementations" -) -class CapsuleTypeTests(BaseTestCase): - def test_capsule_type(self): - import _datetime - self.assertIsInstance(_datetime.datetime_CAPI, typing_extensions.CapsuleType) + @skipIf( + sys.version_info < (3, 11, 1), + "Not yet backported for older versions of Python" + ) + def test_typevar_and_paramspec_specialization(self): + T = TypeVar("T") + U = TypeVar("U", default=float) + P = ParamSpec('P', default=[str, int]) + self.assertEqual(P.__default__, [str, int]) + class A(Generic[T, U, P]): ... + self.assertEqual(A[float].__args__, (float, float, (str, int))) + self.assertEqual(A[float, int].__args__, (float, int, (str, int))) + self.assertEqual(A[float, int, [range]].__args__, (float, int, (range,))) + @skipIf( + sys.version_info < (3, 11, 1), + "Not yet backported for older versions of Python" + ) + def test_paramspec_and_typevar_specialization(self): + T = TypeVar("T") + P = ParamSpec('P', default=[str, int]) + U = TypeVar("U", default=float) + self.assertEqual(P.__default__, [str, int]) + class A(Generic[T, P, U]): ... + self.assertEqual(A[float].__args__, (float, (str, int), float)) + self.assertEqual(A[float, [range]].__args__, (float, (range,), float)) + self.assertEqual(A[float, [range], int].__args__, (float, (range,), int)) -class MyClass: - def __repr__(self): - return "my repr" +class NoDefaultTests(BaseTestCase): + @skip_if_py313_beta_1 + def test_pickling(self): + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + s = pickle.dumps(NoDefault, proto) + loaded = pickle.loads(s) + self.assertIs(NoDefault, loaded) -class TestTypeRepr(BaseTestCase): - def test_custom_types(self): + @skip_if_py313_beta_1 + def test_doc(self): + self.assertIsInstance(NoDefault.__doc__, str) - class Nested: - pass + def test_constructor(self): + self.assertIs(NoDefault, type(NoDefault)()) + with self.assertRaises(TypeError): + type(NoDefault)(1) - def nested(): - pass + def test_repr(self): + self.assertRegex(repr(NoDefault), r'typing(_extensions)?\.NoDefault') - self.assertEqual(type_repr(MyClass), f"{__name__}.MyClass") - self.assertEqual( - type_repr(Nested), - f"{__name__}.TestTypeRepr.test_custom_types..Nested", - ) - self.assertEqual( - type_repr(nested), - f"{__name__}.TestTypeRepr.test_custom_types..nested", - ) - self.assertEqual(type_repr(times_three), f"{__name__}.times_three") - self.assertEqual(type_repr(Format.VALUE), repr(Format.VALUE)) - self.assertEqual(type_repr(MyClass()), "my repr") + def test_no_call(self): + with self.assertRaises(TypeError): + NoDefault() - def test_builtin_types(self): - self.assertEqual(type_repr(int), "int") - self.assertEqual(type_repr(object), "object") - self.assertEqual(type_repr(None), "None") - self.assertEqual(type_repr(len), "len") - self.assertEqual(type_repr(1), "1") - self.assertEqual(type_repr("1"), "'1'") - self.assertEqual(type_repr(''), "''") - self.assertEqual(type_repr(...), "...") + @skip_if_py313_beta_1 + def test_immutable(self): + with self.assertRaises(AttributeError): + NoDefault.foo = 'bar' + with self.assertRaises(AttributeError): + NoDefault.foo + # TypeError is consistent with the behavior of NoneType + with self.assertRaises(TypeError): + type(NoDefault).foo = 3 + with self.assertRaises(AttributeError): + type(NoDefault).foo -def times_three(fn): - @functools.wraps(fn) - def wrapper(a, b): - return fn(a * 3, b * 3) - return wrapper +class TypeVarInferVarianceTests(BaseTestCase): + def test_typevar(self): + T = typing_extensions.TypeVar('T') + self.assertFalse(T.__infer_variance__) + T_infer = typing_extensions.TypeVar('T_infer', infer_variance=True) + self.assertTrue(T_infer.__infer_variance__) + T_noinfer = typing_extensions.TypeVar('T_noinfer', infer_variance=False) + self.assertFalse(T_noinfer.__infer_variance__) + def test_pickle(self): + global U, U_infer # pickle wants to reference the class by name + U = typing_extensions.TypeVar('U') + U_infer = typing_extensions.TypeVar('U_infer', infer_variance=True) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + for typevar in (U, U_infer): + z = pickle.loads(pickle.dumps(typevar, proto)) + self.assertEqual(z.__name__, typevar.__name__) + self.assertEqual(z.__covariant__, typevar.__covariant__) + self.assertEqual(z.__contravariant__, typevar.__contravariant__) + self.assertEqual(z.__bound__, typevar.__bound__) + self.assertEqual(z.__infer_variance__, typevar.__infer_variance__) -class TestGetAnnotations(BaseTestCase): - @classmethod - def setUpClass(cls): - with tempfile.TemporaryDirectory() as tempdir: - sys.path.append(tempdir) - Path(tempdir, "inspect_stock_annotations.py").write_text(STOCK_ANNOTATIONS) - Path(tempdir, "inspect_stringized_annotations.py").write_text(STRINGIZED_ANNOTATIONS) - Path(tempdir, "inspect_stringized_annotations_2.py").write_text(STRINGIZED_ANNOTATIONS_2) - cls.inspect_stock_annotations = importlib.import_module("inspect_stock_annotations") - cls.inspect_stringized_annotations = importlib.import_module("inspect_stringized_annotations") - cls.inspect_stringized_annotations_2 = importlib.import_module("inspect_stringized_annotations_2") - sys.path.pop() - @classmethod - def tearDownClass(cls): - for modname in ( - "inspect_stock_annotations", - "inspect_stringized_annotations", - "inspect_stringized_annotations_2", - ): - delattr(cls, modname) - del sys.modules[modname] +class BufferTests(BaseTestCase): + def test(self): + self.assertIsInstance(memoryview(b''), Buffer) + self.assertIsInstance(bytearray(), Buffer) + self.assertIsInstance(b"x", Buffer) + self.assertNotIsInstance(1, Buffer) - def test_builtin_type(self): - self.assertEqual(get_annotations(int), {}) - self.assertEqual(get_annotations(object), {}) + self.assertIsSubclass(bytearray, Buffer) + self.assertIsSubclass(memoryview, Buffer) + self.assertIsSubclass(bytes, Buffer) + self.assertNotIsSubclass(int, Buffer) - def test_format(self): - def f1(a: int): - pass + class MyRegisteredBuffer: + def __buffer__(self, flags: int) -> memoryview: + return memoryview(b'') - def f2(a: "undefined"): # noqa: F821 - pass + # On 3.12, collections.abc.Buffer does a structural compatibility check + if TYPING_3_12_0: + self.assertIsInstance(MyRegisteredBuffer(), Buffer) + self.assertIsSubclass(MyRegisteredBuffer, Buffer) + else: + self.assertNotIsInstance(MyRegisteredBuffer(), Buffer) + self.assertNotIsSubclass(MyRegisteredBuffer, Buffer) + Buffer.register(MyRegisteredBuffer) + self.assertIsInstance(MyRegisteredBuffer(), Buffer) + self.assertIsSubclass(MyRegisteredBuffer, Buffer) - self.assertEqual( - get_annotations(f1, format=Format.VALUE), {"a": int} - ) - self.assertEqual(get_annotations(f1, format=1), {"a": int}) + class MySubclassedBuffer(Buffer): + def __buffer__(self, flags: int) -> memoryview: + return memoryview(b'') - self.assertEqual( - get_annotations(f2, format=Format.FORWARDREF), - {"a": "undefined"}, - ) - # Test that the raw int also works - self.assertEqual( - get_annotations(f2, format=Format.FORWARDREF.value), - {"a": "undefined"}, - ) + self.assertIsInstance(MySubclassedBuffer(), Buffer) + self.assertIsSubclass(MySubclassedBuffer, Buffer) - self.assertEqual( - get_annotations(f1, format=Format.STRING), - {"a": "int"}, - ) - self.assertEqual( - get_annotations(f1, format=Format.STRING.value), - {"a": "int"}, - ) - with self.assertRaises(ValueError): - get_annotations(f1, format=0) +class GetOriginalBasesTests(BaseTestCase): + def test_basics(self): + T = TypeVar('T') + class A: pass + class B(Generic[T]): pass + class C(B[int]): pass + class D(B[str], float): pass + self.assertEqual(get_original_bases(A), (object,)) + self.assertEqual(get_original_bases(B), (Generic[T],)) + self.assertEqual(get_original_bases(C), (B[int],)) + self.assertEqual(get_original_bases(int), (object,)) + self.assertEqual(get_original_bases(D), (B[str], float)) - with self.assertRaises(ValueError): - get_annotations(f1, format=42) + with self.assertRaisesRegex(TypeError, "Expected an instance of type"): + get_original_bases(object()) - def test_custom_object_with_annotations(self): - class C: - def __init__(self, x: int = 0, y: str = ""): - self.__annotations__ = {"x": int, "y": str} + def test_builtin_generics(self): + class E(list[T]): pass + class F(list[int]): pass - self.assertEqual(get_annotations(C()), {"x": int, "y": str}) + self.assertEqual(get_original_bases(E), (list[T],)) + self.assertEqual(get_original_bases(F), (list[int],)) - def test_custom_format_eval_str(self): - def foo(): - pass + @skipIf( + sys.version_info[:3] == (3, 12, 0) and sys.version_info[3] in {"alpha", "beta"}, + "Early versions of py312 had a bug" + ) + def test_concrete_subclasses_of_generic_classes(self): + T = TypeVar("T") - with self.assertRaises(ValueError): - get_annotations( - foo, format=Format.FORWARDREF, eval_str=True - ) - get_annotations( - foo, format=Format.STRING, eval_str=True - ) + class FirstBase(Generic[T]): pass + class SecondBase(Generic[T]): pass + class First(FirstBase[int]): pass + class Second(SecondBase[int]): pass + class G(First, Second): pass + self.assertEqual(get_original_bases(G), (First, Second)) - def test_stock_annotations(self): - def foo(a: int, b: str): - pass + class First_(Generic[T]): pass + class Second_(Generic[T]): pass + class H(First_, Second_): pass + self.assertEqual(get_original_bases(H), (First_, Second_)) - for format in (Format.VALUE, Format.FORWARDREF): - with self.subTest(format=format): - self.assertEqual( - get_annotations(foo, format=format), - {"a": int, "b": str}, - ) - self.assertEqual( - get_annotations(foo, format=Format.STRING), - {"a": "int", "b": "str"}, - ) + def test_namedtuples(self): + # On 3.12, this should work well with typing.NamedTuple and typing_extensions.NamedTuple + # On lower versions, it will only work fully with typing_extensions.NamedTuple + if sys.version_info >= (3, 12): + namedtuple_classes = (typing.NamedTuple, typing_extensions.NamedTuple) + else: + namedtuple_classes = (typing_extensions.NamedTuple,) - foo.__annotations__ = {"a": "foo", "b": "str"} - for format in Format: - with self.subTest(format=format): - if format is Format.VALUE_WITH_FAKE_GLOBALS: - with self.assertRaisesRegex( - ValueError, - "The VALUE_WITH_FAKE_GLOBALS format is for internal use only" - ): - get_annotations(foo, format=format) - else: - self.assertEqual( - get_annotations(foo, format=format), - {"a": "foo", "b": "str"}, - ) + for NamedTuple in namedtuple_classes: # noqa: F402 + with self.subTest(cls=NamedTuple): + class ClassBasedNamedTuple(NamedTuple): + x: int - self.assertEqual( - get_annotations(foo, eval_str=True, locals=locals()), - {"a": foo, "b": str}, - ) - self.assertEqual( - get_annotations(foo, eval_str=True, globals=locals()), - {"a": foo, "b": str}, - ) + class GenericNamedTuple(NamedTuple, Generic[T]): + x: T - def test_stock_annotations_in_module(self): - isa = self.inspect_stock_annotations - - for kwargs in [ - {}, - {"eval_str": False}, - {"format": Format.VALUE}, - {"format": Format.FORWARDREF}, - {"format": Format.VALUE, "eval_str": False}, - {"format": Format.FORWARDREF, "eval_str": False}, - ]: - with self.subTest(**kwargs): - self.assertEqual( - get_annotations(isa, **kwargs), {"a": int, "b": str} - ) - self.assertEqual( - get_annotations(isa.MyClass, **kwargs), - {"a": int, "b": str}, - ) - self.assertEqual( - get_annotations(isa.function, **kwargs), - {"a": int, "b": str, "return": isa.MyClass}, - ) - self.assertEqual( - get_annotations(isa.function2, **kwargs), - {"a": int, "b": "str", "c": isa.MyClass, "return": isa.MyClass}, - ) - self.assertEqual( - get_annotations(isa.function3, **kwargs), - {"a": "int", "b": "str", "c": "MyClass"}, - ) - self.assertEqual( - get_annotations(inspect, **kwargs), {} - ) # inspect module has no annotations - self.assertEqual( - get_annotations(isa.UnannotatedClass, **kwargs), {} - ) - self.assertEqual( - get_annotations(isa.unannotated_function, **kwargs), {} - ) + CallBasedNamedTuple = NamedTuple("CallBasedNamedTuple", [("x", int)]) - for kwargs in [ - {"eval_str": True}, - {"format": Format.VALUE, "eval_str": True}, - ]: - with self.subTest(**kwargs): - self.assertEqual( - get_annotations(isa, **kwargs), {"a": int, "b": str} - ) - self.assertEqual( - get_annotations(isa.MyClass, **kwargs), - {"a": int, "b": str}, - ) - self.assertEqual( - get_annotations(isa.function, **kwargs), - {"a": int, "b": str, "return": isa.MyClass}, - ) - self.assertEqual( - get_annotations(isa.function2, **kwargs), - {"a": int, "b": str, "c": isa.MyClass, "return": isa.MyClass}, - ) - self.assertEqual( - get_annotations(isa.function3, **kwargs), - {"a": int, "b": str, "c": isa.MyClass}, + self.assertIs( + get_original_bases(ClassBasedNamedTuple)[0], NamedTuple ) - self.assertEqual(get_annotations(inspect, **kwargs), {}) self.assertEqual( - get_annotations(isa.UnannotatedClass, **kwargs), {} + get_original_bases(GenericNamedTuple), + (NamedTuple, Generic[T]) ) - self.assertEqual( - get_annotations(isa.unannotated_function, **kwargs), {} + self.assertIs( + get_original_bases(CallBasedNamedTuple)[0], NamedTuple ) - self.assertEqual( - get_annotations(isa, format=Format.STRING), - {"a": "int", "b": "str"}, - ) - self.assertEqual( - get_annotations(isa.MyClass, format=Format.STRING), - {"a": "int", "b": "str"}, - ) - mycls = "MyClass" if sys.version_info >= (3, 14) else "inspect_stock_annotations.MyClass" - self.assertEqual( - get_annotations(isa.function, format=Format.STRING), - {"a": "int", "b": "str", "return": mycls}, - ) - self.assertEqual( - get_annotations( - isa.function2, format=Format.STRING - ), - {"a": "int", "b": "str", "c": mycls, "return": mycls}, - ) - self.assertEqual( - get_annotations( - isa.function3, format=Format.STRING - ), - {"a": "int", "b": "str", "c": "MyClass"}, - ) - self.assertEqual( - get_annotations(inspect, format=Format.STRING), - {}, - ) - self.assertEqual( - get_annotations( - isa.UnannotatedClass, format=Format.STRING - ), - {}, - ) - self.assertEqual( - get_annotations( - isa.unannotated_function, format=Format.STRING - ), - {}, - ) + def test_typeddicts(self): + # On 3.12, this should work well with typing.TypedDict and typing_extensions.TypedDict + # On lower versions, it will only work fully with typing_extensions.TypedDict + if sys.version_info >= (3, 12): + typeddict_classes = (typing.TypedDict, typing_extensions.TypedDict) + else: + typeddict_classes = (typing_extensions.TypedDict,) - def test_stock_annotations_on_wrapper(self): - isa = self.inspect_stock_annotations + for TypedDict in typeddict_classes: # noqa: F402 + with self.subTest(cls=TypedDict): + class ClassBasedTypedDict(TypedDict): + x: int - wrapped = times_three(isa.function) - self.assertEqual(wrapped(1, "x"), isa.MyClass(3, "xxx")) - self.assertIsNot(wrapped.__globals__, isa.function.__globals__) - self.assertEqual( - get_annotations(wrapped), - {"a": int, "b": str, "return": isa.MyClass}, - ) - self.assertEqual( - get_annotations(wrapped, format=Format.FORWARDREF), - {"a": int, "b": str, "return": isa.MyClass}, - ) - mycls = "MyClass" if sys.version_info >= (3, 14) else "inspect_stock_annotations.MyClass" - self.assertEqual( - get_annotations(wrapped, format=Format.STRING), - {"a": "int", "b": "str", "return": mycls}, - ) - self.assertEqual( - get_annotations(wrapped, eval_str=True), - {"a": int, "b": str, "return": isa.MyClass}, - ) - self.assertEqual( - get_annotations(wrapped, eval_str=False), - {"a": int, "b": str, "return": isa.MyClass}, - ) + class GenericTypedDict(TypedDict, Generic[T]): + x: T - def test_stringized_annotations_in_module(self): - isa = self.inspect_stringized_annotations - for kwargs in [ - {}, - {"eval_str": False}, - {"format": Format.VALUE}, - {"format": Format.FORWARDREF}, - {"format": Format.STRING}, - {"format": Format.VALUE, "eval_str": False}, - {"format": Format.FORWARDREF, "eval_str": False}, - {"format": Format.STRING, "eval_str": False}, - ]: - with self.subTest(**kwargs): - self.assertEqual( - get_annotations(isa, **kwargs), {"a": "int", "b": "str"} - ) - self.assertEqual( - get_annotations(isa.MyClass, **kwargs), - {"a": "int", "b": "str"}, - ) - self.assertEqual( - get_annotations(isa.function, **kwargs), - {"a": "int", "b": "str", "return": "MyClass"}, - ) - self.assertEqual( - get_annotations(isa.function2, **kwargs), - {"a": "int", "b": "'str'", "c": "MyClass", "return": "MyClass"}, - ) - self.assertEqual( - get_annotations(isa.function3, **kwargs), - {"a": "'int'", "b": "'str'", "c": "'MyClass'"}, - ) - self.assertEqual( - get_annotations(isa.UnannotatedClass, **kwargs), {} - ) - self.assertEqual( - get_annotations(isa.unannotated_function, **kwargs), {} - ) + CallBasedTypedDict = TypedDict("CallBasedTypedDict", {"x": int}) - for kwargs in [ - {"eval_str": True}, - {"format": Format.VALUE, "eval_str": True}, - ]: - with self.subTest(**kwargs): - self.assertEqual( - get_annotations(isa, **kwargs), {"a": int, "b": str} - ) - self.assertEqual( - get_annotations(isa.MyClass, **kwargs), - {"a": int, "b": str}, - ) - self.assertEqual( - get_annotations(isa.function, **kwargs), - {"a": int, "b": str, "return": isa.MyClass}, - ) - self.assertEqual( - get_annotations(isa.function2, **kwargs), - {"a": int, "b": "str", "c": isa.MyClass, "return": isa.MyClass}, - ) - self.assertEqual( - get_annotations(isa.function3, **kwargs), - {"a": "int", "b": "str", "c": "MyClass"}, + self.assertIs( + get_original_bases(ClassBasedTypedDict)[0], + TypedDict ) self.assertEqual( - get_annotations(isa.UnannotatedClass, **kwargs), {} + get_original_bases(GenericTypedDict), + (TypedDict, Generic[T]) ) - self.assertEqual( - get_annotations(isa.unannotated_function, **kwargs), {} + self.assertIs( + get_original_bases(CallBasedTypedDict)[0], + TypedDict ) - def test_stringized_annotations_in_empty_module(self): - isa2 = self.inspect_stringized_annotations_2 - self.assertEqual(get_annotations(isa2), {}) - self.assertEqual(get_annotations(isa2, eval_str=True), {}) - self.assertEqual(get_annotations(isa2, eval_str=False), {}) - - def test_stringized_annotations_on_wrapper(self): - isa = self.inspect_stringized_annotations - wrapped = times_three(isa.function) - self.assertEqual(wrapped(1, "x"), isa.MyClass(3, "xxx")) - self.assertIsNot(wrapped.__globals__, isa.function.__globals__) - self.assertEqual( - get_annotations(wrapped), - {"a": "int", "b": "str", "return": "MyClass"}, - ) - self.assertEqual( - get_annotations(wrapped, eval_str=True), - {"a": int, "b": str, "return": isa.MyClass}, - ) - self.assertEqual( - get_annotations(wrapped, eval_str=False), - {"a": "int", "b": "str", "return": "MyClass"}, - ) - - def test_stringized_annotations_on_class(self): - isa = self.inspect_stringized_annotations - # test that local namespace lookups work - self.assertEqual( - get_annotations(isa.MyClassWithLocalAnnotations), - {"x": "mytype"}, - ) - self.assertEqual( - get_annotations(isa.MyClassWithLocalAnnotations, eval_str=True), - {"x": int}, - ) - - def test_modify_annotations(self): - def f(x: int): - pass - - self.assertEqual(get_annotations(f), {"x": int}) - self.assertEqual( - get_annotations(f, format=Format.FORWARDREF), - {"x": int}, - ) - - f.__annotations__["x"] = str - self.assertEqual(get_annotations(f), {"x": str}) - - -class TestGetAnnotationsMetaclasses(BaseTestCase): - def test_annotated_meta(self): - class Meta(type): - a: int - - class X(metaclass=Meta): - pass - - class Y(metaclass=Meta): - b: float - - self.assertEqual(get_annotations(Meta), {"a": int}) - self.assertEqual(get_annotations(X), {}) - self.assertEqual(get_annotations(Y), {"b": float}) - def test_unannotated_meta(self): - class Meta(type): pass +class TypeAliasTypeTests(BaseTestCase): + def test_attributes(self): + Simple = TypeAliasType("Simple", int) + self.assertEqual(Simple.__name__, "Simple") + self.assertIs(Simple.__value__, int) + self.assertEqual(Simple.__type_params__, ()) + self.assertEqual(Simple.__parameters__, ()) - class X(metaclass=Meta): - a: str + T = TypeVar("T") + ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) + self.assertEqual(ListOrSetT.__name__, "ListOrSetT") + self.assertEqual(ListOrSetT.__value__, Union[List[T], Set[T]]) + self.assertEqual(ListOrSetT.__type_params__, (T,)) + self.assertEqual(ListOrSetT.__parameters__, (T,)) - class Y(X): pass + Ts = TypeVarTuple("Ts") + Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) + self.assertEqual(Variadic.__name__, "Variadic") + self.assertEqual(Variadic.__value__, Tuple[int, Unpack[Ts]]) + self.assertEqual(Variadic.__type_params__, (Ts,)) + self.assertEqual(Variadic.__parameters__, tuple(iter(Ts))) - self.assertEqual(get_annotations(Meta), {}) - self.assertEqual(get_annotations(Y), {}) - self.assertEqual(get_annotations(X), {"a": str}) + P = ParamSpec('P') + CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P, )) + self.assertEqual(CallableP.__name__, "CallableP") + self.assertEqual(CallableP.__value__, Callable[P, Any]) + self.assertEqual(CallableP.__type_params__, (P,)) + self.assertEqual(CallableP.__parameters__, (P,)) - def test_ordering(self): - # Based on a sample by David Ellis - # https://discuss.python.org/t/pep-749-implementing-pep-649/54974/38 + def test_alias_types_and_substitutions(self): + T = TypeVar('T') + T2 = TypeVar('T2') + T_default = TypeVar("T_default", default=int) + Ts = TypeVarTuple("Ts") + P = ParamSpec('P') - def make_classes(): - class Meta(type): - a: int - expected_annotations = {"a": int} + test_argument_cases = { + # arguments : expected parameters + int : (), + ... : (), + None : (), + T2 : (T2,), + Union[int, List[T2]] : (T2,), + Tuple[int, str] : (), + Tuple[T, T_default, T2] : (T, T_default, T2), + Tuple[Unpack[Ts]] : (Ts,), + Callable[[Unpack[Ts]], T2] : (Ts, T2), + Callable[P, T2] : (P, T2), + Callable[Concatenate[T2, P], T_default] : (T2, P, T_default), + TypeAliasType("NestedAlias", List[T], type_params=(T,))[T2] : (T2,), + Unpack[Ts] : (Ts,), + Unpack[Tuple[int, T2]] : (T2,), + Concatenate[int, P] : (P,), + # Not tested usage of bare TypeVarTuple, would need 3.11+ + # Ts : (Ts,), # invalid case + } - class A(type, metaclass=Meta): - b: float - expected_annotations = {"b": float} + test_alias_cases = [ + # Simple cases + TypeAliasType("ListT", List[T], type_params=(T,)), + TypeAliasType("UnionT", Union[int, List[T]], type_params=(T,)), + # Value has no parameter but in type_param + TypeAliasType("ValueWithoutT", int, type_params=(T,)), + # Callable + TypeAliasType("CallableP", Callable[P, Any], type_params=(P, )), + TypeAliasType("CallableT", Callable[..., T], type_params=(T, )), + TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, )), + # TypeVarTuple + TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)), + # TypeVar with default + TypeAliasType("TupleT_default", Tuple[T_default, T], type_params=(T, T_default)), + TypeAliasType("CallableT_default", Callable[[T], T_default], type_params=(T, T_default)), + ] - class B(metaclass=A): - c: str - expected_annotations = {"c": str} + for alias in test_alias_cases: + with self.subTest(alias=alias, args=[]): + subscripted = alias[[]] + self.assertEqual(get_args(subscripted), ([],)) + self.assertEqual(subscripted.__parameters__, ()) + with self.subTest(alias=alias, args=()): + subscripted = alias[()] + self.assertEqual(get_args(subscripted), ()) + self.assertEqual(subscripted.__parameters__, ()) + with self.subTest(alias=alias, args=(int, float)): + subscripted = alias[int, float] + self.assertEqual(get_args(subscripted), (int, float)) + self.assertEqual(subscripted.__parameters__, ()) + with self.subTest(alias=alias, args=[int, float]): + subscripted = alias[[int, float]] + self.assertEqual(get_args(subscripted), ([int, float],)) + self.assertEqual(subscripted.__parameters__, ()) + for expected_args, expected_parameters in test_argument_cases.items(): + with self.subTest(alias=alias, args=expected_args): + self.assertEqual(get_args(alias[expected_args]), (expected_args,)) + self.assertEqual(alias[expected_args].__parameters__, expected_parameters) - class C(B): - expected_annotations = {} + def test_cannot_set_attributes(self): + Simple = TypeAliasType("Simple", int) + with self.assertRaisesRegex(AttributeError, "readonly attribute"): + Simple.__name__ = "NewName" + with self.assertRaisesRegex( + AttributeError, + "attribute '__value__' of 'typing.TypeAliasType' objects is not writable", + ): + Simple.__value__ = str + with self.assertRaisesRegex( + AttributeError, + "attribute '__type_params__' of 'typing.TypeAliasType' objects is not writable", + ): + Simple.__type_params__ = (T,) + with self.assertRaisesRegex( + AttributeError, + "attribute '__parameters__' of 'typing.TypeAliasType' objects is not writable", + ): + Simple.__parameters__ = (T,) + with self.assertRaisesRegex( + AttributeError, + "attribute '__module__' of 'typing.TypeAliasType' objects is not writable", + ): + Simple.__module__ = 42 + with self.assertRaisesRegex( + AttributeError, + "'typing.TypeAliasType' object has no attribute 'some_attribute'", + ): + Simple.some_attribute = "not allowed" - class D(metaclass=Meta): - expected_annotations = {} + def test_cannot_delete_attributes(self): + Simple = TypeAliasType("Simple", int) + with self.assertRaisesRegex(AttributeError, "readonly attribute"): + del Simple.__name__ + with self.assertRaisesRegex( + AttributeError, + "attribute '__value__' of 'typing.TypeAliasType' objects is not writable", + ): + del Simple.__value__ + with self.assertRaisesRegex( + AttributeError, + "'typing.TypeAliasType' object has no attribute 'some_attribute'", + ): + del Simple.some_attribute - return Meta, A, B, C, D + def test_or(self): + Alias = TypeAliasType("Alias", int) + if sys.version_info >= (3, 10): + self.assertEqual(Alias | int, Union[Alias, int]) + self.assertEqual(Alias | None, Union[Alias, None]) + self.assertEqual(Alias | (int | str), Union[Alias, int | str]) + self.assertEqual(Alias | list[float], Union[Alias, list[float]]) - classes = make_classes() - class_count = len(classes) - for order in itertools.permutations(range(class_count), class_count): - names = ", ".join(classes[i].__name__ for i in order) - with self.subTest(names=names): - classes = make_classes() # Regenerate classes - for i in order: - get_annotations(classes[i]) - for c in classes: - with self.subTest(c=c): - self.assertEqual(get_annotations(c), c.expected_annotations) + if sys.version_info >= (3, 12): + Alias2 = typing.TypeAliasType("Alias2", str) + self.assertEqual(Alias | Alias2, Union[Alias, Alias2]) + else: + with self.assertRaises(TypeError): + Alias | int + # Rejected on all versions + with self.assertRaises(TypeError): + Alias | "Ref" + def test_getitem(self): + T = TypeVar('T') + ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) + subscripted = ListOrSetT[int] + self.assertEqual(get_args(subscripted), (int,)) + self.assertIs(get_origin(subscripted), ListOrSetT) + with self.assertRaisesRegex(TypeError, + "not a generic class" + # types.GenericAlias raises a different error in 3.10 + if sys.version_info[:2] != (3, 10) + else "There are no type variables left in ListOrSetT" + ): + subscripted[int] -@skipIf(STRINGIZED_ANNOTATIONS_PEP_695 is None, "PEP 695 has yet to be") -class TestGetAnnotationsWithPEP695(BaseTestCase): - @classmethod - def setUpClass(cls): - with tempfile.TemporaryDirectory() as tempdir: - sys.path.append(tempdir) - Path(tempdir, "inspect_stringized_annotations_pep_695.py").write_text(STRINGIZED_ANNOTATIONS_PEP_695) - cls.inspect_stringized_annotations_pep_695 = importlib.import_module( - "inspect_stringized_annotations_pep_695" - ) - sys.path.pop() - @classmethod - def tearDownClass(cls): - del cls.inspect_stringized_annotations_pep_695 - del sys.modules["inspect_stringized_annotations_pep_695"] - - def test_pep695_generic_class_with_future_annotations(self): - ann_module695 = self.inspect_stringized_annotations_pep_695 - A_annotations = get_annotations(ann_module695.A, eval_str=True) - A_type_params = ann_module695.A.__type_params__ - self.assertIs(A_annotations["x"], A_type_params[0]) - self.assertEqual(A_annotations["y"].__args__[0], Unpack[A_type_params[1]]) - self.assertIs(A_annotations["z"].__args__[0], A_type_params[2]) - - def test_pep695_generic_class_with_future_annotations_and_local_shadowing(self): - B_annotations = get_annotations( - self.inspect_stringized_annotations_pep_695.B, eval_str=True - ) - self.assertEqual(B_annotations, {"x": int, "y": str, "z": bytes}) + still_generic = ListOrSetT[Iterable[T]] + self.assertEqual(get_args(still_generic), (Iterable[T],)) + self.assertIs(get_origin(still_generic), ListOrSetT) + fully_subscripted = still_generic[float] + self.assertEqual(get_args(fully_subscripted), (Iterable[float],)) + self.assertIs(get_origin(fully_subscripted), ListOrSetT) - def test_pep695_generic_class_with_future_annotations_name_clash_with_global_vars(self): - ann_module695 = self.inspect_stringized_annotations_pep_695 - C_annotations = get_annotations(ann_module695.C, eval_str=True) - self.assertEqual( - set(C_annotations.values()), - set(ann_module695.C.__type_params__) - ) + ValueWithoutTypeVar = TypeAliasType("ValueWithoutTypeVar", int, type_params=(T,)) + still_subscripted = ValueWithoutTypeVar[str] + self.assertEqual(get_args(still_subscripted), (str,)) - def test_pep_695_generic_function_with_future_annotations(self): - ann_module695 = self.inspect_stringized_annotations_pep_695 - generic_func_annotations = get_annotations( - ann_module695.generic_function, eval_str=True - ) - func_t_params = ann_module695.generic_function.__type_params__ - self.assertEqual( - generic_func_annotations.keys(), {"x", "y", "z", "zz", "return"} - ) - self.assertIs(generic_func_annotations["x"], func_t_params[0]) - self.assertEqual(generic_func_annotations["y"], Unpack[func_t_params[1]]) - self.assertIs(generic_func_annotations["z"].__origin__, func_t_params[2]) - self.assertIs(generic_func_annotations["zz"].__origin__, func_t_params[2]) + def test_callable_without_concatenate(self): + P = ParamSpec('P') + CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) + get_args_test_cases = [ + # List of (alias, expected_args) + # () -> Any + (CallableP[()], ()), + (CallableP[[]], ([],)), + # (int) -> Any + (CallableP[int], (int,)), + (CallableP[[int]], ([int],)), + # (int, int) -> Any + (CallableP[int, int], (int, int)), + (CallableP[[int, int]], ([int, int],)), + # (...) -> Any + (CallableP[...], (...,)), + # (int, ...) -> Any + (CallableP[[int, ...]], ([int, ...],)), + ] - def test_pep_695_generic_function_with_future_annotations_name_clash_with_global_vars(self): - self.assertEqual( - set( - get_annotations( - self.inspect_stringized_annotations_pep_695.generic_function_2, - eval_str=True - ).values() - ), - set( - self.inspect_stringized_annotations_pep_695.generic_function_2.__type_params__ - ) - ) + for index, (expression, expected_args) in enumerate(get_args_test_cases): + with self.subTest(index=index, expression=expression): + self.assertEqual(get_args(expression), expected_args) - def test_pep_695_generic_method_with_future_annotations(self): - ann_module695 = self.inspect_stringized_annotations_pep_695 - generic_method_annotations = get_annotations( - ann_module695.D.generic_method, eval_str=True - ) - params = { - param.__name__: param - for param in ann_module695.D.generic_method.__type_params__ - } - self.assertEqual( - generic_method_annotations, - {"x": params["Foo"], "y": params["Bar"], "return": None} - ) + self.assertEqual(CallableP[...], CallableP[(...,)]) + # (T) -> Any + CallableT = CallableP[T] + self.assertEqual(get_args(CallableT), (T,)) + self.assertEqual(CallableT.__parameters__, (T,)) - def test_pep_695_generic_method_with_future_annotations_name_clash_with_global_vars(self): - self.assertEqual( - set( - get_annotations( - self.inspect_stringized_annotations_pep_695.D.generic_method_2, - eval_str=True - ).values() - ), - set( - self.inspect_stringized_annotations_pep_695.D.generic_method_2.__type_params__ - ) - ) + def test_callable_with_concatenate(self): + P = ParamSpec('P') + P2 = ParamSpec('P2') + CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) - def test_pep_695_generic_method_with_future_annotations_name_clash_with_global_and_local_vars(self): - self.assertEqual( - get_annotations( - self.inspect_stringized_annotations_pep_695.E, eval_str=True - ), - {"x": str}, - ) + callable_concat = CallableP[Concatenate[int, P2]] + self.assertEqual(callable_concat.__parameters__, (P2,)) + concat_usage = callable_concat[str] + with self.subTest("get_args of Concatenate in TypeAliasType"): + if not TYPING_3_10_0: + # args are: ([, ~P2],) + self.skipTest("Nested ParamSpec is not substituted") + self.assertEqual(get_args(concat_usage), ((int, str),)) + with self.subTest("Equality of parameter_expression without []"): + if not TYPING_3_10_0: + self.skipTest("Nested list is invalid type form") + self.assertEqual(concat_usage, callable_concat[[str]]) - def test_pep_695_generics_with_future_annotations_nested_in_function(self): - results = self.inspect_stringized_annotations_pep_695.nested() + def test_substitution(self): + T = TypeVar('T') + Ts = TypeVarTuple("Ts") - self.assertEqual( - set(results.F_annotations.values()), - set(results.F.__type_params__) - ) - self.assertEqual( - set(results.F_meth_annotations.values()), - set(results.F.generic_method.__type_params__) - ) - self.assertNotEqual( - set(results.F_meth_annotations.values()), - set(results.F.__type_params__) - ) - self.assertEqual( - set(results.F_meth_annotations.values()).intersection(results.F.__type_params__), - set() - ) + CallableTs = TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, )) + unpack_callable = CallableTs[Unpack[Tuple[int, T]]] + self.assertEqual(get_args(unpack_callable), (Unpack[Tuple[int, T]],)) - self.assertEqual(results.G_annotations, {"x": str}) + P = ParamSpec('P') + CallableP = TypeAliasType("CallableP", Callable[P, T], type_params=(P, T)) + callable_concat = CallableP[Concatenate[int, P], Any] + self.assertEqual(get_args(callable_concat), (Concatenate[int, P], Any)) - self.assertEqual( - set(results.generic_func_annotations.values()), - set(results.generic_func.__type_params__) - ) + def test_wrong_amount_of_parameters(self): + T = TypeVar('T') + T2 = TypeVar("T2") + P = ParamSpec('P') + ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) + TwoT = TypeAliasType("TwoT", Union[List[T], Set[T2]], type_params=(T, T2)) + CallablePT = TypeAliasType("CallablePT", Callable[P, T], type_params=(P, T)) + # Not enough parameters + test_cases = [ + # not_enough + (TwoT[int], [(int,), ()]), + (TwoT[T], [(T,), (T,)]), + # callable and not enough + (CallablePT[int], [(int,), ()]), + # too many + (ListOrSetT[int, bool], [(int, bool), ()]), + # callable and too many + (CallablePT[str, float, int], [(str, float, int), ()]), + # Check if TypeVar is still present even if over substituted + (ListOrSetT[int, T], [(int, T), (T,)]), + # With and without list for ParamSpec + (CallablePT[str, float, T], [(str, float, T), (T,)]), + (CallablePT[[str], float, int, T2], [([str], float, int, T2), (T2,)]), + ] -class EvaluateForwardRefTests(BaseTestCase): - def test_evaluate_forward_ref(self): - int_ref = typing_extensions.ForwardRef('int') - self.assertIs(typing_extensions.evaluate_forward_ref(int_ref), int) - self.assertIs( - typing_extensions.evaluate_forward_ref(int_ref, type_params=()), - int, - ) - self.assertIs( - typing_extensions.evaluate_forward_ref(int_ref, format=typing_extensions.Format.VALUE), - int, - ) - self.assertIs( - typing_extensions.evaluate_forward_ref( - int_ref, format=typing_extensions.Format.FORWARDREF, - ), - int, - ) - self.assertEqual( - typing_extensions.evaluate_forward_ref( - int_ref, format=typing_extensions.Format.STRING, - ), - 'int', - ) + for index, (alias, [expected_args, expected_params]) in enumerate(test_cases): + with self.subTest(index=index, alias=alias): + self.assertEqual(get_args(alias), expected_args) + self.assertEqual(alias.__parameters__, expected_params) - def test_evaluate_forward_ref_undefined(self): - missing = typing_extensions.ForwardRef('missing') - with self.assertRaises(NameError): - typing_extensions.evaluate_forward_ref(missing) - self.assertIs( - typing_extensions.evaluate_forward_ref( - missing, format=typing_extensions.Format.FORWARDREF, - ), - missing, - ) - self.assertEqual( - typing_extensions.evaluate_forward_ref( - missing, format=typing_extensions.Format.STRING, - ), - "missing", - ) + # The condition should align with the version of GeneriAlias usage in __getitem__ or be 3.11+ + @skipIf(TYPING_3_10_0, "Most arguments are allowed in 3.11+ or with GenericAlias") + def test_invalid_cases_before_3_10(self): + T = TypeVar('T') + ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) + with self.assertRaises(TypeError): + ListOrSetT[Generic[T]] + with self.assertRaises(TypeError): + ListOrSetT[(Generic[T], )] - def test_evaluate_forward_ref_nested(self): - ref = typing_extensions.ForwardRef("Union[int, list['str']]") - ns = {"Union": Union} - if sys.version_info >= (3, 11): - expected = Union[int, list[str]] - else: - expected = Union[int, list['str']] # TODO: evaluate nested forward refs in Python < 3.11 - self.assertEqual( - typing_extensions.evaluate_forward_ref(ref, globals=ns), - expected, - ) - self.assertEqual( - typing_extensions.evaluate_forward_ref( - ref, globals=ns, format=typing_extensions.Format.FORWARDREF - ), - expected, - ) - self.assertEqual( - typing_extensions.evaluate_forward_ref(ref, format=typing_extensions.Format.STRING), - "Union[int, list['str']]", - ) + def test_unpack_parameter_collection(self): + Ts = TypeVarTuple("Ts") - why = typing_extensions.ForwardRef('"\'str\'"') - self.assertIs(typing_extensions.evaluate_forward_ref(why), str) + class Foo(Generic[Unpack[Ts]]): + bar: Tuple[Unpack[Ts]] - @skipUnless(sys.version_info >= (3, 10), "Relies on PEP 604") - def test_evaluate_forward_ref_nested_pep604(self): - ref = typing_extensions.ForwardRef("int | list['str']") - if sys.version_info >= (3, 11): - expected = int | list[str] - else: - expected = int | list['str'] # TODO: evaluate nested forward refs in Python < 3.11 - self.assertEqual( - typing_extensions.evaluate_forward_ref(ref), - expected, - ) - self.assertEqual( - typing_extensions.evaluate_forward_ref(ref, format=typing_extensions.Format.FORWARDREF), - expected, - ) - self.assertEqual( - typing_extensions.evaluate_forward_ref(ref, format=typing_extensions.Format.STRING), - "int | list['str']", - ) + FooAlias = TypeAliasType("FooAlias", Foo[Unpack[Ts]], type_params=(Ts,)) + self.assertEqual(FooAlias[Unpack[Tuple[str]]].__parameters__, ()) + self.assertEqual(FooAlias[Unpack[Tuple[T]]].__parameters__, (T,)) - def test_evaluate_forward_ref_none(self): - none_ref = typing_extensions.ForwardRef('None') - self.assertIs(typing_extensions.evaluate_forward_ref(none_ref), None) + P = ParamSpec("P") + CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) + call_int_T = CallableP[Unpack[Tuple[int, T]]] + self.assertEqual(call_int_T.__parameters__, (T,)) - def test_globals(self): - A = "str" - ref = typing_extensions.ForwardRef('list[A]') - with self.assertRaises(NameError): - typing_extensions.evaluate_forward_ref(ref) - self.assertEqual( - typing_extensions.evaluate_forward_ref(ref, globals={'A': A}), - list[str] if sys.version_info >= (3, 11) else list['str'], - ) + def test_alias_attributes(self): + T = TypeVar('T') + T2 = TypeVar('T2') + ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) - def test_owner(self): - ref = typing_extensions.ForwardRef("A") + subscripted = ListOrSetT[int] + self.assertEqual(subscripted.__module__, ListOrSetT.__module__) + self.assertEqual(subscripted.__name__, "ListOrSetT") + self.assertEqual(subscripted.__value__, Union[List[T], Set[T]]) + self.assertEqual(subscripted.__type_params__, (T,)) - with self.assertRaises(NameError): - typing_extensions.evaluate_forward_ref(ref) + still_generic = ListOrSetT[Iterable[T2]] + self.assertEqual(still_generic.__module__, ListOrSetT.__module__) + self.assertEqual(still_generic.__name__, "ListOrSetT") + self.assertEqual(still_generic.__value__, Union[List[T], Set[T]]) + self.assertEqual(still_generic.__type_params__, (T,)) - # We default to the globals of `owner`, - # so it no longer raises `NameError` - self.assertIs( - typing_extensions.evaluate_forward_ref(ref, owner=Loop), A - ) + fully_subscripted = still_generic[float] + self.assertEqual(fully_subscripted.__module__, ListOrSetT.__module__) + self.assertEqual(fully_subscripted.__name__, "ListOrSetT") + self.assertEqual(fully_subscripted.__value__, Union[List[T], Set[T]]) + self.assertEqual(fully_subscripted.__type_params__, (T,)) - @skipUnless(sys.version_info >= (3, 14), "Not yet implemented in Python < 3.14") - def test_inherited_owner(self): - # owner passed to evaluate_forward_ref - ref = typing_extensions.ForwardRef("list['A']") - self.assertEqual( - typing_extensions.evaluate_forward_ref(ref, owner=Loop), - list[A], - ) + def test_subscription_without_type_params(self): + Simple = TypeAliasType("Simple", int) + with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): + Simple[int] - # owner set on the ForwardRef - ref = typing_extensions.ForwardRef("list['A']", owner=Loop) - self.assertEqual( - typing_extensions.evaluate_forward_ref(ref), - list[A], - ) + # A TypeVar in the value does not allow subscription + T = TypeVar('T') + MissingTypeParamsErr = TypeAliasType("MissingTypeParamsErr", List[T]) + self.assertEqual(MissingTypeParamsErr.__type_params__, ()) + self.assertEqual(MissingTypeParamsErr.__parameters__, ()) + with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): + MissingTypeParamsErr[int] - @skipUnless(sys.version_info >= (3, 14), "Not yet implemented in Python < 3.14") - def test_partial_evaluation(self): - ref = typing_extensions.ForwardRef("list[A]") - with self.assertRaises(NameError): - typing_extensions.evaluate_forward_ref(ref) + def test_pickle(self): + global Alias + Alias = TypeAliasType("Alias", int) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(proto=proto): + pickled = pickle.dumps(Alias, proto) + unpickled = pickle.loads(pickled) + self.assertIs(unpickled, Alias) - self.assertEqual( - typing_extensions.evaluate_forward_ref(ref, format=typing_extensions.Format.FORWARDREF), - list[EqualToForwardRef('A')], - ) + def test_no_instance_subclassing(self): + with self.assertRaises(TypeError): + class MyAlias(TypeAliasType): + pass - def test_global_constant(self): - if sys.version_info[:3] > (3, 10, 0): - self.assertTrue(_FORWARD_REF_HAS_CLASS) + def test_type_var_compatibility(self): + # Regression test to assure compatibility with typing variants + typingT = typing.TypeVar('typingT') + T1 = TypeAliasType("TypingTypeVar", ..., type_params=(typingT,)) + self.assertEqual(T1.__type_params__, (typingT,)) - def test_forward_ref_fallback(self): - with self.assertRaises(NameError): - evaluate_forward_ref(typing.ForwardRef("doesntexist")) - ref = typing.ForwardRef("doesntexist") - self.assertIs(evaluate_forward_ref(ref, format=Format.FORWARDREF), ref) - - class X: - unresolvable = "doesnotexist2" - - evaluated_ref = evaluate_forward_ref( - typing.ForwardRef("X.unresolvable"), - locals={"X": X}, - type_params=None, - format=Format.FORWARDREF, - ) - self.assertEqual(evaluated_ref, EqualToForwardRef("doesnotexist2")) - - def test_evaluate_with_type_params(self): - # Use a T name that is not in globals - self.assertNotIn("Tx", globals()) - if not TYPING_3_12_0: - Tx = TypeVar("Tx") - class Gen(Generic[Tx]): - alias = int - if not hasattr(Gen, "__type_params__"): - Gen.__type_params__ = (Tx,) - self.assertEqual(Gen.__type_params__, (Tx,)) - del Tx - else: - ns = {} - exec(textwrap.dedent(""" - class Gen[Tx]: - alias = int - """), None, ns) - Gen = ns["Gen"] - - # owner=None, type_params=None - # NOTE: The behavior of owner=None might change in the future when ForwardRef.__owner__ is available - with self.assertRaises(NameError): - evaluate_forward_ref(typing.ForwardRef("Tx")) - with self.assertRaises(NameError): - evaluate_forward_ref(typing.ForwardRef("Tx"), type_params=()) - with self.assertRaises(NameError): - evaluate_forward_ref(typing.ForwardRef("Tx"), owner=int) + # Test typing_extensions backports + textT = TypeVar('textT') + T2 = TypeAliasType("TypingExtTypeVar", ..., type_params=(textT,)) + self.assertEqual(T2.__type_params__, (textT,)) - (Tx,) = Gen.__type_params__ - self.assertIs(evaluate_forward_ref(typing.ForwardRef("Tx"), type_params=Gen.__type_params__), Tx) + textP = ParamSpec("textP") + T3 = TypeAliasType("TypingExtParamSpec", ..., type_params=(textP,)) + self.assertEqual(T3.__type_params__, (textP,)) - # For this test its important that Tx is not a global variable, i.e. do not use "T" here - self.assertNotIn("Tx", globals()) - self.assertIs(evaluate_forward_ref(typing.ForwardRef("Tx"), owner=Gen), Tx) + textTs = TypeVarTuple("textTs") + T4 = TypeAliasType("TypingExtTypeVarTuple", ..., type_params=(textTs,)) + self.assertEqual(T4.__type_params__, (textTs,)) - # Different type_params take precedence - not_Tx = TypeVar("Tx") # different TypeVar with same name - self.assertIs(evaluate_forward_ref(typing.ForwardRef("Tx"), type_params=(not_Tx,), owner=Gen), not_Tx) + @skipUnless(TYPING_3_10_0, "typing.ParamSpec is not available before 3.10") + def test_param_spec_compatibility(self): + # Regression test to assure compatibility with typing variant + typingP = typing.ParamSpec("typingP") + T5 = TypeAliasType("TypingParamSpec", ..., type_params=(typingP,)) + self.assertEqual(T5.__type_params__, (typingP,)) - # globals do not take higher precedence - self.assertIs(evaluate_forward_ref(typing.ForwardRef("Tx", is_class=True), owner=Gen, globals={"Tx": str}), Tx) - self.assertIs(evaluate_forward_ref(typing.ForwardRef("Tx", is_class=True), owner=Gen, type_params=(not_Tx,), globals={"Tx": str}), not_Tx) + @skipUnless(TYPING_3_12_0, "typing.TypeVarTuple is not available before 3.12") + def test_type_var_tuple_compatibility(self): + # Regression test to assure compatibility with typing variant + typingTs = typing.TypeVarTuple("typingTs") + T6 = TypeAliasType("TypingTypeVarTuple", ..., type_params=(typingTs,)) + self.assertEqual(T6.__type_params__, (typingTs,)) - with self.assertRaises(NameError): - evaluate_forward_ref(typing.ForwardRef("alias"), type_params=Gen.__type_params__) - self.assertIs(evaluate_forward_ref(typing.ForwardRef("alias"), owner=Gen), int) - # If you pass custom locals, we don't look at the owner's locals - with self.assertRaises(NameError): - evaluate_forward_ref(typing.ForwardRef("alias"), owner=Gen, locals={}) - # But if the name exists in the locals, it works - self.assertIs( - evaluate_forward_ref(typing.ForwardRef("alias"), owner=Gen, locals={"alias": str}), str - ) + def test_type_params_possibilities(self): + T = TypeVar('T') + # Test not a tuple + with self.assertRaisesRegex(TypeError, "type_params must be a tuple"): + TypeAliasType("InvalidTypeParams", List[T], type_params=[T]) - @skipUnless( - HAS_FORWARD_MODULE, "Needs module 'forward' to test forward references" - ) - def test_fwdref_with_module(self): - self.assertIs( - evaluate_forward_ref(typing.ForwardRef("Counter", module="collections")), collections.Counter - ) - self.assertEqual( - evaluate_forward_ref(typing.ForwardRef("Counter[int]", module="collections")), - collections.Counter[int], - ) + # Test default order and other invalid inputs + T_default = TypeVar('T_default', default=int) + Ts = TypeVarTuple('Ts') + Ts_default = TypeVarTuple('Ts_default', default=Unpack[Tuple[str, int]]) + P = ParamSpec('P') + P_default = ParamSpec('P_default', default=[str, int]) - with self.assertRaises(NameError): - # If globals are passed explicitly, we don't look at the module dict - evaluate_forward_ref(typing.ForwardRef("Format", module="annotationlib"), globals={}) + # NOTE: PEP 696 states: "TypeVars with defaults cannot immediately follow TypeVarTuples" + # this is currently not enforced for the type statement and is not tested. + # PEP 695: Double usage of the same name is also not enforced and not tested. + valid_cases = [ + (T, P, Ts), + (T, Ts_default), + (P_default, T_default), + (P, T_default, Ts_default), + (T_default, P_default, Ts_default), + ] + invalid_cases = [ + ((T_default, T), f"non-default type parameter '{T!r}' follows default"), + ((P_default, P), f"non-default type parameter '{P!r}' follows default"), + ((Ts_default, T), f"non-default type parameter '{T!r}' follows default"), + # Only type params are accepted + ((1,), "Expected a type param, got 1"), + ((str,), f"Expected a type param, got {str!r}"), + # Unpack is not a TypeVar but isinstance(Unpack[Ts], TypeVar) is True in Python < 3.12 + ((Unpack[Ts],), f"Expected a type param, got {re.escape(repr(Unpack[Ts]))}"), + ] - def test_fwdref_to_builtin(self): - self.assertIs(evaluate_forward_ref(typing.ForwardRef("int")), int) - if HAS_FORWARD_MODULE: - self.assertIs(evaluate_forward_ref(typing.ForwardRef("int", module="collections")), int) - self.assertIs(evaluate_forward_ref(typing.ForwardRef("int"), owner=str), int) + for case in valid_cases: + with self.subTest(type_params=case): + TypeAliasType("OkCase", List[T], type_params=case) + for case, msg in invalid_cases: + with self.subTest(type_params=case): + with self.assertRaisesRegex(TypeError, msg): + TypeAliasType("InvalidCase", List[T], type_params=case) - # builtins are still searched with explicit globals - self.assertIs(evaluate_forward_ref(typing.ForwardRef("int"), globals={}), int) +class DocTests(BaseTestCase): + def test_annotation(self): - def test_fwdref_with_globals(self): - # explicit values in globals have precedence - obj = object() - self.assertIs(evaluate_forward_ref(typing.ForwardRef("int"), globals={"int": obj}), obj) + def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: pass - def test_fwdref_with_owner(self): - self.assertEqual( - evaluate_forward_ref(typing.ForwardRef("Counter[int]"), owner=collections), - collections.Counter[int], - ) + hints = get_type_hints(hi, include_extras=True) + doc_info = hints["to"].__metadata__[0] + self.assertEqual(doc_info.documentation, "Who to say hi to") + self.assertIsInstance(doc_info, Doc) - def test_name_lookup_without_eval(self): - # test the codepath where we look up simple names directly in the - # namespaces without going through eval() - self.assertIs(evaluate_forward_ref(typing.ForwardRef("int")), int) - self.assertIs(evaluate_forward_ref(typing.ForwardRef("int"), locals={"int": str}), str) - self.assertIs( - evaluate_forward_ref(typing.ForwardRef("int"), locals={"int": float}, globals={"int": str}), - float, - ) - self.assertIs(evaluate_forward_ref(typing.ForwardRef("int"), globals={"int": str}), str) - import builtins + def test_repr(self): + doc_info = Doc("Who to say hi to") + self.assertEqual(repr(doc_info), "Doc('Who to say hi to')") - from test import support - with support.swap_attr(builtins, "int", dict): - self.assertIs(evaluate_forward_ref(typing.ForwardRef("int")), dict) + def test_hashability(self): + doc_info = Doc("Who to say hi to") + self.assertIsInstance(hash(doc_info), int) + self.assertNotEqual(hash(doc_info), hash(Doc("Who not to say hi to"))) - def test_nested_strings(self): - # This variable must have a different name TypeVar - Tx = TypeVar("Tx") + def test_equality(self): + doc_info = Doc("Who to say hi to") + # Equal to itself + self.assertEqual(doc_info, doc_info) + # Equal to another instance with the same string + self.assertEqual(doc_info, Doc("Who to say hi to")) + # Not equal to another instance with a different string + self.assertNotEqual(doc_info, Doc("Who not to say hi to")) - class Y(Generic[Tx]): - a = "X" - bT = "Y[T_nonlocal]" + def test_pickle(self): + doc_info = Doc("Who to say hi to") + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + pickled = pickle.dumps(doc_info, protocol=proto) + self.assertEqual(doc_info, pickle.loads(pickled)) - Z = TypeAliasType("Z", Y[Tx], type_params=(Tx,)) - evaluated_ref1a = evaluate_forward_ref(typing.ForwardRef("Y[Y['Tx']]"), locals={"Y": Y, "Tx": Tx}) - self.assertEqual(get_origin(evaluated_ref1a), Y) - self.assertEqual(get_args(evaluated_ref1a), (Y[Tx],)) +@skipUnless( + hasattr(typing_extensions, "CapsuleType"), + "CapsuleType is not available on all Python implementations" +) +class CapsuleTypeTests(BaseTestCase): + def test_capsule_type(self): + import _datetime + self.assertIsInstance(_datetime.datetime_CAPI, typing_extensions.CapsuleType) - evaluated_ref1b = evaluate_forward_ref( - typing.ForwardRef("Y[Y['Tx']]"), locals={"Y": Y}, type_params=(Tx,) - ) - self.assertEqual(get_origin(evaluated_ref1b), Y) - self.assertEqual(get_args(evaluated_ref1b), (Y[Tx],)) - - with self.subTest("nested string of TypeVar"): - evaluated_ref2 = evaluate_forward_ref(typing.ForwardRef("""Y["Y['Tx']"]"""), locals={"Y": Y, "Tx": Tx}) - self.assertEqual(get_origin(evaluated_ref2), Y) - self.assertEqual(get_args(evaluated_ref2), (Y[Tx],)) - - with self.subTest("nested string of TypeAliasType and alias"): - # NOTE: Using Y here works for 3.10 - evaluated_ref3 = evaluate_forward_ref(typing.ForwardRef("""Y['Z["StrAlias"]']"""), locals={"Y": Y, "Z": Z, "StrAlias": str}) - self.assertEqual(get_origin(evaluated_ref3), Y) - if sys.version_info[:2] == (3, 10): - self.skipTest("Nested string 'StrAlias' is not resolved in 3.10") - self.assertEqual(get_args(evaluated_ref3), (Z[str],)) - - def test_invalid_special_forms(self): - for name in ("Protocol", "Final", "ClassVar", "Generic"): - with self.subTest(name=name): - self.assertIs( - evaluate_forward_ref(typing.ForwardRef(name), globals=vars(typing)), - getattr(typing, name), - ) - if _FORWARD_REF_HAS_CLASS: - self.assertIs(evaluate_forward_ref(typing.ForwardRef("Final", is_class=True), globals=vars(typing)), Final) - self.assertIs(evaluate_forward_ref(typing.ForwardRef("ClassVar", is_class=True), globals=vars(typing)), ClassVar) - self.assertIs(evaluate_forward_ref(typing.ForwardRef("Final", is_argument=False), globals=vars(typing)), Final) - self.assertIs(evaluate_forward_ref(typing.ForwardRef("ClassVar", is_argument=False), globals=vars(typing)), ClassVar) +class MyClass: + def __repr__(self): + return "my repr" -class TestSentinels(BaseTestCase): - def test_sentinel_no_repr(self): - sentinel_no_repr = Sentinel('sentinel_no_repr') - self.assertEqual(sentinel_no_repr._name, 'sentinel_no_repr') - self.assertEqual(repr(sentinel_no_repr), '') +class TestTypeRepr(BaseTestCase): + def test_custom_types(self): - def test_sentinel_explicit_repr(self): - sentinel_explicit_repr = Sentinel('sentinel_explicit_repr', repr='explicit_repr') + class Nested: + pass - self.assertEqual(repr(sentinel_explicit_repr), 'explicit_repr') + def nested(): + pass - @skipIf(sys.version_info < (3, 10), reason='New unions not available in 3.9') - def test_sentinel_type_expression_union(self): - sentinel = Sentinel('sentinel') + self.assertEqual(type_repr(MyClass), f"{__name__}.MyClass") + self.assertEqual( + type_repr(Nested), + f"{__name__}.TestTypeRepr.test_custom_types..Nested", + ) + self.assertEqual( + type_repr(nested), + f"{__name__}.TestTypeRepr.test_custom_types..nested", + ) + self.assertEqual(type_repr(times_three), f"{__name__}.times_three") + self.assertEqual(type_repr(Format.VALUE), repr(Format.VALUE)) + self.assertEqual(type_repr(MyClass()), "my repr") - def func1(a: int | sentinel = sentinel): pass - def func2(a: sentinel | int = sentinel): pass + def test_builtin_types(self): + self.assertEqual(type_repr(int), "int") + self.assertEqual(type_repr(object), "object") + self.assertEqual(type_repr(None), "None") + self.assertEqual(type_repr(len), "len") + self.assertEqual(type_repr(1), "1") + self.assertEqual(type_repr("1"), "'1'") + self.assertEqual(type_repr(''), "''") + self.assertEqual(type_repr(...), "...") - self.assertEqual(func1.__annotations__['a'], Union[int, sentinel]) - self.assertEqual(func2.__annotations__['a'], Union[sentinel, int]) - def test_sentinel_not_callable(self): - sentinel = Sentinel('sentinel') - with self.assertRaisesRegex( - TypeError, - "'Sentinel' object is not callable" - ): - sentinel() +def times_three(fn): + @functools.wraps(fn) + def wrapper(a, b): + return fn(a * 3, b * 3) - def test_sentinel_not_picklable(self): - sentinel = Sentinel('sentinel') - with self.assertRaisesRegex( - TypeError, - "Cannot pickle 'Sentinel' object" - ): - pickle.dumps(sentinel) + return wrapper if __name__ == '__main__': From 5d1939fcefdab4ffe73fd7963095d9ef88ba7486 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 22 Aug 2025 15:37:25 +0100 Subject: [PATCH 2/3] delete moaaaaaaar --- src/test_typing_extensions.py | 937 ---------------------------------- 1 file changed, 937 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 2e897880..d97537ed 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -5691,948 +5691,11 @@ def test_cannot_combine_explicit_and_infer(self): TypeVar('T', contravariant=True, infer_variance=True) -class TypeVarLikeDefaultsTests(BaseTestCase): - def test_typevar(self): - T = typing_extensions.TypeVar('T', default=int) - typing_T = typing.TypeVar('T') - self.assertEqual(T.__default__, int) - self.assertIsInstance(T, typing_extensions.TypeVar) - self.assertIsInstance(T, typing.TypeVar) - self.assertIsInstance(typing_T, typing.TypeVar) - self.assertIsInstance(typing_T, typing_extensions.TypeVar) - - class A(Generic[T]): ... - self.assertEqual(Optional[T].__args__, (T, type(None))) - - def test_typevar_none(self): - U = typing_extensions.TypeVar('U') - U_None = typing_extensions.TypeVar('U_None', default=None) - self.assertIs(U.__default__, NoDefault) - self.assertFalse(U.has_default()) - self.assertEqual(U_None.__default__, None) - self.assertTrue(U_None.has_default()) - - def test_paramspec(self): - P = ParamSpec('P', default=[str, int]) - self.assertEqual(P.__default__, [str, int]) - self.assertTrue(P.has_default()) - self.assertIsInstance(P, ParamSpec) - if hasattr(typing, "ParamSpec"): - self.assertIsInstance(P, typing.ParamSpec) - typing_P = typing.ParamSpec('P') - self.assertIsInstance(typing_P, typing.ParamSpec) - self.assertIsInstance(typing_P, ParamSpec) - - class A(Generic[P]): ... - self.assertEqual(typing.Callable[P, None].__args__, (P, type(None))) - - P_default = ParamSpec('P_default', default=...) - self.assertIs(P_default.__default__, ...) - self.assertTrue(P_default.has_default()) - - def test_paramspec_none(self): - U = ParamSpec('U') - U_None = ParamSpec('U_None', default=None) - self.assertIs(U.__default__, NoDefault) - self.assertFalse(U.has_default()) - self.assertIs(U_None.__default__, None) - self.assertTrue(U_None.has_default()) - - def test_typevartuple(self): - Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]]) - self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]]) - self.assertIsInstance(Ts, TypeVarTuple) - self.assertTrue(Ts.has_default()) - if hasattr(typing, "TypeVarTuple"): - self.assertIsInstance(Ts, typing.TypeVarTuple) - typing_Ts = typing.TypeVarTuple('Ts') - self.assertIsInstance(typing_Ts, typing.TypeVarTuple) - self.assertIsInstance(typing_Ts, TypeVarTuple) - - class A(Generic[Unpack[Ts]]): ... - self.assertEqual(Optional[Unpack[Ts]].__args__, (Unpack[Ts], type(None))) - - @skipIf( - sys.version_info < (3, 11, 1), - "Not yet backported for older versions of Python" - ) - def test_typevartuple_specialization(self): - T = TypeVar("T") - Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]]) - self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]]) - class A(Generic[T, Unpack[Ts]]): ... - self.assertEqual(A[float].__args__, (float, str, int)) - self.assertEqual(A[float, range].__args__, (float, range)) - self.assertEqual(A[float, Unpack[tuple[int, ...]]].__args__, (float, Unpack[tuple[int, ...]])) - - @skipIf( - sys.version_info < (3, 11, 1), - "Not yet backported for older versions of Python" - ) - def test_typevar_and_typevartuple_specialization(self): - T = TypeVar("T") - U = TypeVar("U", default=float) - Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]]) - self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]]) - class A(Generic[T, U, Unpack[Ts]]): ... - self.assertEqual(A[int].__args__, (int, float, str, int)) - self.assertEqual(A[int, str].__args__, (int, str, str, int)) - self.assertEqual(A[int, str, range].__args__, (int, str, range)) - self.assertEqual(A[int, str, Unpack[tuple[int, ...]]].__args__, (int, str, Unpack[tuple[int, ...]])) - - def test_no_default_after_typevar_tuple(self): - T = TypeVar("T", default=int) - Ts = TypeVarTuple("Ts") - Ts_default = TypeVarTuple("Ts_default", default=Unpack[Tuple[str, int]]) - - with self.assertRaises(TypeError): - class X(Generic[Unpack[Ts], T]): ... - - with self.assertRaises(TypeError): - class Y(Generic[Unpack[Ts_default], T]): ... - - def test_typevartuple_none(self): - U = TypeVarTuple('U') - U_None = TypeVarTuple('U_None', default=None) - self.assertIs(U.__default__, NoDefault) - self.assertFalse(U.has_default()) - self.assertIs(U_None.__default__, None) - self.assertTrue(U_None.has_default()) - - def test_no_default_after_non_default(self): - DefaultStrT = typing_extensions.TypeVar('DefaultStrT', default=str) - T = TypeVar('T') - - with self.assertRaises(TypeError): - Generic[DefaultStrT, T] - - def test_need_more_params(self): - DefaultStrT = typing_extensions.TypeVar('DefaultStrT', default=str) - T = typing_extensions.TypeVar('T') - U = typing_extensions.TypeVar('U') - - class A(Generic[T, U, DefaultStrT]): ... - A[int, bool] - A[int, bool, str] - - with self.assertRaises( - TypeError, msg="Too few arguments for .+; actual 1, expected at least 2" - ): - A[int] - - def test_pickle(self): - global U, U_co, U_contra, U_default # pickle wants to reference the class by name - U = typing_extensions.TypeVar('U') - U_co = typing_extensions.TypeVar('U_co', covariant=True) - U_contra = typing_extensions.TypeVar('U_contra', contravariant=True) - U_default = typing_extensions.TypeVar('U_default', default=int) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - for typevar in (U, U_co, U_contra, U_default): - z = pickle.loads(pickle.dumps(typevar, proto)) - self.assertEqual(z.__name__, typevar.__name__) - self.assertEqual(z.__covariant__, typevar.__covariant__) - self.assertEqual(z.__contravariant__, typevar.__contravariant__) - self.assertEqual(z.__bound__, typevar.__bound__) - self.assertEqual(z.__default__, typevar.__default__) - - def test_strange_defaults_are_allowed(self): - # Leave it to type checkers to check whether strange default values - # should be allowed or disallowed - def not_a_type(): ... - - for typevarlike_cls in TypeVar, ParamSpec, TypeVarTuple: - for default in not_a_type, 42, bytearray(), (int, not_a_type, 42): - with self.subTest(typevarlike_cls=typevarlike_cls, default=default): - T = typevarlike_cls("T", default=default) - self.assertEqual(T.__default__, default) - - @skip_if_py313_beta_1 - def test_allow_default_after_non_default_in_alias(self): - T_default = TypeVar('T_default', default=int) - T = TypeVar('T') - Ts = TypeVarTuple('Ts') - - a1 = Callable[[T_default], T] - self.assertEqual(a1.__args__, (T_default, T)) - - a2 = dict[T_default, T] - self.assertEqual(a2.__args__, (T_default, T)) - - a3 = typing.Dict[T_default, T] - self.assertEqual(a3.__args__, (T_default, T)) - - a4 = Callable[[Unpack[Ts]], T] - self.assertEqual(a4.__args__, (Unpack[Ts], T)) - - @skipIf( - typing_extensions.Protocol is typing.Protocol, - "Test currently fails with the CPython version of Protocol and that's not our fault" - ) - def test_generic_with_broken_eq(self): - # See https://github.com/python/typing_extensions/pull/422 for context - class BrokenEq(type): - def __eq__(self, other): - if other is typing_extensions.Protocol: - raise TypeError("I'm broken") - return False - - class G(Generic[T], metaclass=BrokenEq): - pass - - alias = G[int] - self.assertIs(get_origin(alias), G) - self.assertEqual(get_args(alias), (int,)) - - @skipIf( - sys.version_info < (3, 11, 1), - "Not yet backported for older versions of Python" - ) - def test_paramspec_specialization(self): - T = TypeVar("T") - P = ParamSpec('P', default=[str, int]) - self.assertEqual(P.__default__, [str, int]) - class A(Generic[T, P]): ... - self.assertEqual(A[float].__args__, (float, (str, int))) - self.assertEqual(A[float, [range]].__args__, (float, (range,))) - - @skipIf( - sys.version_info < (3, 11, 1), - "Not yet backported for older versions of Python" - ) - def test_typevar_and_paramspec_specialization(self): - T = TypeVar("T") - U = TypeVar("U", default=float) - P = ParamSpec('P', default=[str, int]) - self.assertEqual(P.__default__, [str, int]) - class A(Generic[T, U, P]): ... - self.assertEqual(A[float].__args__, (float, float, (str, int))) - self.assertEqual(A[float, int].__args__, (float, int, (str, int))) - self.assertEqual(A[float, int, [range]].__args__, (float, int, (range,))) - - @skipIf( - sys.version_info < (3, 11, 1), - "Not yet backported for older versions of Python" - ) - def test_paramspec_and_typevar_specialization(self): - T = TypeVar("T") - P = ParamSpec('P', default=[str, int]) - U = TypeVar("U", default=float) - self.assertEqual(P.__default__, [str, int]) - class A(Generic[T, P, U]): ... - self.assertEqual(A[float].__args__, (float, (str, int), float)) - self.assertEqual(A[float, [range]].__args__, (float, (range,), float)) - self.assertEqual(A[float, [range], int].__args__, (float, (range,), int)) - - -class NoDefaultTests(BaseTestCase): - @skip_if_py313_beta_1 - def test_pickling(self): - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - s = pickle.dumps(NoDefault, proto) - loaded = pickle.loads(s) - self.assertIs(NoDefault, loaded) - - @skip_if_py313_beta_1 - def test_doc(self): - self.assertIsInstance(NoDefault.__doc__, str) - - def test_constructor(self): - self.assertIs(NoDefault, type(NoDefault)()) - with self.assertRaises(TypeError): - type(NoDefault)(1) - - def test_repr(self): - self.assertRegex(repr(NoDefault), r'typing(_extensions)?\.NoDefault') - - def test_no_call(self): - with self.assertRaises(TypeError): - NoDefault() - - @skip_if_py313_beta_1 - def test_immutable(self): - with self.assertRaises(AttributeError): - NoDefault.foo = 'bar' - with self.assertRaises(AttributeError): - NoDefault.foo - - # TypeError is consistent with the behavior of NoneType - with self.assertRaises(TypeError): - type(NoDefault).foo = 3 - with self.assertRaises(AttributeError): - type(NoDefault).foo - - -class TypeVarInferVarianceTests(BaseTestCase): - def test_typevar(self): - T = typing_extensions.TypeVar('T') - self.assertFalse(T.__infer_variance__) - T_infer = typing_extensions.TypeVar('T_infer', infer_variance=True) - self.assertTrue(T_infer.__infer_variance__) - T_noinfer = typing_extensions.TypeVar('T_noinfer', infer_variance=False) - self.assertFalse(T_noinfer.__infer_variance__) - - def test_pickle(self): - global U, U_infer # pickle wants to reference the class by name - U = typing_extensions.TypeVar('U') - U_infer = typing_extensions.TypeVar('U_infer', infer_variance=True) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - for typevar in (U, U_infer): - z = pickle.loads(pickle.dumps(typevar, proto)) - self.assertEqual(z.__name__, typevar.__name__) - self.assertEqual(z.__covariant__, typevar.__covariant__) - self.assertEqual(z.__contravariant__, typevar.__contravariant__) - self.assertEqual(z.__bound__, typevar.__bound__) - self.assertEqual(z.__infer_variance__, typevar.__infer_variance__) - - -class BufferTests(BaseTestCase): - def test(self): - self.assertIsInstance(memoryview(b''), Buffer) - self.assertIsInstance(bytearray(), Buffer) - self.assertIsInstance(b"x", Buffer) - self.assertNotIsInstance(1, Buffer) - - self.assertIsSubclass(bytearray, Buffer) - self.assertIsSubclass(memoryview, Buffer) - self.assertIsSubclass(bytes, Buffer) - self.assertNotIsSubclass(int, Buffer) - - class MyRegisteredBuffer: - def __buffer__(self, flags: int) -> memoryview: - return memoryview(b'') - - # On 3.12, collections.abc.Buffer does a structural compatibility check - if TYPING_3_12_0: - self.assertIsInstance(MyRegisteredBuffer(), Buffer) - self.assertIsSubclass(MyRegisteredBuffer, Buffer) - else: - self.assertNotIsInstance(MyRegisteredBuffer(), Buffer) - self.assertNotIsSubclass(MyRegisteredBuffer, Buffer) - Buffer.register(MyRegisteredBuffer) - self.assertIsInstance(MyRegisteredBuffer(), Buffer) - self.assertIsSubclass(MyRegisteredBuffer, Buffer) - - class MySubclassedBuffer(Buffer): - def __buffer__(self, flags: int) -> memoryview: - return memoryview(b'') - - self.assertIsInstance(MySubclassedBuffer(), Buffer) - self.assertIsSubclass(MySubclassedBuffer, Buffer) - - -class GetOriginalBasesTests(BaseTestCase): - def test_basics(self): - T = TypeVar('T') - class A: pass - class B(Generic[T]): pass - class C(B[int]): pass - class D(B[str], float): pass - self.assertEqual(get_original_bases(A), (object,)) - self.assertEqual(get_original_bases(B), (Generic[T],)) - self.assertEqual(get_original_bases(C), (B[int],)) - self.assertEqual(get_original_bases(int), (object,)) - self.assertEqual(get_original_bases(D), (B[str], float)) - - with self.assertRaisesRegex(TypeError, "Expected an instance of type"): - get_original_bases(object()) - - def test_builtin_generics(self): - class E(list[T]): pass - class F(list[int]): pass - - self.assertEqual(get_original_bases(E), (list[T],)) - self.assertEqual(get_original_bases(F), (list[int],)) - - @skipIf( - sys.version_info[:3] == (3, 12, 0) and sys.version_info[3] in {"alpha", "beta"}, - "Early versions of py312 had a bug" - ) - def test_concrete_subclasses_of_generic_classes(self): - T = TypeVar("T") - - class FirstBase(Generic[T]): pass - class SecondBase(Generic[T]): pass - class First(FirstBase[int]): pass - class Second(SecondBase[int]): pass - class G(First, Second): pass - self.assertEqual(get_original_bases(G), (First, Second)) - - class First_(Generic[T]): pass - class Second_(Generic[T]): pass - class H(First_, Second_): pass - self.assertEqual(get_original_bases(H), (First_, Second_)) - - def test_namedtuples(self): - # On 3.12, this should work well with typing.NamedTuple and typing_extensions.NamedTuple - # On lower versions, it will only work fully with typing_extensions.NamedTuple - if sys.version_info >= (3, 12): - namedtuple_classes = (typing.NamedTuple, typing_extensions.NamedTuple) - else: - namedtuple_classes = (typing_extensions.NamedTuple,) - - for NamedTuple in namedtuple_classes: # noqa: F402 - with self.subTest(cls=NamedTuple): - class ClassBasedNamedTuple(NamedTuple): - x: int - - class GenericNamedTuple(NamedTuple, Generic[T]): - x: T - - CallBasedNamedTuple = NamedTuple("CallBasedNamedTuple", [("x", int)]) - - self.assertIs( - get_original_bases(ClassBasedNamedTuple)[0], NamedTuple - ) - self.assertEqual( - get_original_bases(GenericNamedTuple), - (NamedTuple, Generic[T]) - ) - self.assertIs( - get_original_bases(CallBasedNamedTuple)[0], NamedTuple - ) - - def test_typeddicts(self): - # On 3.12, this should work well with typing.TypedDict and typing_extensions.TypedDict - # On lower versions, it will only work fully with typing_extensions.TypedDict - if sys.version_info >= (3, 12): - typeddict_classes = (typing.TypedDict, typing_extensions.TypedDict) - else: - typeddict_classes = (typing_extensions.TypedDict,) - - for TypedDict in typeddict_classes: # noqa: F402 - with self.subTest(cls=TypedDict): - class ClassBasedTypedDict(TypedDict): - x: int - - class GenericTypedDict(TypedDict, Generic[T]): - x: T - - CallBasedTypedDict = TypedDict("CallBasedTypedDict", {"x": int}) - - self.assertIs( - get_original_bases(ClassBasedTypedDict)[0], - TypedDict - ) - self.assertEqual( - get_original_bases(GenericTypedDict), - (TypedDict, Generic[T]) - ) - self.assertIs( - get_original_bases(CallBasedTypedDict)[0], - TypedDict - ) - - -class TypeAliasTypeTests(BaseTestCase): - def test_attributes(self): - Simple = TypeAliasType("Simple", int) - self.assertEqual(Simple.__name__, "Simple") - self.assertIs(Simple.__value__, int) - self.assertEqual(Simple.__type_params__, ()) - self.assertEqual(Simple.__parameters__, ()) - - T = TypeVar("T") - ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) - self.assertEqual(ListOrSetT.__name__, "ListOrSetT") - self.assertEqual(ListOrSetT.__value__, Union[List[T], Set[T]]) - self.assertEqual(ListOrSetT.__type_params__, (T,)) - self.assertEqual(ListOrSetT.__parameters__, (T,)) - - Ts = TypeVarTuple("Ts") - Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) - self.assertEqual(Variadic.__name__, "Variadic") - self.assertEqual(Variadic.__value__, Tuple[int, Unpack[Ts]]) - self.assertEqual(Variadic.__type_params__, (Ts,)) - self.assertEqual(Variadic.__parameters__, tuple(iter(Ts))) - - P = ParamSpec('P') - CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P, )) - self.assertEqual(CallableP.__name__, "CallableP") - self.assertEqual(CallableP.__value__, Callable[P, Any]) - self.assertEqual(CallableP.__type_params__, (P,)) - self.assertEqual(CallableP.__parameters__, (P,)) - - def test_alias_types_and_substitutions(self): - T = TypeVar('T') - T2 = TypeVar('T2') - T_default = TypeVar("T_default", default=int) - Ts = TypeVarTuple("Ts") - P = ParamSpec('P') - - test_argument_cases = { - # arguments : expected parameters - int : (), - ... : (), - None : (), - T2 : (T2,), - Union[int, List[T2]] : (T2,), - Tuple[int, str] : (), - Tuple[T, T_default, T2] : (T, T_default, T2), - Tuple[Unpack[Ts]] : (Ts,), - Callable[[Unpack[Ts]], T2] : (Ts, T2), - Callable[P, T2] : (P, T2), - Callable[Concatenate[T2, P], T_default] : (T2, P, T_default), - TypeAliasType("NestedAlias", List[T], type_params=(T,))[T2] : (T2,), - Unpack[Ts] : (Ts,), - Unpack[Tuple[int, T2]] : (T2,), - Concatenate[int, P] : (P,), - # Not tested usage of bare TypeVarTuple, would need 3.11+ - # Ts : (Ts,), # invalid case - } - - test_alias_cases = [ - # Simple cases - TypeAliasType("ListT", List[T], type_params=(T,)), - TypeAliasType("UnionT", Union[int, List[T]], type_params=(T,)), - # Value has no parameter but in type_param - TypeAliasType("ValueWithoutT", int, type_params=(T,)), - # Callable - TypeAliasType("CallableP", Callable[P, Any], type_params=(P, )), - TypeAliasType("CallableT", Callable[..., T], type_params=(T, )), - TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, )), - # TypeVarTuple - TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)), - # TypeVar with default - TypeAliasType("TupleT_default", Tuple[T_default, T], type_params=(T, T_default)), - TypeAliasType("CallableT_default", Callable[[T], T_default], type_params=(T, T_default)), - ] - - for alias in test_alias_cases: - with self.subTest(alias=alias, args=[]): - subscripted = alias[[]] - self.assertEqual(get_args(subscripted), ([],)) - self.assertEqual(subscripted.__parameters__, ()) - with self.subTest(alias=alias, args=()): - subscripted = alias[()] - self.assertEqual(get_args(subscripted), ()) - self.assertEqual(subscripted.__parameters__, ()) - with self.subTest(alias=alias, args=(int, float)): - subscripted = alias[int, float] - self.assertEqual(get_args(subscripted), (int, float)) - self.assertEqual(subscripted.__parameters__, ()) - with self.subTest(alias=alias, args=[int, float]): - subscripted = alias[[int, float]] - self.assertEqual(get_args(subscripted), ([int, float],)) - self.assertEqual(subscripted.__parameters__, ()) - for expected_args, expected_parameters in test_argument_cases.items(): - with self.subTest(alias=alias, args=expected_args): - self.assertEqual(get_args(alias[expected_args]), (expected_args,)) - self.assertEqual(alias[expected_args].__parameters__, expected_parameters) - - def test_cannot_set_attributes(self): - Simple = TypeAliasType("Simple", int) - with self.assertRaisesRegex(AttributeError, "readonly attribute"): - Simple.__name__ = "NewName" - with self.assertRaisesRegex( - AttributeError, - "attribute '__value__' of 'typing.TypeAliasType' objects is not writable", - ): - Simple.__value__ = str - with self.assertRaisesRegex( - AttributeError, - "attribute '__type_params__' of 'typing.TypeAliasType' objects is not writable", - ): - Simple.__type_params__ = (T,) - with self.assertRaisesRegex( - AttributeError, - "attribute '__parameters__' of 'typing.TypeAliasType' objects is not writable", - ): - Simple.__parameters__ = (T,) - with self.assertRaisesRegex( - AttributeError, - "attribute '__module__' of 'typing.TypeAliasType' objects is not writable", - ): - Simple.__module__ = 42 - with self.assertRaisesRegex( - AttributeError, - "'typing.TypeAliasType' object has no attribute 'some_attribute'", - ): - Simple.some_attribute = "not allowed" - - def test_cannot_delete_attributes(self): - Simple = TypeAliasType("Simple", int) - with self.assertRaisesRegex(AttributeError, "readonly attribute"): - del Simple.__name__ - with self.assertRaisesRegex( - AttributeError, - "attribute '__value__' of 'typing.TypeAliasType' objects is not writable", - ): - del Simple.__value__ - with self.assertRaisesRegex( - AttributeError, - "'typing.TypeAliasType' object has no attribute 'some_attribute'", - ): - del Simple.some_attribute - - def test_or(self): - Alias = TypeAliasType("Alias", int) - if sys.version_info >= (3, 10): - self.assertEqual(Alias | int, Union[Alias, int]) - self.assertEqual(Alias | None, Union[Alias, None]) - self.assertEqual(Alias | (int | str), Union[Alias, int | str]) - self.assertEqual(Alias | list[float], Union[Alias, list[float]]) - - if sys.version_info >= (3, 12): - Alias2 = typing.TypeAliasType("Alias2", str) - self.assertEqual(Alias | Alias2, Union[Alias, Alias2]) - else: - with self.assertRaises(TypeError): - Alias | int - # Rejected on all versions - with self.assertRaises(TypeError): - Alias | "Ref" - - def test_getitem(self): - T = TypeVar('T') - ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) - subscripted = ListOrSetT[int] - self.assertEqual(get_args(subscripted), (int,)) - self.assertIs(get_origin(subscripted), ListOrSetT) - with self.assertRaisesRegex(TypeError, - "not a generic class" - # types.GenericAlias raises a different error in 3.10 - if sys.version_info[:2] != (3, 10) - else "There are no type variables left in ListOrSetT" - ): - subscripted[int] - - - still_generic = ListOrSetT[Iterable[T]] - self.assertEqual(get_args(still_generic), (Iterable[T],)) - self.assertIs(get_origin(still_generic), ListOrSetT) - fully_subscripted = still_generic[float] - self.assertEqual(get_args(fully_subscripted), (Iterable[float],)) - self.assertIs(get_origin(fully_subscripted), ListOrSetT) - - ValueWithoutTypeVar = TypeAliasType("ValueWithoutTypeVar", int, type_params=(T,)) - still_subscripted = ValueWithoutTypeVar[str] - self.assertEqual(get_args(still_subscripted), (str,)) - - def test_callable_without_concatenate(self): - P = ParamSpec('P') - CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) - get_args_test_cases = [ - # List of (alias, expected_args) - # () -> Any - (CallableP[()], ()), - (CallableP[[]], ([],)), - # (int) -> Any - (CallableP[int], (int,)), - (CallableP[[int]], ([int],)), - # (int, int) -> Any - (CallableP[int, int], (int, int)), - (CallableP[[int, int]], ([int, int],)), - # (...) -> Any - (CallableP[...], (...,)), - # (int, ...) -> Any - (CallableP[[int, ...]], ([int, ...],)), - ] - - for index, (expression, expected_args) in enumerate(get_args_test_cases): - with self.subTest(index=index, expression=expression): - self.assertEqual(get_args(expression), expected_args) - - self.assertEqual(CallableP[...], CallableP[(...,)]) - # (T) -> Any - CallableT = CallableP[T] - self.assertEqual(get_args(CallableT), (T,)) - self.assertEqual(CallableT.__parameters__, (T,)) - - def test_callable_with_concatenate(self): - P = ParamSpec('P') - P2 = ParamSpec('P2') - CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) - - callable_concat = CallableP[Concatenate[int, P2]] - self.assertEqual(callable_concat.__parameters__, (P2,)) - concat_usage = callable_concat[str] - with self.subTest("get_args of Concatenate in TypeAliasType"): - if not TYPING_3_10_0: - # args are: ([, ~P2],) - self.skipTest("Nested ParamSpec is not substituted") - self.assertEqual(get_args(concat_usage), ((int, str),)) - with self.subTest("Equality of parameter_expression without []"): - if not TYPING_3_10_0: - self.skipTest("Nested list is invalid type form") - self.assertEqual(concat_usage, callable_concat[[str]]) - - def test_substitution(self): - T = TypeVar('T') - Ts = TypeVarTuple("Ts") - - CallableTs = TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, )) - unpack_callable = CallableTs[Unpack[Tuple[int, T]]] - self.assertEqual(get_args(unpack_callable), (Unpack[Tuple[int, T]],)) - - P = ParamSpec('P') - CallableP = TypeAliasType("CallableP", Callable[P, T], type_params=(P, T)) - callable_concat = CallableP[Concatenate[int, P], Any] - self.assertEqual(get_args(callable_concat), (Concatenate[int, P], Any)) - - def test_wrong_amount_of_parameters(self): - T = TypeVar('T') - T2 = TypeVar("T2") - P = ParamSpec('P') - ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) - TwoT = TypeAliasType("TwoT", Union[List[T], Set[T2]], type_params=(T, T2)) - CallablePT = TypeAliasType("CallablePT", Callable[P, T], type_params=(P, T)) - - # Not enough parameters - test_cases = [ - # not_enough - (TwoT[int], [(int,), ()]), - (TwoT[T], [(T,), (T,)]), - # callable and not enough - (CallablePT[int], [(int,), ()]), - # too many - (ListOrSetT[int, bool], [(int, bool), ()]), - # callable and too many - (CallablePT[str, float, int], [(str, float, int), ()]), - # Check if TypeVar is still present even if over substituted - (ListOrSetT[int, T], [(int, T), (T,)]), - # With and without list for ParamSpec - (CallablePT[str, float, T], [(str, float, T), (T,)]), - (CallablePT[[str], float, int, T2], [([str], float, int, T2), (T2,)]), - ] - - for index, (alias, [expected_args, expected_params]) in enumerate(test_cases): - with self.subTest(index=index, alias=alias): - self.assertEqual(get_args(alias), expected_args) - self.assertEqual(alias.__parameters__, expected_params) - - # The condition should align with the version of GeneriAlias usage in __getitem__ or be 3.11+ - @skipIf(TYPING_3_10_0, "Most arguments are allowed in 3.11+ or with GenericAlias") - def test_invalid_cases_before_3_10(self): - T = TypeVar('T') - ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) - with self.assertRaises(TypeError): - ListOrSetT[Generic[T]] - with self.assertRaises(TypeError): - ListOrSetT[(Generic[T], )] - - def test_unpack_parameter_collection(self): - Ts = TypeVarTuple("Ts") - - class Foo(Generic[Unpack[Ts]]): - bar: Tuple[Unpack[Ts]] - - FooAlias = TypeAliasType("FooAlias", Foo[Unpack[Ts]], type_params=(Ts,)) - self.assertEqual(FooAlias[Unpack[Tuple[str]]].__parameters__, ()) - self.assertEqual(FooAlias[Unpack[Tuple[T]]].__parameters__, (T,)) - - P = ParamSpec("P") - CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) - call_int_T = CallableP[Unpack[Tuple[int, T]]] - self.assertEqual(call_int_T.__parameters__, (T,)) - - def test_alias_attributes(self): - T = TypeVar('T') - T2 = TypeVar('T2') - ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) - - subscripted = ListOrSetT[int] - self.assertEqual(subscripted.__module__, ListOrSetT.__module__) - self.assertEqual(subscripted.__name__, "ListOrSetT") - self.assertEqual(subscripted.__value__, Union[List[T], Set[T]]) - self.assertEqual(subscripted.__type_params__, (T,)) - - still_generic = ListOrSetT[Iterable[T2]] - self.assertEqual(still_generic.__module__, ListOrSetT.__module__) - self.assertEqual(still_generic.__name__, "ListOrSetT") - self.assertEqual(still_generic.__value__, Union[List[T], Set[T]]) - self.assertEqual(still_generic.__type_params__, (T,)) - - fully_subscripted = still_generic[float] - self.assertEqual(fully_subscripted.__module__, ListOrSetT.__module__) - self.assertEqual(fully_subscripted.__name__, "ListOrSetT") - self.assertEqual(fully_subscripted.__value__, Union[List[T], Set[T]]) - self.assertEqual(fully_subscripted.__type_params__, (T,)) - - def test_subscription_without_type_params(self): - Simple = TypeAliasType("Simple", int) - with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): - Simple[int] - - # A TypeVar in the value does not allow subscription - T = TypeVar('T') - MissingTypeParamsErr = TypeAliasType("MissingTypeParamsErr", List[T]) - self.assertEqual(MissingTypeParamsErr.__type_params__, ()) - self.assertEqual(MissingTypeParamsErr.__parameters__, ()) - with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): - MissingTypeParamsErr[int] - - def test_pickle(self): - global Alias - Alias = TypeAliasType("Alias", int) - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - with self.subTest(proto=proto): - pickled = pickle.dumps(Alias, proto) - unpickled = pickle.loads(pickled) - self.assertIs(unpickled, Alias) - - def test_no_instance_subclassing(self): - with self.assertRaises(TypeError): - class MyAlias(TypeAliasType): - pass - - def test_type_var_compatibility(self): - # Regression test to assure compatibility with typing variants - typingT = typing.TypeVar('typingT') - T1 = TypeAliasType("TypingTypeVar", ..., type_params=(typingT,)) - self.assertEqual(T1.__type_params__, (typingT,)) - - # Test typing_extensions backports - textT = TypeVar('textT') - T2 = TypeAliasType("TypingExtTypeVar", ..., type_params=(textT,)) - self.assertEqual(T2.__type_params__, (textT,)) - - textP = ParamSpec("textP") - T3 = TypeAliasType("TypingExtParamSpec", ..., type_params=(textP,)) - self.assertEqual(T3.__type_params__, (textP,)) - - textTs = TypeVarTuple("textTs") - T4 = TypeAliasType("TypingExtTypeVarTuple", ..., type_params=(textTs,)) - self.assertEqual(T4.__type_params__, (textTs,)) - - @skipUnless(TYPING_3_10_0, "typing.ParamSpec is not available before 3.10") - def test_param_spec_compatibility(self): - # Regression test to assure compatibility with typing variant - typingP = typing.ParamSpec("typingP") - T5 = TypeAliasType("TypingParamSpec", ..., type_params=(typingP,)) - self.assertEqual(T5.__type_params__, (typingP,)) - - @skipUnless(TYPING_3_12_0, "typing.TypeVarTuple is not available before 3.12") - def test_type_var_tuple_compatibility(self): - # Regression test to assure compatibility with typing variant - typingTs = typing.TypeVarTuple("typingTs") - T6 = TypeAliasType("TypingTypeVarTuple", ..., type_params=(typingTs,)) - self.assertEqual(T6.__type_params__, (typingTs,)) - - def test_type_params_possibilities(self): - T = TypeVar('T') - # Test not a tuple - with self.assertRaisesRegex(TypeError, "type_params must be a tuple"): - TypeAliasType("InvalidTypeParams", List[T], type_params=[T]) - - # Test default order and other invalid inputs - T_default = TypeVar('T_default', default=int) - Ts = TypeVarTuple('Ts') - Ts_default = TypeVarTuple('Ts_default', default=Unpack[Tuple[str, int]]) - P = ParamSpec('P') - P_default = ParamSpec('P_default', default=[str, int]) - - # NOTE: PEP 696 states: "TypeVars with defaults cannot immediately follow TypeVarTuples" - # this is currently not enforced for the type statement and is not tested. - # PEP 695: Double usage of the same name is also not enforced and not tested. - valid_cases = [ - (T, P, Ts), - (T, Ts_default), - (P_default, T_default), - (P, T_default, Ts_default), - (T_default, P_default, Ts_default), - ] - invalid_cases = [ - ((T_default, T), f"non-default type parameter '{T!r}' follows default"), - ((P_default, P), f"non-default type parameter '{P!r}' follows default"), - ((Ts_default, T), f"non-default type parameter '{T!r}' follows default"), - # Only type params are accepted - ((1,), "Expected a type param, got 1"), - ((str,), f"Expected a type param, got {str!r}"), - # Unpack is not a TypeVar but isinstance(Unpack[Ts], TypeVar) is True in Python < 3.12 - ((Unpack[Ts],), f"Expected a type param, got {re.escape(repr(Unpack[Ts]))}"), - ] - - for case in valid_cases: - with self.subTest(type_params=case): - TypeAliasType("OkCase", List[T], type_params=case) - for case, msg in invalid_cases: - with self.subTest(type_params=case): - with self.assertRaisesRegex(TypeError, msg): - TypeAliasType("InvalidCase", List[T], type_params=case) - -class DocTests(BaseTestCase): - def test_annotation(self): - - def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: pass - - hints = get_type_hints(hi, include_extras=True) - doc_info = hints["to"].__metadata__[0] - self.assertEqual(doc_info.documentation, "Who to say hi to") - self.assertIsInstance(doc_info, Doc) - - def test_repr(self): - doc_info = Doc("Who to say hi to") - self.assertEqual(repr(doc_info), "Doc('Who to say hi to')") - - def test_hashability(self): - doc_info = Doc("Who to say hi to") - self.assertIsInstance(hash(doc_info), int) - self.assertNotEqual(hash(doc_info), hash(Doc("Who not to say hi to"))) - - def test_equality(self): - doc_info = Doc("Who to say hi to") - # Equal to itself - self.assertEqual(doc_info, doc_info) - # Equal to another instance with the same string - self.assertEqual(doc_info, Doc("Who to say hi to")) - # Not equal to another instance with a different string - self.assertNotEqual(doc_info, Doc("Who not to say hi to")) - - def test_pickle(self): - doc_info = Doc("Who to say hi to") - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - pickled = pickle.dumps(doc_info, protocol=proto) - self.assertEqual(doc_info, pickle.loads(pickled)) - - -@skipUnless( - hasattr(typing_extensions, "CapsuleType"), - "CapsuleType is not available on all Python implementations" -) -class CapsuleTypeTests(BaseTestCase): - def test_capsule_type(self): - import _datetime - self.assertIsInstance(_datetime.datetime_CAPI, typing_extensions.CapsuleType) - - class MyClass: def __repr__(self): return "my repr" -class TestTypeRepr(BaseTestCase): - def test_custom_types(self): - - class Nested: - pass - - def nested(): - pass - - self.assertEqual(type_repr(MyClass), f"{__name__}.MyClass") - self.assertEqual( - type_repr(Nested), - f"{__name__}.TestTypeRepr.test_custom_types..Nested", - ) - self.assertEqual( - type_repr(nested), - f"{__name__}.TestTypeRepr.test_custom_types..nested", - ) - self.assertEqual(type_repr(times_three), f"{__name__}.times_three") - self.assertEqual(type_repr(Format.VALUE), repr(Format.VALUE)) - self.assertEqual(type_repr(MyClass()), "my repr") - - def test_builtin_types(self): - self.assertEqual(type_repr(int), "int") - self.assertEqual(type_repr(object), "object") - self.assertEqual(type_repr(None), "None") - self.assertEqual(type_repr(len), "len") - self.assertEqual(type_repr(1), "1") - self.assertEqual(type_repr("1"), "'1'") - self.assertEqual(type_repr(''), "''") - self.assertEqual(type_repr(...), "...") - - def times_three(fn): @functools.wraps(fn) def wrapper(a, b): From 0bbafab00654324a60555f5b7bf8ad0617c3a004 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 22 Aug 2025 15:42:38 +0100 Subject: [PATCH 3/3] moar --- src/test_typing_extensions.py | 626 ---------------------------------- 1 file changed, 626 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index d97537ed..413ebf53 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -4490,632 +4490,6 @@ def test_no_isinstance(self): issubclass(int, TypeForm) -class LiteralStringTests(BaseTestCase): - def test_basics(self): - class Foo: - def bar(self) -> LiteralString: ... - def baz(self) -> "LiteralString": ... - - self.assertEqual(gth(Foo.bar), {'return': LiteralString}) - self.assertEqual(gth(Foo.baz), {'return': LiteralString}) - - def test_get_origin(self): - self.assertIsNone(get_origin(LiteralString)) - - def test_repr(self): - if hasattr(typing, 'LiteralString'): - mod_name = 'typing' - else: - mod_name = 'typing_extensions' - self.assertEqual(repr(LiteralString), f'{mod_name}.LiteralString') - - def test_cannot_subscript(self): - with self.assertRaises(TypeError): - LiteralString[int] - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(LiteralString)): - pass - with self.assertRaises(TypeError): - class D(LiteralString): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - LiteralString() - with self.assertRaises(TypeError): - type(LiteralString)() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, LiteralString) - with self.assertRaises(TypeError): - issubclass(int, LiteralString) - - def test_alias(self): - StringTuple = Tuple[LiteralString, LiteralString] - class Alias: - def return_tuple(self) -> StringTuple: - return ("foo", "pep" + "675") - - def test_typevar(self): - StrT = TypeVar("StrT", bound=LiteralString) - self.assertIs(StrT.__bound__, LiteralString) - - def test_pickle(self): - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - pickled = pickle.dumps(LiteralString, protocol=proto) - self.assertIs(LiteralString, pickle.loads(pickled)) - - -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), f'{mod_name}.Self') - - 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) - - def test_alias(self): - TupleSelf = Tuple[Self, Self] - class Alias: - def return_tuple(self) -> TupleSelf: - return (self, self) - - def test_pickle(self): - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - pickled = pickle.dumps(Self, protocol=proto) - self.assertIs(Self, pickle.loads(pickled)) - - -class UnpackTests(BaseTestCase): - def test_basic_plain(self): - Ts = TypeVarTuple('Ts') - self.assertEqual(Unpack[Ts], Unpack[Ts]) - with self.assertRaises(TypeError): - Unpack() - - def test_repr(self): - Ts = TypeVarTuple('Ts') - self.assertEqual(repr(Unpack[Ts]), f'{Unpack.__module__}.Unpack[Ts]') - - def test_cannot_subclass_vars(self): - with self.assertRaises(TypeError): - class V(Unpack[TypeVarTuple('Ts')]): - pass - - def test_tuple(self): - Ts = TypeVarTuple('Ts') - Tuple[Unpack[Ts]] - - def test_union(self): - Xs = TypeVarTuple('Xs') - Ys = TypeVarTuple('Ys') - self.assertEqual( - Union[Unpack[Xs]], - Unpack[Xs] - ) - self.assertNotEqual( - Union[Unpack[Xs]], - Union[Unpack[Xs], Unpack[Ys]] - ) - self.assertEqual( - Union[Unpack[Xs], Unpack[Xs]], - Unpack[Xs] - ) - self.assertNotEqual( - Union[Unpack[Xs], int], - Union[Unpack[Xs]] - ) - self.assertNotEqual( - Union[Unpack[Xs], int], - Union[int] - ) - self.assertEqual( - Union[Unpack[Xs], int].__args__, - (Unpack[Xs], int) - ) - self.assertEqual( - Union[Unpack[Xs], int].__parameters__, - (Xs,) - ) - self.assertIs( - Union[Unpack[Xs], int].__origin__, - Union - ) - - def test_concatenation(self): - Xs = TypeVarTuple('Xs') - self.assertEqual(Tuple[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) - self.assertEqual(Tuple[Unpack[Xs], int].__args__, (Unpack[Xs], int)) - self.assertEqual(Tuple[int, Unpack[Xs], str].__args__, - (int, Unpack[Xs], str)) - class C(Generic[Unpack[Xs]]): pass - class D(Protocol[Unpack[Xs]]): pass - for klass in C, D: - with self.subTest(klass=klass.__name__): - self.assertEqual(klass[int, Unpack[Xs]].__args__, (int, Unpack[Xs])) - self.assertEqual(klass[Unpack[Xs], int].__args__, (Unpack[Xs], int)) - self.assertEqual(klass[int, Unpack[Xs], str].__args__, - (int, Unpack[Xs], str)) - - def test_class(self): - Ts = TypeVarTuple('Ts') - - class C(Generic[Unpack[Ts]]): pass - class D(Protocol[Unpack[Ts]]): pass - - for klass in C, D: - with self.subTest(klass=klass.__name__): - self.assertEqual(klass[int].__args__, (int,)) - self.assertEqual(klass[int, str].__args__, (int, str)) - - with self.assertRaises(TypeError): - class C(Generic[Unpack[Ts], int]): pass - - with self.assertRaises(TypeError): - class D(Protocol[Unpack[Ts], int]): pass - - T1 = TypeVar('T') - T2 = TypeVar('T') - class C(Generic[T1, T2, Unpack[Ts]]): pass - class D(Protocol[T1, T2, Unpack[Ts]]): pass - for klass in C, D: - with self.subTest(klass=klass.__name__): - self.assertEqual(klass[int, str].__args__, (int, str)) - self.assertEqual(klass[int, str, float].__args__, (int, str, float)) - self.assertEqual( - klass[int, str, float, bool].__args__, (int, str, float, bool) - ) - # A bug was fixed in 3.11.1 - # (https://github.com/python/cpython/commit/74920aa27d0c57443dd7f704d6272cca9c507ab3) - # That means this assertion doesn't pass on 3.11.0, - # but it passes on all other Python versions - if sys.version_info[:3] != (3, 11, 0): - with self.assertRaises(TypeError): - klass[int] - - def test_substitution(self): - Ts = TypeVarTuple("Ts") - unpacked_str = Unpack[Ts][str] # This should not raise an error - self.assertIs(unpacked_str, str) - - @skipUnless(TYPING_3_11_0, "Needs Issue #103 for <3.11") - def test_nested_unpack(self): - Ts = TypeVarTuple("Ts") - Variadic = Tuple[int, Unpack[Ts]] - # Tuple[int, int, Tuple[str, int]] - direct_subscription = Variadic[int, Tuple[str, int]] - # Tuple[int, int, Tuple[*Ts, int]] - TupleAliasTs = Variadic[int, Tuple[Unpack[Ts], int]] - - # Tuple[int, int, Tuple[str, int]] - recursive_unpack = TupleAliasTs[str] - self.assertEqual(direct_subscription, recursive_unpack) - self.assertEqual(get_args(recursive_unpack), (int, int, Tuple[str, int])) - - # Test with Callable - T = TypeVar("T") - # Tuple[int, (*Ts) -> T] - CallableAliasTsT = Variadic[Callable[[Unpack[Ts]], T]] - # Tuple[int, (str, int) -> object] - callable_fully_subscripted = CallableAliasTsT[Unpack[Tuple[str, int]], object] - self.assertEqual(get_args(callable_fully_subscripted), (int, Callable[[str, int], object])) - - @skipUnless(TYPING_3_11_0, "Needs Issue #103 for <3.11") - def test_equivalent_nested_variadics(self): - T = TypeVar("T") - Ts = TypeVarTuple("Ts") - Variadic = Tuple[int, Unpack[Ts]] - TupleAliasTsT = Variadic[Tuple[Unpack[Ts], T]] - nested_tuple_bare = TupleAliasTsT[str, int, object] - - self.assertEqual(get_args(nested_tuple_bare), (int, Tuple[str, int, object])) - # Variants - self.assertEqual(nested_tuple_bare, TupleAliasTsT[Unpack[Tuple[str, int, object]]]) - self.assertEqual(nested_tuple_bare, TupleAliasTsT[Unpack[Tuple[str, int]], object]) - self.assertEqual(nested_tuple_bare, TupleAliasTsT[Unpack[Tuple[str]], Unpack[Tuple[int]], object]) - - @skipUnless(TYPING_3_11_0, "Needed for backport") - def test_type_var_inheritance(self): - Ts = TypeVarTuple("Ts") - self.assertFalse(isinstance(Unpack[Ts], TypeVar)) - self.assertFalse(isinstance(Unpack[Ts], typing.TypeVar)) - - -class TypeVarTupleTests(BaseTestCase): - - def test_basic_plain(self): - Ts = TypeVarTuple('Ts') - self.assertEqual(Ts, Ts) - self.assertIsInstance(Ts, TypeVarTuple) - Xs = TypeVarTuple('Xs') - Ys = TypeVarTuple('Ys') - self.assertNotEqual(Xs, Ys) - - def test_repr(self): - Ts = TypeVarTuple('Ts') - self.assertEqual(repr(Ts), 'Ts') - - def test_no_redefinition(self): - self.assertNotEqual(TypeVarTuple('Ts'), TypeVarTuple('Ts')) - - def test_cannot_subclass_vars(self): - with self.assertRaises(TypeError): - class V(TypeVarTuple('Ts')): - pass - - def test_cannot_subclass_var_itself(self): - with self.assertRaises(TypeError): - class V(TypeVarTuple): - pass - - def test_cannot_instantiate_vars(self): - Ts = TypeVarTuple('Ts') - with self.assertRaises(TypeError): - Ts() - - def test_tuple(self): - Ts = TypeVarTuple('Ts') - # Not legal at type checking time but we can't really check against it. - Tuple[Ts] - - def test_args_and_parameters(self): - Ts = TypeVarTuple('Ts') - - t = Tuple[tuple(Ts)] - if sys.version_info >= (3, 11): - self.assertEqual(t.__args__, (typing.Unpack[Ts],)) - else: - self.assertEqual(t.__args__, (Unpack[Ts],)) - self.assertEqual(t.__parameters__, (Ts,)) - - def test_pickle(self): - global Ts, Ts_default # pickle wants to reference the class by name - Ts = TypeVarTuple('Ts') - Ts_default = TypeVarTuple('Ts_default', default=Unpack[Tuple[int, str]]) - - for proto in range(pickle.HIGHEST_PROTOCOL + 1): - for typevartuple in (Ts, Ts_default): - z = pickle.loads(pickle.dumps(typevartuple, proto)) - self.assertEqual(z.__name__, typevartuple.__name__) - self.assertEqual(z.__default__, typevartuple.__default__) - - -class FinalDecoratorTests(BaseTestCase): - def test_final_unmodified(self): - def func(x): ... - self.assertIs(func, final(func)) - - def test_dunder_final(self): - @final - def func(): ... - @final - class Cls: ... - self.assertIs(True, func.__final__) - self.assertIs(True, Cls.__final__) - - class Wrapper: - __slots__ = ("func",) - def __init__(self, func): - self.func = func - def __call__(self, *args, **kwargs): - return self.func(*args, **kwargs) - - # Check that no error is thrown if the attribute - # is not writable. - @final - @Wrapper - def wrapped(): ... - self.assertIsInstance(wrapped, Wrapper) - self.assertIs(False, hasattr(wrapped, "__final__")) - - class Meta(type): - @property - def __final__(self): return "can't set me" - @final - class WithMeta(metaclass=Meta): ... - self.assertEqual(WithMeta.__final__, "can't set me") - - # Builtin classes throw TypeError if you try to set an - # attribute. - final(int) - self.assertIs(False, hasattr(int, "__final__")) - - # Make sure it works with common builtin decorators - class Methods: - @final - @classmethod - def clsmethod(cls): ... - - @final - @staticmethod - def stmethod(): ... - - # The other order doesn't work because property objects - # don't allow attribute assignment. - @property - @final - def prop(self): ... - - @final - @lru_cache # noqa: B019 - def cached(self): ... - - # Use getattr_static because the descriptor returns the - # underlying function, which doesn't have __final__. - self.assertIs( - True, - inspect.getattr_static(Methods, "clsmethod").__final__ - ) - self.assertIs( - True, - inspect.getattr_static(Methods, "stmethod").__final__ - ) - self.assertIs(True, Methods.prop.fget.__final__) - self.assertIs(True, Methods.cached.__final__) - - -class DisjointBaseTests(BaseTestCase): - def test_disjoint_base_unmodified(self): - class C: ... - self.assertIs(C, disjoint_base(C)) - - def test_dunder_disjoint_base(self): - @disjoint_base - class C: ... - - self.assertIs(C.__disjoint_base__, True) - - -class RevealTypeTests(BaseTestCase): - def test_reveal_type(self): - obj = object() - - with contextlib.redirect_stderr(io.StringIO()) as stderr: - self.assertIs(obj, reveal_type(obj)) - self.assertEqual("Runtime type is 'object'", stderr.getvalue().strip()) - - -class DataclassTransformTests(BaseTestCase): - def test_decorator(self): - def create_model(*, frozen: bool = False, kw_only: bool = True): - return lambda cls: cls - - decorated = dataclass_transform(kw_only_default=True, order_default=False)(create_model) - - class CustomerModel: - id: int - - self.assertIs(decorated, create_model) - self.assertEqual( - decorated.__dataclass_transform__, - { - "eq_default": True, - "order_default": False, - "kw_only_default": True, - "frozen_default": False, - "field_specifiers": (), - "kwargs": {}, - } - ) - self.assertIs( - decorated(frozen=True, kw_only=False)(CustomerModel), - CustomerModel - ) - - def test_base_class(self): - class ModelBase: - def __init_subclass__(cls, *, frozen: bool = False): ... - - Decorated = dataclass_transform( - eq_default=True, - order_default=True, - # Arbitrary unrecognized kwargs are accepted at runtime. - make_everything_awesome=True, - )(ModelBase) - - class CustomerModel(Decorated, frozen=True): - id: int - - self.assertIs(Decorated, ModelBase) - self.assertEqual( - Decorated.__dataclass_transform__, - { - "eq_default": True, - "order_default": True, - "kw_only_default": False, - "frozen_default": False, - "field_specifiers": (), - "kwargs": {"make_everything_awesome": True}, - } - ) - self.assertIsSubclass(CustomerModel, Decorated) - - def test_metaclass(self): - class Field: ... - - class ModelMeta(type): - def __new__( - cls, name, bases, namespace, *, init: bool = True, - ): - return super().__new__(cls, name, bases, namespace) - - Decorated = dataclass_transform( - order_default=True, field_specifiers=(Field,) - )(ModelMeta) - - class ModelBase(metaclass=Decorated): ... - - class CustomerModel(ModelBase, init=False): - id: int - - self.assertIs(Decorated, ModelMeta) - self.assertEqual( - Decorated.__dataclass_transform__, - { - "eq_default": True, - "order_default": True, - "kw_only_default": False, - "frozen_default": False, - "field_specifiers": (Field,), - "kwargs": {}, - } - ) - self.assertIsInstance(CustomerModel, Decorated) - - -class AllTests(BaseTestCase): - - def test_drop_in_for_typing(self): - # Check that the typing_extensions.__all__ is a superset of - # typing.__all__. - t_all = set(typing.__all__) - te_all = set(typing_extensions.__all__) - exceptions = {"ByteString"} - self.assertGreaterEqual(te_all, t_all - exceptions) - # Deprecated, to be removed in 3.14 - self.assertFalse(hasattr(typing_extensions, "ByteString")) - # These were never included in `typing.__all__`, - # and have been removed in Python 3.13 - self.assertNotIn('re', te_all) - self.assertNotIn('io', te_all) - - def test_typing_extensions_includes_standard(self): - a = typing_extensions.__all__ - self.assertIn('ClassVar', a) - self.assertIn('Type', a) - self.assertIn('ChainMap', a) - self.assertIn('ContextManager', a) - self.assertIn('Counter', a) - self.assertIn('DefaultDict', a) - self.assertIn('Deque', a) - self.assertIn('NewType', a) - self.assertIn('overload', a) - self.assertIn('Text', a) - self.assertIn('TYPE_CHECKING', a) - self.assertIn('TypeAlias', a) - self.assertIn('ParamSpec', a) - self.assertIn("Concatenate", a) - - self.assertIn('Annotated', a) - self.assertIn('get_type_hints', a) - - self.assertIn('Awaitable', a) - self.assertIn('AsyncIterator', a) - self.assertIn('AsyncIterable', a) - self.assertIn('Coroutine', a) - self.assertIn('AsyncContextManager', a) - - self.assertIn('AsyncGenerator', a) - - self.assertIn('Protocol', a) - self.assertIn('runtime', a) - - # Check that all objects in `__all__` are present in the module - for name in a: - self.assertTrue(hasattr(typing_extensions, name)) - - def test_all_names_in___all__(self): - exclude = { - 'GenericMeta', - 'KT', - 'PEP_560', - 'T', - 'T_co', - 'T_contra', - 'VT', - } - actual_names = { - name for name in dir(typing_extensions) - if not name.startswith("_") - and not isinstance(getattr(typing_extensions, name), types.ModuleType) - } - # Make sure all public names are in __all__ - self.assertEqual({*exclude, *typing_extensions.__all__}, - actual_names) - # Make sure all excluded names actually exist - self.assertLessEqual(exclude, actual_names) - - def test_typing_extensions_defers_when_possible(self): - exclude = set() - if sys.version_info < (3, 10): - exclude |= {'get_args', 'get_origin'} - if sys.version_info < (3, 10, 1): - exclude |= {"Literal"} - if sys.version_info < (3, 11): - exclude |= {'final', 'Any', 'NewType', 'overload', 'Concatenate'} - if sys.version_info < (3, 12): - exclude |= { - 'SupportsAbs', 'SupportsBytes', - 'SupportsComplex', 'SupportsFloat', 'SupportsIndex', 'SupportsInt', - 'SupportsRound', 'Unpack', 'dataclass_transform', - } - if sys.version_info < (3, 13): - exclude |= { - 'NamedTuple', 'Protocol', 'runtime_checkable', 'Generator', - 'AsyncGenerator', 'ContextManager', 'AsyncContextManager', - 'ParamSpec', 'TypeVar', 'TypeVarTuple', 'get_type_hints', - } - if sys.version_info < (3, 14): - exclude |= { - 'TypeAliasType' - } - if not typing_extensions._PEP_728_IMPLEMENTED: - exclude |= {'TypedDict', 'is_typeddict'} - for item in typing_extensions.__all__: - if item not in exclude and hasattr(typing, item): - self.assertIs( - getattr(typing_extensions, item), - getattr(typing, item)) - - def test_alias_names_still_exist(self): - for name in typing_extensions._typing_names: - # If this fails, change _typing_names to conditionally add the name - # depending on the Python version. - self.assertTrue( - hasattr(typing_extensions, name), - f"{name} no longer exists in typing", - ) - - def test_typing_extensions_compiles_with_opt(self): - file_path = typing_extensions.__file__ - try: - subprocess.check_output(f'{sys.executable} -OO {file_path}', - stderr=subprocess.STDOUT, - shell=True) - except subprocess.CalledProcessError: - self.fail('Module does not compile with optimize=2 (-OO flag).') - - class CoolEmployee(NamedTuple): name: str cool: int