diff --git a/python2/test_typing.py b/python2/test_typing.py index a4459f0b..0550d885 100644 --- a/python2/test_typing.py +++ b/python2/test_typing.py @@ -7,6 +7,8 @@ import re import subprocess import sys +import abc +import types from unittest import TestCase, main, SkipTest from copy import copy, deepcopy @@ -16,11 +18,11 @@ from typing import Union, Optional from typing import Tuple, List, MutableMapping from typing import Callable -from typing import Generic, ClassVar, GenericMeta +from typing import Generic, ClassVar, GenericMeta, Final, Literal from typing import cast -from typing import Type +from typing import Type, Protocol, runtime_checkable from typing import NewType -from typing import NamedTuple +from typing import NamedTuple, TypedDict from typing import Pattern, Match import typing import weakref @@ -514,6 +516,514 @@ def get(self, key, default=None): class ProtocolTests(BaseTestCase): + def test_basic_protocol(self): + @runtime_checkable + class P(Protocol): + def meth(self): + pass + class C(object): pass + class D(object): + 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(object): 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): + @runtime_checkable + class Function(Protocol): + def __call__(self, *args, **kwargs): + pass + def f(): + pass + self.assertIsInstance(f, Function) + + def test_no_inheritance_from_nominal(self): + class C(object): pass + class BP(Protocol): pass + with self.assertRaises(TypeError): + class P(C, Protocol): + pass + with self.assertRaises(TypeError): + class P(Protocol, C): + pass + with self.assertRaises(TypeError): + class P(BP, C, Protocol): + pass + class D(BP, C): pass + class E(C, BP): pass + self.assertNotIsInstance(D(), E) + self.assertNotIsInstance(E(), D) + + def test_no_instantiation(self): + class P(Protocol): pass + with self.assertRaises(TypeError): + P() + class C(P): pass + self.assertIsInstance(C(), C) + T = typing.TypeVar('T') + class PG(Protocol[T]): pass + with self.assertRaises(TypeError): + PG() + with self.assertRaises(TypeError): + PG[int]() + with self.assertRaises(TypeError): + PG[T]() + class CG(PG[T]): pass + self.assertIsInstance(CG[int](), CG) + + def test_cannot_instantiate_abstract(self): + @runtime_checkable + class P(Protocol): + @abc.abstractmethod + def ameth(self): + raise NotImplementedError + class B(P): + pass + class C(B): + def ameth(self): + return 26 + 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(object): + def meth1(self): + pass + def meth2(self): + pass + class C1(object): + def meth1(self): + pass + class C2(object): + 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(object): + def meth1(self): + pass + def meth2(self): + pass + class C1(object): + def meth1(self): + pass + class C2(object): + 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 = typing.TypeVar('T') + @runtime_checkable + class P(Protocol): + def x(self): pass + @runtime_checkable + class PG(Protocol[T]): + def x(self): pass + class BadP(Protocol): + def x(self): pass + class BadPG(Protocol[T]): + def x(self): pass + class C(object): + def x(self): pass + self.assertIsSubclass(C, P) + self.assertIsSubclass(C, PG) + self.assertIsSubclass(BadP, PG) + self.assertIsSubclass(PG[int], PG) + self.assertIsSubclass(BadPG[int], P) + self.assertIsSubclass(BadPG[T], PG) + with self.assertRaises(TypeError): + issubclass(C, PG[T]) + with self.assertRaises(TypeError): + issubclass(C, PG[C]) + with self.assertRaises(TypeError): + issubclass(C, BadP) + with self.assertRaises(TypeError): + issubclass(C, BadPG) + with self.assertRaises(TypeError): + issubclass(P, PG[T]) + with self.assertRaises(TypeError): + issubclass(PG, PG[int]) + + def test_protocols_issubclass_non_callable(self): + class C(object): + x = 1 + @runtime_checkable + class PNonCall(Protocol): + x = 1 + with self.assertRaises(TypeError): + issubclass(C, PNonCall) + self.assertIsInstance(C(), PNonCall) + PNonCall.register(C) + with self.assertRaises(TypeError): + issubclass(C, PNonCall) + self.assertIsInstance(C(), PNonCall) + # check that non-protocol subclasses are not affected + class D(PNonCall): pass + self.assertNotIsSubclass(C, D) + self.assertNotIsInstance(C(), D) + D.register(C) + self.assertIsSubclass(C, D) + self.assertIsInstance(C(), D) + with self.assertRaises(TypeError): + issubclass(D, PNonCall) + + def test_protocols_isinstance(self): + T = typing.TypeVar('T') + @runtime_checkable + class P(Protocol): + def meth(x): pass + @runtime_checkable + class PG(Protocol[T]): + def meth(x): pass + class BadP(Protocol): + def meth(x): pass + class BadPG(Protocol[T]): + def meth(x): pass + class C(object): + def meth(x): pass + self.assertIsInstance(C(), P) + self.assertIsInstance(C(), PG) + with self.assertRaises(TypeError): + isinstance(C(), PG[T]) + with self.assertRaises(TypeError): + isinstance(C(), PG[C]) + with self.assertRaises(TypeError): + isinstance(C(), BadP) + with self.assertRaises(TypeError): + isinstance(C(), BadPG) + + def test_protocols_isinstance_init(self): + T = typing.TypeVar('T') + @runtime_checkable + class P(Protocol): + x = 1 + @runtime_checkable + class PG(Protocol[T]): + x = 1 + class C(object): + def __init__(self, x): + self.x = x + self.assertIsInstance(C(1), P) + self.assertIsInstance(C(1), PG) + + 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(object): 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(object): + x = 1 + class B(A): + x = None + class C(object): + 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): pass + class A(object): + def x(self): pass + class B(A): + x = None + class C(object): + def __init__(self): + self.x = None + self.assertNotIsInstance(B(), P) + self.assertNotIsInstance(C(), P) + + 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(object): + x = 1 + class D(object): + def meth(self): pass + self.assertNotIsInstance(C(), NonP) + self.assertNotIsInstance(D(), NonPR) + self.assertNotIsSubclass(C, NonP) + self.assertNotIsSubclass(D, NonPR) + self.assertIsInstance(NonPR(), PR) + self.assertIsSubclass(NonPR, PR) + + def test_custom_subclasshook(self): + class P(Protocol): + x = 1 + class OKClass(object): pass + class BadClass(object): + 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) + + def test_issubclass_fails_correctly(self): + @runtime_checkable + class P(Protocol): + x = 1 + class C: pass + with self.assertRaises(TypeError): + issubclass(C(), P) + + def test_defining_generic_protocols(self): + T = typing.TypeVar('T') + S = typing.TypeVar('S') + @runtime_checkable + class PR(Protocol[T, S]): + def meth(self): pass + class P(PR[int, T], Protocol[T]): + y = 1 + self.assertIsSubclass(PR[int, T], PR) + self.assertIsSubclass(P[str], PR) + with self.assertRaises(TypeError): + PR[int] + with self.assertRaises(TypeError): + P[int, str] + 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) + + def test_defining_generic_protocols_old_style(self): + T = typing.TypeVar('T') + S = typing.TypeVar('S') + @runtime_checkable + class PR(Protocol, typing.Generic[T, S]): + def meth(self): pass + class P(PR[int, str], Protocol): + y = 1 + self.assertIsSubclass(PR[int, str], PR) + self.assertIsSubclass(P, PR) + with self.assertRaises(TypeError): + PR[int] + with self.assertRaises(TypeError): + PR[int, 1] + class P1(Protocol, typing.Generic[T]): + def bar(self, x): pass + class P2(typing.Generic[T], Protocol): + def bar(self, x): pass + @runtime_checkable + class PSub(P1[str], Protocol): + x = 1 + class Test(object): + x = 1 + def bar(self, x): + return x + self.assertIsInstance(Test(), PSub) + with self.assertRaises(TypeError): + PR[int, ClassVar] + + def test_init_called(self): + T = typing.TypeVar('T') + class P(Protocol[T]): pass + class C(P[T]): + def __init__(self): + self.test = 'OK' + self.assertEqual(C[int]().test, 'OK') + + def test_protocols_bad_subscripts(self): + T = typing.TypeVar('T') + S = typing.TypeVar('S') + with self.assertRaises(TypeError): + class P(Protocol[T, T]): pass + with self.assertRaises(TypeError): + class P(Protocol[int]): pass + with self.assertRaises(TypeError): + class P(Protocol[T], Protocol[S]): pass + with self.assertRaises(TypeError): + class P(Protocol[T], typing.Mapping[T, S]): pass + + def test_generic_protocols_repr(self): + T = typing.TypeVar('T') + S = typing.TypeVar('S') + class P(Protocol[T, S]): pass + self.assertTrue(repr(P).endswith('P')) + self.assertTrue(repr(P[T, S]).endswith('P[~T, ~S]')) + self.assertTrue(repr(P[int, str]).endswith('P[int, str]')) + + def test_generic_protocols_eq(self): + T = typing.TypeVar('T') + S = typing.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][typing.Tuple[T, S]][int, str], + P[typing.Tuple[int, str], typing.Tuple[int, str]]) + + def test_generic_protocols_special_from_generic(self): + T = typing.TypeVar('T') + class P(Protocol[T]): pass + self.assertEqual(P.__parameters__, (T,)) + self.assertIs(P.__args__, None) + self.assertIs(P.__origin__, None) + 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 = typing.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) + with self.assertRaises(AttributeError): + self.assertFalse(P._is_runtime_protocol) + self.assertTrue(PR._is_runtime_protocol) + self.assertTrue(PG[int]._is_protocol) + self.assertEqual(P._get_protocol_attrs(), {'meth'}) + self.assertEqual(PR._get_protocol_attrs(), {'x'}) + self.assertEqual(frozenset(PG._get_protocol_attrs()), + frozenset({'x', 'meth'})) + self.assertEqual(frozenset(PG[int]._get_protocol_attrs()), + frozenset({'x', 'meth'})) + + def test_no_runtime_deco_on_nominal(self): + with self.assertRaises(TypeError): + @runtime_checkable + class C(object): pass + class Proto(Protocol): + x = 1 + with self.assertRaises(TypeError): + @runtime_checkable + class Concrete(Proto): + pass + + def test_none_treated_correctly(self): + @runtime_checkable + class P(Protocol): + x = None # type: int + class B(object): pass + self.assertNotIsInstance(B(), P) + class C(object): + x = 1 + class D(object): + x = None + self.assertIsInstance(C(), P) + self.assertIsInstance(D(), P) + class CI(object): + def __init__(self): + self.x = 1 + class DI(object): + def __init__(self): + self.x = None + self.assertIsInstance(C(), P) + self.assertIsInstance(D(), P) + + def test_protocols_in_unions(self): + class P(Protocol): + x = None # type: int + 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 = typing.TypeVar('T') + + @runtime_checkable + class P(Protocol[T]): + x = 1 + class CP(P[int]): + pass + + 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(object): + x = 1 + self.assertIsInstance(E(), D) + def test_supports_int(self): self.assertIsSubclass(int, typing.SupportsInt) self.assertNotIsSubclass(str, typing.SupportsInt) @@ -546,8 +1056,8 @@ def test_supports_index(self): self.assertNotIsSubclass(str, typing.SupportsIndex) def test_protocol_instance_type_error(self): - with self.assertRaises(TypeError): - isinstance(0, typing.SupportsAbs) + self.assertIsInstance(0, typing.SupportsAbs) + self.assertNotIsInstance('no', typing.SupportsAbs) class C1(typing.SupportsInt): def __int__(self): return 42 @@ -657,7 +1167,7 @@ def test_new_repr_complex(self): def test_new_repr_bare(self): T = TypeVar('T') self.assertEqual(repr(Generic[T]), 'typing.Generic[~T]') - self.assertEqual(repr(typing._Protocol[T]), 'typing.Protocol[~T]') + self.assertEqual(repr(typing.Protocol[T]), 'typing.Protocol[~T]') class C(typing.Dict[Any, Any]): pass # this line should just work repr(C.__mro__) @@ -941,7 +1451,7 @@ def test_fail_with_bare_generic(self): with self.assertRaises(TypeError): Tuple[Generic[T]] with self.assertRaises(TypeError): - List[typing._Protocol] + List[typing.Protocol] with self.assertRaises(TypeError): isinstance(1, Generic) @@ -1346,6 +1856,105 @@ def test_no_isinstance(self): issubclass(int, ClassVar) +class FinalTests(BaseTestCase): + + def test_basics(self): + with self.assertRaises(TypeError): + Final[1] + with self.assertRaises(TypeError): + Final[int, str] + with self.assertRaises(TypeError): + Final[int][str] + + def test_repr(self): + self.assertEqual(repr(Final), 'typing.Final') + cv = Final[int] + self.assertEqual(repr(cv), 'typing.Final[int]') + cv = Final[Employee] + self.assertEqual(repr(cv), 'typing.Final[%s.Employee]' % __name__) + + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class C(type(Final)): + pass + with self.assertRaises(TypeError): + class C(type(Final[int])): + pass + + def test_cannot_init(self): + with self.assertRaises(TypeError): + Final() + with self.assertRaises(TypeError): + type(Final)() + with self.assertRaises(TypeError): + type(Final[typing.Optional[int]])() + + def test_no_isinstance(self): + with self.assertRaises(TypeError): + isinstance(1, Final[int]) + with self.assertRaises(TypeError): + issubclass(int, Final) + + +class LiteralTests(BaseTestCase): + def test_basics(self): + Literal[1] + Literal[1, 2, 3] + Literal["x", "y", "z"] + Literal[None] + + def test_illegal_parameters_do_not_raise_runtime_errors(self): + # Type checkers should reject these types, but we do not + # raise errors at runtime to maintain maximium flexibility + Literal[int] + Literal[Literal[1, 2], Literal[4, 5]] + Literal[3j + 2, ..., ()] + Literal[b"foo", u"bar"] + Literal[{"foo": 3, "bar": 4}] + Literal[T] + + def test_literals_inside_other_types(self): + typing.List[Literal[1, 2, 3]] + typing.List[Literal[("foo", "bar", "baz")]] + + def test_repr(self): + self.assertEqual(repr(Literal[1]), "typing.Literal[1]") + self.assertEqual(repr(Literal[1, True, "foo"]), "typing.Literal[1, True, u'foo']") + self.assertEqual(repr(Literal[int]), "typing.Literal[int]") + self.assertEqual(repr(Literal), "typing.Literal") + self.assertEqual(repr(Literal[None]), "typing.Literal[None]") + + def test_cannot_init(self): + with self.assertRaises(TypeError): + Literal() + with self.assertRaises(TypeError): + Literal[1]() + with self.assertRaises(TypeError): + type(Literal)() + with self.assertRaises(TypeError): + type(Literal[1])() + + def test_no_isinstance_or_issubclass(self): + with self.assertRaises(TypeError): + isinstance(1, Literal[1]) + with self.assertRaises(TypeError): + isinstance(int, Literal[1]) + with self.assertRaises(TypeError): + issubclass(1, Literal[1]) + with self.assertRaises(TypeError): + issubclass(int, Literal[1]) + + def test_no_subclassing(self): + with self.assertRaises(TypeError): + class Foo(Literal[1]): pass + with self.assertRaises(TypeError): + class Bar(Literal): pass + + def test_no_multiple_subscripts(self): + with self.assertRaises(TypeError): + Literal[1][1] + + class CastTests(BaseTestCase): def test_basics(self): @@ -1855,6 +2464,85 @@ def test_pickle(self): self.assertEqual(jane2, jane) +class TypedDictTests(BaseTestCase): + + def test_basics_iterable_syntax(self): + Emp = TypedDict(b'Emp', {'name': str, 'id': int}) + self.assertIsSubclass(Emp, dict) + self.assertIsSubclass(Emp, typing.MutableMapping) + if sys.version_info[0] >= 3: + import collections.abc + 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) + + def test_basics_keywords_syntax(self): + Emp = TypedDict(b'Emp', name=str, id=int) + self.assertIsSubclass(Emp, dict) + self.assertIsSubclass(Emp, typing.MutableMapping) + if sys.version_info[0] >= 3: + import collections.abc + 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) + + def test_typeddict_errors(self): + Emp = TypedDict(b'Emp', {'name': str, 'id': int}) + self.assertEqual(TypedDict.__module__, 'typing') + 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) + with self.assertRaises(TypeError): + TypedDict('Hi', x=1) + with self.assertRaises(TypeError): + TypedDict('Hi', [('x', int), ('y', 1)]) + with self.assertRaises(TypeError): + TypedDict('Hi', [('x', int)], y=int) + + def test_pickle(self): + global EmpD # pickle wants to reference the class by name + EmpD = TypedDict(b'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) + + def test_optional(self): + EmpD = TypedDict(b'EmpD', name=str, id=int) + + self.assertEqual(typing.Optional[EmpD], typing.Union[None, EmpD]) + self.assertNotEqual(typing.List[EmpD], typing.Tuple[EmpD]) + + def test_total(self): + D = TypedDict(b'D', {'x': int}, total=False) + self.assertEqual(D(), {}) + self.assertEqual(D(x=1), {'x': 1}) + self.assertEqual(D.__total__, False) + + class IOTests(BaseTestCase): def test_io_submodule(self): diff --git a/python2/typing.py b/python2/typing.py index d3e7f9c8..eedb18b0 100644 --- a/python2/typing.py +++ b/python2/typing.py @@ -20,8 +20,11 @@ 'Any', 'Callable', 'ClassVar', + 'Final', 'Generic', + 'Literal', 'Optional', + 'Protocol', 'Tuple', 'Type', 'TypeVar', @@ -65,17 +68,20 @@ 'Set', 'FrozenSet', 'NamedTuple', # Not really a type. + 'TypedDict', # Not really a type. 'Generator', # One-off things. 'AnyStr', 'cast', + 'final', 'get_type_hints', 'NewType', 'no_type_check', 'no_type_check_decorator', 'NoReturn', 'overload', + 'runtime_checkable', 'Text', 'TYPE_CHECKING', ] @@ -360,7 +366,7 @@ def _type_check(arg, msg): if ( type(arg).__name__ in ('_Union', '_Optional') and not getattr(arg, '__origin__', None) or - isinstance(arg, TypingMeta) and arg._gorg in (Generic, _Protocol) + isinstance(arg, TypingMeta) and arg._gorg in (Generic, Protocol) ): raise TypeError("Plain %s is not valid as type argument" % arg) return arg @@ -379,7 +385,7 @@ def _type_repr(obj): return _qualname(obj) return '%s.%s' % (obj.__module__, _qualname(obj)) if obj is Ellipsis: - return('...') + return '...' if isinstance(obj, types.FunctionType): return obj.__name__ return repr(obj) @@ -450,6 +456,157 @@ def __eq__(self, other): ClassVar = _ClassVar(_root=True) +class _FinalMeta(TypingMeta): + """Metaclass for _Final""" + + def __new__(cls, name, bases, namespace): + cls.assert_no_subclassing(bases) + self = super(_FinalMeta, cls).__new__(cls, name, bases, namespace) + return self + + +class _Final(_FinalTypingBase): + """A special typing construct to indicate that a name + cannot be re-assigned or overridden in a subclass. + For example: + + MAX_SIZE: Final = 9000 + MAX_SIZE += 1 # Error reported by type checker + + class Connection: + TIMEOUT: Final[int] = 10 + class FastConnector(Connection): + TIMEOUT = 1 # Error reported by type checker + + There is no runtime checking of these properties. + """ + + __metaclass__ = _FinalMeta + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(_type_check(item, + '{} accepts only single type.'.format(cls.__name__[1:])), + _root=True) + raise TypeError('{} cannot be further subscripted' + .format(cls.__name__[1:])) + + def _eval_type(self, globalns, localns): + new_tp = _eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super(_Final, self).__repr__() + if self.__type__ is not None: + r += '[{}]'.format(_type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _Final): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + +Final = _Final(_root=True) + + +def final(f): + """This decorator can be used to indicate to type checkers that + the decorated method cannot be overridden, and decorated class + cannot be subclassed. For example: + + class Base: + @final + def done(self) -> None: + ... + class Sub(Base): + def done(self) -> None: # Error reported by type checker + ... + @final + class Leaf: + ... + class Other(Leaf): # Error reported by type checker + ... + + There is no runtime checking of these properties. + """ + return f + + +class _LiteralMeta(TypingMeta): + """Metaclass for _Literal""" + + def __new__(cls, name, bases, namespace): + cls.assert_no_subclassing(bases) + self = super(_LiteralMeta, cls).__new__(cls, name, bases, namespace) + return self + + +class _Literal(_FinalTypingBase): + """A type that can be used to indicate to type checkers that the + corresponding value has a value literally equivalent to the + provided parameter. For example: + + var: Literal[4] = 4 + + The type checker understands that 'var' is literally equal to the + value 4 and no other value. + + Literal[...] cannot be subclassed. There is no runtime checking + verifying that the parameter is actually a value instead of a type. + """ + + __metaclass__ = _LiteralMeta + __slots__ = ('__values__',) + + def __init__(self, values=None, **kwds): + self.__values__ = values + + def __getitem__(self, item): + cls = type(self) + if self.__values__ is None: + if not isinstance(item, tuple): + item = (item,) + return cls(values=item, + _root=True) + raise TypeError('{} cannot be further subscripted' + .format(cls.__name__[1:])) + + def _eval_type(self, globalns, localns): + return self + + def __repr__(self): + r = super(_Literal, self).__repr__() + if self.__values__ is not None: + r += '[{}]'.format(', '.join(map(_type_repr, self.__values__))) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__values__)) + + def __eq__(self, other): + if not isinstance(other, _Literal): + return NotImplemented + if self.__values__ is not None: + return self.__values__ == other.__values__ + return self is other + + +Literal = _Literal(_root=True) + + class AnyMeta(TypingMeta): """Metaclass for Any.""" @@ -1035,10 +1192,11 @@ def __new__(cls, name, bases, namespace, if base is Generic: raise TypeError("Cannot inherit from plain Generic") if (isinstance(base, GenericMeta) and - base.__origin__ is Generic): + base.__origin__ in (Generic, Protocol)): if gvars is not None: raise TypeError( - "Cannot inherit from Generic[...] multiple types.") + "Cannot inherit from Generic[...] or" + " Protocol[...] multiple times.") gvars = base.__parameters__ if gvars is None: gvars = tvars @@ -1048,8 +1206,10 @@ def __new__(cls, name, bases, namespace, if not tvarset <= gvarset: raise TypeError( "Some type variables (%s) " - "are not listed in Generic[%s]" % + "are not listed in %s[%s]" % (", ".join(str(t) for t in tvars if t not in gvarset), + "Generic" if any(b.__origin__ is Generic + for b in bases) else "Protocol", ", ".join(str(g) for g in gvars))) tvars = gvars @@ -1198,25 +1358,21 @@ def __getitem__(self, params): "Parameter list to %s[...] cannot be empty" % _qualname(self)) msg = "Parameters to generic types must be types." params = tuple(_type_check(p, msg) for p in params) - if self is Generic: + if self in (Generic, Protocol): # Generic can only be subscripted with unique type variables. if not all(isinstance(p, TypeVar) for p in params): raise TypeError( - "Parameters to Generic[...] must all be type variables") + "Parameters to %s[...] must all be type variables" % self.__name__) if len(set(params)) != len(params): raise TypeError( - "Parameters to Generic[...] must all be unique") + "Parameters to %s[...] must all be unique" % self.__name__) tvars = params args = params elif self in (Tuple, Callable): tvars = _type_vars(params) args = params - elif self is _Protocol: - # _Protocol is internal, don't check anything. - tvars = params - args = params - elif self.__origin__ in (Generic, _Protocol): - # Can't subscript Generic[...] or _Protocol[...]. + elif self.__origin__ in (Generic, Protocol): + # Can't subscript Generic[...] or Protocol[...]. raise TypeError("Cannot subscript already-subscripted %s" % repr(self)) else: @@ -1604,84 +1760,165 @@ def utf8(value): class _ProtocolMeta(GenericMeta): - """Internal metaclass for _Protocol. + """Internal metaclass for Protocol. - This exists so _Protocol classes can be generic without deriving + This exists so Protocol classes can be generic without deriving from Generic. """ + def __init__(cls, *args, **kwargs): + super(_ProtocolMeta, cls).__init__(*args, **kwargs) + if not cls.__dict__.get('_is_protocol', None): + cls._is_protocol = any(b is Protocol or + isinstance(b, _ProtocolMeta) and + b.__origin__ is Protocol + for b in cls.__bases__) + if cls._is_protocol: + for base in cls.__mro__[1:]: + if not (base in (object, Generic, Callable) or + isinstance(base, TypingMeta) and base._is_protocol or + isinstance(base, GenericMeta) and base.__origin__ is Generic): + raise TypeError('Protocols can only inherit from other protocols,' + ' got %r' % base) + cls._callable_members_only = all(callable(getattr(cls, attr)) + for attr in cls._get_protocol_attrs()) + + def _no_init(self, *args, **kwargs): + if type(self)._is_protocol: + raise TypeError('Protocols cannot be instantiated') + cls.__init__ = _no_init + + def _proto_hook(cls, other): + if not cls.__dict__.get('_is_protocol', None): + return NotImplemented + if not isinstance(other, type): + # Similar error as for issubclass(1, int) + # (also not a chance for old-style classes) + raise TypeError('issubclass() arg 1 must be a new-style class') + for attr in cls._get_protocol_attrs(): + for base in other.__mro__: + if attr in base.__dict__: + if base.__dict__[attr] is None: + return NotImplemented + break + else: + return NotImplemented + return True + if '__subclasshook__' not in cls.__dict__: + cls.__subclasshook__ = classmethod(_proto_hook) - def __instancecheck__(self, obj): - if _Protocol not in self.__bases__: - return super(_ProtocolMeta, self).__instancecheck__(obj) - raise TypeError("Protocols cannot be used with isinstance().") + def __instancecheck__(self, instance): + # We need this method for situations where attributes are assigned in __init__ + if isinstance(instance, type): + # This looks like a fundamental limitation of Python 2. + # It cannot support runtime protocol metaclasses, On Python 2 classes + # cannot be correctly inspected as instances of protocols. + return False + if ((not getattr(self, '_is_protocol', False) or + self._callable_members_only) and + issubclass(instance.__class__, self)): + return True + if self._is_protocol: + if all(hasattr(instance, attr) and + (not callable(getattr(self, attr)) or + getattr(instance, attr) is not None) + for attr in self._get_protocol_attrs()): + return True + return super(GenericMeta, self).__instancecheck__(instance) def __subclasscheck__(self, cls): - if not self._is_protocol: - # No structural checks since this isn't a protocol. - return NotImplemented + if (self.__dict__.get('_is_protocol', None) and + not self.__dict__.get('_is_runtime_protocol', None)): + if sys._getframe(1).f_globals['__name__'] in ['abc', 'functools']: + return False + raise TypeError("Instance and class checks can only be used with" + " @runtime_checkable protocols") + if (self.__dict__.get('_is_runtime_protocol', None) and + not self._callable_members_only): + if sys._getframe(1).f_globals['__name__'] in ['abc', 'functools']: + return super(GenericMeta, self).__subclasscheck__(cls) + raise TypeError("Protocols with non-method members" + " don't support issubclass()") + return super(_ProtocolMeta, self).__subclasscheck__(cls) - if self is _Protocol: - # Every class is a subclass of the empty protocol. - return True + def _get_protocol_attrs(self): + attrs = set() + for base in self.__mro__[:-1]: # without object + if base.__name__ in ('Protocol', 'Generic'): + continue + annotations = getattr(base, '__annotations__', {}) + for attr in list(base.__dict__.keys()) + list(annotations.keys()): + if (not attr.startswith('_abc_') and attr not in ( + '__abstractmethods__', '__annotations__', '__weakref__', + '_is_protocol', '_is_runtime_protocol', '__dict__', + '__args__', '__slots__', '_get_protocol_attrs', + '__next_in_mro__', '__parameters__', '__origin__', + '__orig_bases__', '__extra__', '__tree_hash__', + '__doc__', '__subclasshook__', '__init__', '__new__', + '__module__', '_MutableMapping__marker', + '__metaclass__', '_gorg', '_callable_members_only')): + attrs.add(attr) + return attrs - # Find all attributes defined in the protocol. - attrs = self._get_protocol_attrs() - for attr in attrs: - if not any(attr in d.__dict__ for d in cls.__mro__): - return False - return True +class Protocol(object): + """Base class for protocol classes. Protocol classes are defined as:: - def _get_protocol_attrs(self): - # Get all Protocol base classes. - protocol_bases = [] - for c in self.__mro__: - if getattr(c, '_is_protocol', False) and c.__name__ != '_Protocol': - protocol_bases.append(c) + class Proto(Protocol): + def meth(self): + # type: () -> int + pass - # Get attributes included in protocol. - attrs = set() - for base in protocol_bases: - for attr in base.__dict__.keys(): - # Include attributes not defined in any non-protocol bases. - for c in self.__mro__: - if (c is not base and attr in c.__dict__ and - not getattr(c, '_is_protocol', False)): - break - else: - if (not attr.startswith('_abc_') and - attr != '__abstractmethods__' and - attr != '_is_protocol' and - attr != '_gorg' and - attr != '__dict__' and - attr != '__args__' and - attr != '__slots__' and - attr != '_get_protocol_attrs' and - attr != '__next_in_mro__' and - attr != '__parameters__' and - attr != '__origin__' and - attr != '__orig_bases__' and - attr != '__extra__' and - attr != '__tree_hash__' and - attr != '__module__'): - attrs.add(attr) + Such classes are primarily used with static type checkers that recognize + structural subtyping (static duck-typing), for example:: - return attrs + class C: + def meth(self): + # type: () -> int + return 0 + def func(x): + # type: (Proto) -> int + return x.meth() -class _Protocol(object): - """Internal base class for protocol classes. + func(C()) # Passes static type check - This implements a simple-minded structural issubclass check - (similar but more general than the one-offs in collections.abc - such as Hashable). + See PEP 544 for details. Protocol classes decorated with @typing.runtime_checkable + act as simple-minded runtime protocols that checks only the presence of + given attributes, ignoring their type signatures. + + Protocol classes can be generic, they are defined as:: + + class GenProto(Protocol[T]): + def meth(self): + # type: () -> T + pass """ __metaclass__ = _ProtocolMeta __slots__ = () - _is_protocol = True + def __new__(cls, *args, **kwds): + if cls._gorg is Protocol: + raise TypeError("Type Protocol cannot be instantiated; " + "it can be used only as a base class") + return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) + + +def runtime_checkable(cls): + """Mark a protocol class as a runtime protocol, so that it + can be used with isinstance() and issubclass(). Raise TypeError + if applied to a non-protocol class. + + This allows a simple-minded structural check very similar to the + one-offs in collections.abc such as Hashable. + """ + if not isinstance(cls, _ProtocolMeta) or not cls._is_protocol: + raise TypeError('@runtime_checkable can be only applied to protocol classes,' + ' got %r' % cls) + cls._is_runtime_protocol = True + return cls + # Various ABCs mimicking those in collections.abc. # A few are simply re-exported for completeness. @@ -1699,7 +1936,8 @@ class Iterator(Iterable[T_co]): __extra__ = collections_abc.Iterator -class SupportsInt(_Protocol): +@runtime_checkable +class SupportsInt(Protocol): __slots__ = () @abstractmethod @@ -1707,7 +1945,8 @@ def __int__(self): pass -class SupportsFloat(_Protocol): +@runtime_checkable +class SupportsFloat(Protocol): __slots__ = () @abstractmethod @@ -1715,7 +1954,8 @@ def __float__(self): pass -class SupportsComplex(_Protocol): +@runtime_checkable +class SupportsComplex(Protocol): __slots__ = () @abstractmethod @@ -1723,7 +1963,8 @@ def __complex__(self): pass -class SupportsIndex(_Protocol): +@runtime_checkable +class SupportsIndex(Protocol): __slots__ = () @abstractmethod @@ -1731,7 +1972,8 @@ def __index__(self): pass -class SupportsAbs(_Protocol[T_co]): +@runtime_checkable +class SupportsAbs(Protocol[T_co]): __slots__ = () @abstractmethod @@ -1744,7 +1986,8 @@ class Reversible(Iterable[T_co]): __slots__ = () __extra__ = collections_abc.Reversible else: - class Reversible(_Protocol[T_co]): + @runtime_checkable + class Reversible(Protocol[T_co]): __slots__ = () @abstractmethod @@ -2006,6 +2249,87 @@ def NamedTuple(typename, fields): return cls +def _check_fails(cls, other): + try: + if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools', 'typing']: + # Typed dicts are only for static structural subtyping. + raise TypeError('TypedDict does not support instance and class checks') + except (AttributeError, ValueError): + pass + return False + + +def _dict_new(cls, *args, **kwargs): + return dict(*args, **kwargs) + + +def _typeddict_new(cls, _typename, _fields=None, **kwargs): + total = kwargs.pop('total', True) + if _fields is None: + _fields = kwargs + elif kwargs: + raise TypeError("TypedDict takes either a dict or keyword arguments," + " but not both") + + ns = {'__annotations__': dict(_fields), '__total__': total} + try: + # Setting correct module is necessary to make typed dict classes pickleable. + ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass + + return _TypedDictMeta(_typename, (), ns) + + +class _TypedDictMeta(type): + def __new__(cls, name, bases, ns, total=True): + # Create new typed dict class object. + # This method is called directly when TypedDict is subclassed, + # or via _typeddict_new when TypedDict is instantiated. This way + # TypedDict supports all three syntaxes described in its docstring. + # Subclasses and instances of TypedDict return actual dictionaries + # via _dict_new. + ns['__new__'] = _typeddict_new if name == b'TypedDict' else _dict_new + tp_dict = super(_TypedDictMeta, cls).__new__(cls, name, (dict,), ns) + + anns = ns.get('__annotations__', {}) + msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" + anns = {n: _type_check(tp, msg) for n, tp in anns.items()} + for base in bases: + anns.update(base.__dict__.get('__annotations__', {})) + tp_dict.__annotations__ = anns + if not hasattr(tp_dict, '__total__'): + tp_dict.__total__ = total + return tp_dict + + __instancecheck__ = __subclasscheck__ = _check_fails + + +TypedDict = _TypedDictMeta(b'TypedDict', (dict,), {}) +TypedDict.__module__ = __name__ +TypedDict.__doc__ = \ + """A simple typed name space. At runtime it is equivalent to a plain dict. + + TypedDict creates a dictionary type that expects all of its + instances to have a certain set of keys, with each key + associated with a value of a consistent type. This expectation + is not checked at runtime but is only enforced by type checkers. + Usage:: + + Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) + + a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK + b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check + + assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') + + The type info could be accessed via Point2D.__annotations__. TypedDict + supports an additional equivalent form:: + + Point2D = TypedDict('Point2D', x=int, y=int, label=str) + """ + + def NewType(name, tp): """NewType creates simple unique types with almost zero runtime overhead. NewType(name, tp) is considered a subtype of tp diff --git a/test-requirements.txt b/test-requirements.txt index d31b1380..1a033f49 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,4 +4,3 @@ flake8-pyi; python_version >= '3.6' pytest>=4.4.1; python_version >= '3.4' pytest-xdist>=1.18; python_version >= '3.4' pytest-cov>=2.4.0; python_version >= '3.4' -typing>=3.6.1; python_version <= '3.4' diff --git a/typing_extensions/src_py2/test_typing_extensions.py b/typing_extensions/src_py2/test_typing_extensions.py index cb7c3831..76e51907 100644 --- a/typing_extensions/src_py2/test_typing_extensions.py +++ b/typing_extensions/src_py2/test_typing_extensions.py @@ -119,46 +119,6 @@ def test_no_isinstance(self): issubclass(int, ClassVar) -class FinalTests(BaseTestCase): - - def test_basics(self): - with self.assertRaises(TypeError): - Final[1] - with self.assertRaises(TypeError): - Final[int, str] - with self.assertRaises(TypeError): - Final[int][str] - - def test_repr(self): - self.assertEqual(repr(Final), 'typing_extensions.Final') - cv = Final[int] - self.assertEqual(repr(cv), 'typing_extensions.Final[int]') - cv = Final[Employee] - self.assertEqual(repr(cv), 'typing_extensions.Final[%s.Employee]' % __name__) - - def test_cannot_subclass(self): - with self.assertRaises(TypeError): - class C(type(Final)): - pass - with self.assertRaises(TypeError): - class C(type(Final[int])): - pass - - def test_cannot_init(self): - with self.assertRaises(TypeError): - Final() - with self.assertRaises(TypeError): - type(Final)() - with self.assertRaises(TypeError): - type(Final[typing.Optional[int]])() - - def test_no_isinstance(self): - with self.assertRaises(TypeError): - isinstance(1, Final[int]) - with self.assertRaises(TypeError): - issubclass(int, Final) - - class IntVarTests(BaseTestCase): def test_valid(self): T_ints = IntVar("T_ints") @@ -172,65 +132,6 @@ def test_invalid(self): T_ints = IntVar("T_ints", covariant=True) -class LiteralTests(BaseTestCase): - def test_basics(self): - Literal[1] - Literal[1, 2, 3] - Literal["x", "y", "z"] - Literal[None] - - def test_illegal_parameters_do_not_raise_runtime_errors(self): - # Type checkers should reject these types, but we do not - # raise errors at runtime to maintain maximium flexibility - Literal[int] - Literal[Literal[1, 2], Literal[4, 5]] - Literal[3j + 2, ..., ()] - Literal[b"foo", u"bar"] - Literal[{"foo": 3, "bar": 4}] - Literal[T] - - def test_literals_inside_other_types(self): - typing.List[Literal[1, 2, 3]] - typing.List[Literal[("foo", "bar", "baz")]] - - def test_repr(self): - self.assertEqual(repr(Literal[1]), "typing_extensions.Literal[1]") - self.assertEqual(repr(Literal[1, True, "foo"]), "typing_extensions.Literal[1, True, 'foo']") - self.assertEqual(repr(Literal[int]), "typing_extensions.Literal[int]") - self.assertEqual(repr(Literal), "typing_extensions.Literal") - self.assertEqual(repr(Literal[None]), "typing_extensions.Literal[None]") - - def test_cannot_init(self): - with self.assertRaises(TypeError): - Literal() - with self.assertRaises(TypeError): - Literal[1]() - with self.assertRaises(TypeError): - type(Literal)() - with self.assertRaises(TypeError): - type(Literal[1])() - - def test_no_isinstance_or_issubclass(self): - with self.assertRaises(TypeError): - isinstance(1, Literal[1]) - with self.assertRaises(TypeError): - isinstance(int, Literal[1]) - with self.assertRaises(TypeError): - issubclass(1, Literal[1]) - with self.assertRaises(TypeError): - issubclass(int, Literal[1]) - - def test_no_subclassing(self): - with self.assertRaises(TypeError): - class Foo(Literal[1]): pass - with self.assertRaises(TypeError): - class Bar(Literal): pass - - def test_no_multiple_subscripts(self): - with self.assertRaises(TypeError): - Literal[1][1] - - class CollectionsAbcTests(BaseTestCase): def test_isinstance_collections(self): @@ -337,596 +238,6 @@ def blah(): blah() -class ProtocolTests(BaseTestCase): - - def test_basic_protocol(self): - @runtime - class P(Protocol): - def meth(self): - pass - class C(object): pass - class D(object): - 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 - class Empty(Protocol): pass - class C(object): 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): - @runtime - class Function(Protocol): - def __call__(self, *args, **kwargs): - pass - def f(): - pass - self.assertIsInstance(f, Function) - - def test_no_inheritance_from_nominal(self): - class C(object): pass - class BP(Protocol): pass - with self.assertRaises(TypeError): - class P(C, Protocol): - pass - with self.assertRaises(TypeError): - class P(Protocol, C): - pass - with self.assertRaises(TypeError): - class P(BP, C, Protocol): - pass - class D(BP, C): pass - class E(C, BP): pass - self.assertNotIsInstance(D(), E) - self.assertNotIsInstance(E(), D) - - def test_no_instantiation(self): - class P(Protocol): pass - with self.assertRaises(TypeError): - P() - class C(P): pass - self.assertIsInstance(C(), C) - T = typing.TypeVar('T') - class PG(Protocol[T]): pass - with self.assertRaises(TypeError): - PG() - with self.assertRaises(TypeError): - PG[int]() - with self.assertRaises(TypeError): - PG[T]() - class CG(PG[T]): pass - self.assertIsInstance(CG[int](), CG) - - def test_cannot_instantiate_abstract(self): - @runtime - class P(Protocol): - @abc.abstractmethod - def ameth(self): - raise NotImplementedError - class B(P): - pass - class C(B): - def ameth(self): - return 26 - with self.assertRaises(TypeError): - B() - self.assertIsInstance(C(), P) - - def test_subprotocols_extending(self): - class P1(Protocol): - def meth1(self): - pass - @runtime - class P2(P1, Protocol): - def meth2(self): - pass - class C(object): - def meth1(self): - pass - def meth2(self): - pass - class C1(object): - def meth1(self): - pass - class C2(object): - 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 - class P(P1, P2, Protocol): - pass - class C(object): - def meth1(self): - pass - def meth2(self): - pass - class C1(object): - def meth1(self): - pass - class C2(object): - 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 = typing.TypeVar('T') - @runtime - class P(Protocol): - def x(self): pass - @runtime - class PG(Protocol[T]): - def x(self): pass - class BadP(Protocol): - def x(self): pass - class BadPG(Protocol[T]): - def x(self): pass - class C(object): - def x(self): pass - self.assertIsSubclass(C, P) - self.assertIsSubclass(C, PG) - self.assertIsSubclass(BadP, PG) - self.assertIsSubclass(PG[int], PG) - self.assertIsSubclass(BadPG[int], P) - self.assertIsSubclass(BadPG[T], PG) - with self.assertRaises(TypeError): - issubclass(C, PG[T]) - with self.assertRaises(TypeError): - issubclass(C, PG[C]) - with self.assertRaises(TypeError): - issubclass(C, BadP) - with self.assertRaises(TypeError): - issubclass(C, BadPG) - with self.assertRaises(TypeError): - issubclass(P, PG[T]) - with self.assertRaises(TypeError): - issubclass(PG, PG[int]) - - def test_protocols_issubclass_non_callable(self): - class C(object): - x = 1 - @runtime - class PNonCall(Protocol): - x = 1 - with self.assertRaises(TypeError): - issubclass(C, PNonCall) - self.assertIsInstance(C(), PNonCall) - PNonCall.register(C) - with self.assertRaises(TypeError): - issubclass(C, PNonCall) - self.assertIsInstance(C(), PNonCall) - # check that non-protocol subclasses are not affected - class D(PNonCall): pass - self.assertNotIsSubclass(C, D) - self.assertNotIsInstance(C(), D) - D.register(C) - self.assertIsSubclass(C, D) - self.assertIsInstance(C(), D) - with self.assertRaises(TypeError): - issubclass(D, PNonCall) - - def test_protocols_isinstance(self): - T = typing.TypeVar('T') - @runtime - class P(Protocol): - def meth(x): pass - @runtime - class PG(Protocol[T]): - def meth(x): pass - class BadP(Protocol): - def meth(x): pass - class BadPG(Protocol[T]): - def meth(x): pass - class C(object): - def meth(x): pass - self.assertIsInstance(C(), P) - self.assertIsInstance(C(), PG) - with self.assertRaises(TypeError): - isinstance(C(), PG[T]) - with self.assertRaises(TypeError): - isinstance(C(), PG[C]) - with self.assertRaises(TypeError): - isinstance(C(), BadP) - with self.assertRaises(TypeError): - isinstance(C(), BadPG) - - def test_protocols_isinstance_init(self): - T = typing.TypeVar('T') - @runtime - class P(Protocol): - x = 1 - @runtime - class PG(Protocol[T]): - x = 1 - class C(object): - def __init__(self, x): - self.x = x - self.assertIsInstance(C(1), P) - self.assertIsInstance(C(1), PG) - - def test_protocols_support_register(self): - @runtime - class P(Protocol): - x = 1 - class PM(Protocol): - def meth(self): pass - class D(PM): pass - class C(object): 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 - class P(Protocol): - x = 1 - class A(object): - x = 1 - class B(A): - x = None - class C(object): - def __init__(self): - self.x = None - self.assertIsInstance(B(), P) - self.assertIsInstance(C(), P) - - def test_none_on_callable_blocks_implementation(self): - @runtime - class P(Protocol): - def x(self): pass - class A(object): - def x(self): pass - class B(A): - x = None - class C(object): - def __init__(self): - self.x = None - self.assertNotIsInstance(B(), P) - self.assertNotIsInstance(C(), P) - - def test_non_protocol_subclasses(self): - class P(Protocol): - x = 1 - @runtime - class PR(Protocol): - def meth(self): pass - class NonP(P): - x = 1 - class NonPR(PR): pass - class C(object): - x = 1 - class D(object): - def meth(self): pass - self.assertNotIsInstance(C(), NonP) - self.assertNotIsInstance(D(), NonPR) - self.assertNotIsSubclass(C, NonP) - self.assertNotIsSubclass(D, NonPR) - self.assertIsInstance(NonPR(), PR) - self.assertIsSubclass(NonPR, PR) - - def test_custom_subclasshook(self): - class P(Protocol): - x = 1 - class OKClass(object): pass - class BadClass(object): - 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) - - def test_issubclass_fails_correctly(self): - @runtime - class P(Protocol): - x = 1 - class C: pass - with self.assertRaises(TypeError): - issubclass(C(), P) - - def test_defining_generic_protocols(self): - T = typing.TypeVar('T') - S = typing.TypeVar('S') - @runtime - class PR(Protocol[T, S]): - def meth(self): pass - class P(PR[int, T], Protocol[T]): - y = 1 - self.assertIsSubclass(PR[int, T], PR) - self.assertIsSubclass(P[str], PR) - with self.assertRaises(TypeError): - PR[int] - with self.assertRaises(TypeError): - P[int, str] - 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) - - def test_defining_generic_protocols_old_style(self): - T = typing.TypeVar('T') - S = typing.TypeVar('S') - @runtime - class PR(Protocol, typing.Generic[T, S]): - def meth(self): pass - class P(PR[int, str], Protocol): - y = 1 - self.assertIsSubclass(PR[int, str], PR) - self.assertIsSubclass(P, PR) - with self.assertRaises(TypeError): - PR[int] - with self.assertRaises(TypeError): - PR[int, 1] - class P1(Protocol, typing.Generic[T]): - def bar(self, x): pass - class P2(typing.Generic[T], Protocol): - def bar(self, x): pass - @runtime - class PSub(P1[str], Protocol): - x = 1 - class Test(object): - x = 1 - def bar(self, x): - return x - self.assertIsInstance(Test(), PSub) - with self.assertRaises(TypeError): - PR[int, ClassVar] - - def test_init_called(self): - T = typing.TypeVar('T') - class P(Protocol[T]): pass - class C(P[T]): - def __init__(self): - self.test = 'OK' - self.assertEqual(C[int]().test, 'OK') - - def test_protocols_bad_subscripts(self): - T = typing.TypeVar('T') - S = typing.TypeVar('S') - with self.assertRaises(TypeError): - class P(Protocol[T, T]): pass - with self.assertRaises(TypeError): - class P(Protocol[int]): pass - with self.assertRaises(TypeError): - class P(Protocol[T], Protocol[S]): pass - with self.assertRaises(TypeError): - class P(Protocol[T], typing.Mapping[T, S]): pass - - def test_generic_protocols_repr(self): - T = typing.TypeVar('T') - S = typing.TypeVar('S') - class P(Protocol[T, S]): pass - self.assertTrue(repr(P).endswith('P')) - self.assertTrue(repr(P[T, S]).endswith('P[~T, ~S]')) - self.assertTrue(repr(P[int, str]).endswith('P[int, str]')) - - def test_generic_protocols_eq(self): - T = typing.TypeVar('T') - S = typing.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][typing.Tuple[T, S]][int, str], - P[typing.Tuple[int, str], typing.Tuple[int, str]]) - - def test_generic_protocols_special_from_generic(self): - T = typing.TypeVar('T') - class P(Protocol[T]): pass - self.assertEqual(P.__parameters__, (T,)) - self.assertIs(P.__args__, None) - self.assertIs(P.__origin__, None) - 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 - class PR(Protocol): - x = 1 - class P(Protocol): - def meth(self): - pass - T = typing.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) - with self.assertRaises(AttributeError): - self.assertFalse(P._is_runtime_protocol) - self.assertTrue(PR._is_runtime_protocol) - self.assertTrue(PG[int]._is_protocol) - self.assertEqual(P._get_protocol_attrs(), {'meth'}) - self.assertEqual(PR._get_protocol_attrs(), {'x'}) - self.assertEqual(frozenset(PG._get_protocol_attrs()), - frozenset({'x', 'meth'})) - self.assertEqual(frozenset(PG[int]._get_protocol_attrs()), - frozenset({'x', 'meth'})) - - def test_no_runtime_deco_on_nominal(self): - with self.assertRaises(TypeError): - @runtime - class C(object): pass - class Proto(Protocol): - x = 1 - with self.assertRaises(TypeError): - @runtime - class Concrete(Proto): - pass - - def test_none_treated_correctly(self): - @runtime - class P(Protocol): - x = None # type: int - class B(object): pass - self.assertNotIsInstance(B(), P) - class C(object): - x = 1 - class D(object): - x = None - self.assertIsInstance(C(), P) - self.assertIsInstance(D(), P) - class CI(object): - def __init__(self): - self.x = 1 - class DI(object): - def __init__(self): - self.x = None - self.assertIsInstance(C(), P) - self.assertIsInstance(D(), P) - - def test_protocols_in_unions(self): - class P(Protocol): - x = None # type: int - 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 = typing.TypeVar('T') - - @runtime - class P(Protocol[T]): - x = 1 - class CP(P[int]): - pass - - 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(object): - x = 1 - self.assertIsInstance(E(), D) - - -class TypedDictTests(BaseTestCase): - - def test_basics_iterable_syntax(self): - Emp = TypedDict('Emp', {'name': str, 'id': int}) - self.assertIsSubclass(Emp, dict) - self.assertIsSubclass(Emp, typing.MutableMapping) - if sys.version_info[0] >= 3: - import collections.abc - 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) - - def test_basics_keywords_syntax(self): - Emp = TypedDict('Emp', name=str, id=int) - self.assertIsSubclass(Emp, dict) - self.assertIsSubclass(Emp, typing.MutableMapping) - if sys.version_info[0] >= 3: - import collections.abc - 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) - - 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) - with self.assertRaises(TypeError): - TypedDict('Hi', x=1) - with self.assertRaises(TypeError): - TypedDict('Hi', [('x', int), ('y', 1)]) - with self.assertRaises(TypeError): - TypedDict('Hi', [('x', int)], y=int) - - 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) - - def test_optional(self): - EmpD = TypedDict('EmpD', name=str, id=int) - - self.assertEqual(typing.Optional[EmpD], typing.Union[None, EmpD]) - self.assertNotEqual(typing.List[EmpD], typing.Tuple[EmpD]) - - 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) - - class AnnotatedTests(BaseTestCase): def test_repr(self): diff --git a/typing_extensions/src_py2/typing_extensions.py b/typing_extensions/src_py2/typing_extensions.py index c7c2a66b..e5f53852 100644 --- a/typing_extensions/src_py2/typing_extensions.py +++ b/typing_extensions/src_py2/typing_extensions.py @@ -3,12 +3,12 @@ import typing from typing import ( ClassVar, Type, Generic, Callable, GenericMeta, TypingMeta, - Counter, DefaultDict, Deque, TypeVar, Tuple, - NewType, overload, Text, TYPE_CHECKING, + Counter, DefaultDict, Deque, TypeVar, Tuple, Final, final, + NewType, overload, Text, TYPE_CHECKING, Literal, TypedDict, Protocol, + runtime_checkable, # We use internal typing helpers here, but this significantly reduces # code duplication. (Also this is only until Protocol is in typing.) - _generic_new, _type_vars, _next_in_mro, _tp_cache, _type_check, - _TypingEllipsis, _TypingEmpty, _check_generic + _type_vars, _tp_cache, _type_check, ) # Please keep __all__ alphabetized within each category. @@ -18,6 +18,7 @@ 'Final', 'Protocol', 'Type', + 'TypedDict', # Concrete collection types. 'ContextManager', @@ -31,7 +32,7 @@ 'Literal', 'NewType', 'overload', - 'runtime', + 'runtime_checkable', 'Text', 'TYPE_CHECKING', ] @@ -100,516 +101,15 @@ def __subclasshook__(cls, C): return NotImplemented -def _gorg(cls): - """This function exists for compatibility with old typing versions.""" - assert isinstance(cls, GenericMeta) - if hasattr(cls, '_gorg'): - return cls._gorg - while cls.__origin__ is not None: - cls = cls.__origin__ - return cls - - -class _FinalMeta(TypingMeta): - """Metaclass for _Final""" - - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - self = super(_FinalMeta, cls).__new__(cls, name, bases, namespace) - return self - - -class _Final(typing._FinalTypingBase): - """A special typing construct to indicate that a name - cannot be re-assigned or overridden in a subclass. - For example: - - MAX_SIZE: Final = 9000 - MAX_SIZE += 1 # Error reported by type checker - - class Connection: - TIMEOUT: Final[int] = 10 - class FastConnector(Connection): - TIMEOUT = 1 # Error reported by type checker - - There is no runtime checking of these properties. - """ - - __metaclass__ = _FinalMeta - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(typing._type_check(item, - '{} accepts only single type.'.format(cls.__name__[1:])), - _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - new_tp = typing._eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super(_Final, self).__repr__() - if self.__type__ is not None: - r += '[{}]'.format(typing._type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _Final): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - -Final = _Final(_root=True) - - -def final(f): - """This decorator can be used to indicate to type checkers that - the decorated method cannot be overridden, and decorated class - cannot be subclassed. For example: - - class Base: - @final - def done(self) -> None: - ... - class Sub(Base): - def done(self) -> None: # Error reported by type checker - ... - @final - class Leaf: - ... - class Other(Leaf): # Error reported by type checker - ... - - There is no runtime checking of these properties. - """ - return f - - def IntVar(name): return TypeVar(name) -class _LiteralMeta(TypingMeta): - """Metaclass for _Literal""" - - def __new__(cls, name, bases, namespace): - cls.assert_no_subclassing(bases) - self = super(_LiteralMeta, cls).__new__(cls, name, bases, namespace) - return self - - -class _Literal(typing._FinalTypingBase): - """A type that can be used to indicate to type checkers that the - corresponding value has a value literally equivalent to the - provided parameter. For example: - - var: Literal[4] = 4 - - The type checker understands that 'var' is literally equal to the - value 4 and no other value. - - Literal[...] cannot be subclassed. There is no runtime checking - verifying that the parameter is actually a value instead of a type. - """ - - __metaclass__ = _LiteralMeta - __slots__ = ('__values__',) - - def __init__(self, values=None, **kwds): - self.__values__ = values - - def __getitem__(self, item): - cls = type(self) - if self.__values__ is None: - if not isinstance(item, tuple): - item = (item,) - return cls(values=item, - _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - return self - - def __repr__(self): - r = super(_Literal, self).__repr__() - if self.__values__ is not None: - r += '[{}]'.format(', '.join(map(typing._type_repr, self.__values__))) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__values__)) - - def __eq__(self, other): - if not isinstance(other, _Literal): - return NotImplemented - if self.__values__ is not None: - return self.__values__ == other.__values__ - return self is other - -Literal = _Literal(_root=True) - - -class _ProtocolMeta(GenericMeta): - """Internal metaclass for Protocol. - - This exists so Protocol classes can be generic without deriving - from Generic. - """ - - def __new__(cls, name, bases, namespace, - tvars=None, args=None, origin=None, extra=None, orig_bases=None): - # This is just a version copied from GenericMeta.__new__ that - # includes "Protocol" special treatment. (Comments removed for brevity.) - assert extra is None # Protocols should not have extra - if tvars is not None: - assert origin is not None - assert all(isinstance(t, TypeVar) for t in tvars), tvars - else: - tvars = _type_vars(bases) - gvars = None - for base in bases: - if base is Generic: - raise TypeError("Cannot inherit from plain Generic") - if (isinstance(base, GenericMeta) and - base.__origin__ in (Generic, Protocol)): - if gvars is not None: - raise TypeError( - "Cannot inherit from Generic[...] or" - " Protocol[...] multiple times.") - gvars = base.__parameters__ - if gvars is None: - gvars = tvars - else: - tvarset = set(tvars) - gvarset = set(gvars) - if not tvarset <= gvarset: - raise TypeError( - "Some type variables (%s) " - "are not listed in %s[%s]" % - (", ".join(str(t) for t in tvars if t not in gvarset), - "Generic" if any(b.__origin__ is Generic - for b in bases) else "Protocol", - ", ".join(str(g) for g in gvars))) - tvars = gvars - - initial_bases = bases - if extra is None: - extra = namespace.get('__extra__') - if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: - bases = (extra,) + bases - bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b for b in bases) - - if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): - bases = tuple(b for b in bases if b is not Generic) - namespace.update({'__origin__': origin, '__extra__': extra}) - self = abc.ABCMeta.__new__(cls, name, bases, namespace) - abc.ABCMeta.__setattr__(self, '_gorg', self if not origin else _gorg(origin)) - - self.__parameters__ = tvars - self.__args__ = tuple(Ellipsis if a is _TypingEllipsis else - () if a is _TypingEmpty else - a for a in args) if args else None - self.__next_in_mro__ = _next_in_mro(self) - if orig_bases is None: - self.__orig_bases__ = initial_bases - self.__tree_hash__ = (hash(self._subs_tree()) if origin else - abc.ABCMeta.__hash__(self)) - return self - - def __init__(cls, *args, **kwargs): - super(_ProtocolMeta, cls).__init__(*args, **kwargs) - if not cls.__dict__.get('_is_protocol', None): - cls._is_protocol = any(b is Protocol or - isinstance(b, _ProtocolMeta) and - b.__origin__ is Protocol - for b in cls.__bases__) - if cls._is_protocol: - for base in cls.__mro__[1:]: - if not (base in (object, Generic, Callable) or - isinstance(base, TypingMeta) and base._is_protocol or - isinstance(base, GenericMeta) and base.__origin__ is Generic): - raise TypeError('Protocols can only inherit from other protocols,' - ' got %r' % base) - cls._callable_members_only = all(callable(getattr(cls, attr)) - for attr in cls._get_protocol_attrs()) - - def _no_init(self, *args, **kwargs): - if type(self)._is_protocol: - raise TypeError('Protocols cannot be instantiated') - cls.__init__ = _no_init - - def _proto_hook(cls, other): - if not cls.__dict__.get('_is_protocol', None): - return NotImplemented - if not isinstance(other, type): - # Similar error as for issubclass(1, int) - # (also not a chance for old-style classes) - raise TypeError('issubclass() arg 1 must be a new-style class') - for attr in cls._get_protocol_attrs(): - for base in other.__mro__: - if attr in base.__dict__: - if base.__dict__[attr] is None: - return NotImplemented - break - else: - return NotImplemented - return True - if '__subclasshook__' not in cls.__dict__: - cls.__subclasshook__ = classmethod(_proto_hook) - - def __instancecheck__(self, instance): - # We need this method for situations where attributes are assigned in __init__ - if isinstance(instance, type): - # This looks like a fundamental limitation of Python 2. - # It cannot support runtime protocol metaclasses, On Python 2 classes - # cannot be correctly inspected as instances of protocols. - return False - if ((not getattr(self, '_is_protocol', False) or - self._callable_members_only) and - issubclass(instance.__class__, self)): - return True - if self._is_protocol: - if all(hasattr(instance, attr) and - (not callable(getattr(self, attr)) or - getattr(instance, attr) is not None) - for attr in self._get_protocol_attrs()): - return True - return super(GenericMeta, self).__instancecheck__(instance) - - def __subclasscheck__(self, cls): - if (self.__dict__.get('_is_protocol', None) and - not self.__dict__.get('_is_runtime_protocol', None)): - if sys._getframe(1).f_globals['__name__'] in ['abc', 'functools', 'typing']: - return False - raise TypeError("Instance and class checks can only be used with" - " @runtime protocols") - if (self.__dict__.get('_is_runtime_protocol', None) and - not self._callable_members_only): - if sys._getframe(1).f_globals['__name__'] in ['abc', 'functools', 'typing']: - return super(GenericMeta, self).__subclasscheck__(cls) - raise TypeError("Protocols with non-method members" - " don't support issubclass()") - return super(_ProtocolMeta, self).__subclasscheck__(cls) - - def _get_protocol_attrs(self): - attrs = set() - for base in self.__mro__[:-1]: # without object - if base.__name__ in ('Protocol', 'Generic'): - continue - annotations = getattr(base, '__annotations__', {}) - for attr in list(base.__dict__.keys()) + list(annotations.keys()): - if (not attr.startswith('_abc_') and attr not in ( - '__abstractmethods__', '__annotations__', '__weakref__', - '_is_protocol', '_is_runtime_protocol', '__dict__', - '__args__', '__slots__', '_get_protocol_attrs', - '__next_in_mro__', '__parameters__', '__origin__', - '__orig_bases__', '__extra__', '__tree_hash__', - '__doc__', '__subclasshook__', '__init__', '__new__', - '__module__', '_MutableMapping__marker', - '__metaclass__', '_gorg', '_callable_members_only')): - attrs.add(attr) - return attrs - - @_tp_cache - def __getitem__(self, params): - # We also need to copy this from GenericMeta.__getitem__ to get - # special treatment of "Protocol". (Comments removed for brevity.) - if not isinstance(params, tuple): - params = (params,) - if not params and _gorg(self) is not Tuple: - raise TypeError( - "Parameter list to %s[...] cannot be empty" % self.__qualname__) - msg = "Parameters to generic types must be types." - params = tuple(_type_check(p, msg) for p in params) - if self in (Generic, Protocol): - if not all(isinstance(p, TypeVar) for p in params): - raise TypeError( - "Parameters to %r[...] must all be type variables", self) - if len(set(params)) != len(params): - raise TypeError( - "Parameters to %r[...] must all be unique", self) - tvars = params - args = params - elif self in (Tuple, Callable): - tvars = _type_vars(params) - args = params - elif self.__origin__ in (Generic, Protocol): - raise TypeError("Cannot subscript already-subscripted %s" % - repr(self)) - else: - _check_generic(self, params) - tvars = _type_vars(params) - args = params - - prepend = (self,) if self.__origin__ is None else () - return self.__class__(self.__name__, - prepend + self.__bases__, - dict(self.__dict__), - tvars=tvars, - args=args, - origin=self, - extra=self.__extra__, - orig_bases=self.__orig_bases__) - - -class Protocol(object): - """Base class for protocol classes. Protocol classes are defined as:: - - class Proto(Protocol): - def meth(self): - # type: () -> int - pass - - Such classes are primarily used with static type checkers that recognize - structural subtyping (static duck-typing), for example:: - - class C: - def meth(self): - # type: () -> int - return 0 - - def func(x): - # type: (Proto) -> int - return x.meth() - - func(C()) # Passes static type check - - See PEP 544 for details. Protocol classes decorated with @typing_extensions.runtime - act as simple-minded runtime protocols that checks only the presence of - given attributes, ignoring their type signatures. - - Protocol classes can be generic, they are defined as:: - - class GenProto(Protocol[T]): - def meth(self): - # type: () -> T - pass - """ - - __metaclass__ = _ProtocolMeta - __slots__ = () - _is_protocol = True - - def __new__(cls, *args, **kwds): - if _gorg(cls) is Protocol: - raise TypeError("Type Protocol cannot be instantiated; " - "it can be used only as a base class") - return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) - - -def runtime(cls): - """Mark a protocol class as a runtime protocol, so that it - can be used with isinstance() and issubclass(). Raise TypeError - if applied to a non-protocol class. - - This allows a simple-minded structural check very similar to the - one-offs in collections.abc such as Hashable. - """ - if not isinstance(cls, _ProtocolMeta) or not cls._is_protocol: - raise TypeError('@runtime can be only applied to protocol classes,' - ' got %r' % cls) - cls._is_runtime_protocol = True - return cls - - -def _check_fails(cls, other): - try: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools', 'typing']: - # Typed dicts are only for static structural subtyping. - raise TypeError('TypedDict does not support instance and class checks') - except (AttributeError, ValueError): - pass - return False - - -def _dict_new(cls, *args, **kwargs): - return dict(*args, **kwargs) - - -def _typeddict_new(cls, _typename, _fields=None, **kwargs): - total = kwargs.pop('total', True) - if _fields is None: - _fields = kwargs - elif kwargs: - raise TypeError("TypedDict takes either a dict or keyword arguments," - " but not both") - - ns = {'__annotations__': dict(_fields), '__total__': total} - try: - # Setting correct module is necessary to make typed dict classes pickleable. - ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - pass - - return _TypedDictMeta(_typename, (), ns) - - -class _TypedDictMeta(type): - def __new__(cls, name, bases, ns, total=True): - # Create new typed dict class object. - # This method is called directly when TypedDict is subclassed, - # or via _typeddict_new when TypedDict is instantiated. This way - # TypedDict supports all three syntaxes described in its docstring. - # Subclasses and instances of TypedDict return actual dictionaries - # via _dict_new. - ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new - tp_dict = super(_TypedDictMeta, cls).__new__(cls, name, (dict,), ns) - - anns = ns.get('__annotations__', {}) - msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" - anns = {n: _type_check(tp, msg) for n, tp in anns.items()} - for base in bases: - anns.update(base.__dict__.get('__annotations__', {})) - tp_dict.__annotations__ = anns - if not hasattr(tp_dict, '__total__'): - tp_dict.__total__ = total - return tp_dict - - __instancecheck__ = __subclasscheck__ = _check_fails - - -TypedDict = _TypedDictMeta('TypedDict', (dict,), {}) -TypedDict.__module__ = __name__ -TypedDict.__doc__ = \ - """A simple typed name space. At runtime it is equivalent to a plain dict. - - TypedDict creates a dictionary type that expects all of its - instances to have a certain set of keys, with each key - associated with a value of a consistent type. This expectation - is not checked at runtime but is only enforced by type checkers. - Usage:: - - Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) - - a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK - b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check - - assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') - - The type info could be accessed via Point2D.__annotations__. TypedDict - supports an additional equivalent form:: - - Point2D = TypedDict('Point2D', x=int, y=int, label=str) - """ - def _is_dunder(name): """Returns True if name is a __dunder_variable_name__.""" return len(name) > 4 and name.startswith('__') and name.endswith('__') + class AnnotatedMeta(GenericMeta): """Metaclass for Annotated""" @@ -735,3 +235,7 @@ class Annotated(object): """ __metaclass__ = AnnotatedMeta __slots__ = () + + +# This alias exists for backwards compatibility. +runtime = runtime_checkable