]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | """Defines experimental extensions to the standard "typing" module that are |
2 | supported by the mypy typechecker. | |
3 | ||
4 | Example usage: | |
5 | from mypy_extensions import TypedDict | |
6 | """ | |
7 | ||
8 | from typing import Any | |
9 | ||
10 | import sys | |
11 | # _type_check is NOT a part of public typing API, it is used here only to mimic | |
12 | # the (convenient) behavior of types provided by typing module. | |
13 | from typing import _type_check # type: ignore | |
14 | ||
15 | ||
16 | def _check_fails(cls, other): | |
17 | try: | |
18 | if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools', 'typing']: | |
19 | # Typed dicts are only for static structural subtyping. | |
20 | raise TypeError('TypedDict does not support instance and class checks') | |
21 | except (AttributeError, ValueError): | |
22 | pass | |
23 | return False | |
24 | ||
25 | ||
26 | def _dict_new(cls, *args, **kwargs): | |
27 | return dict(*args, **kwargs) | |
28 | ||
29 | ||
30 | def _typeddict_new(cls, _typename, _fields=None, **kwargs): | |
31 | total = kwargs.pop('total', True) | |
32 | if _fields is None: | |
33 | _fields = kwargs | |
34 | elif kwargs: | |
35 | raise TypeError("TypedDict takes either a dict or keyword arguments," | |
36 | " but not both") | |
37 | ||
38 | ns = {'__annotations__': dict(_fields), '__total__': total} | |
39 | try: | |
40 | # Setting correct module is necessary to make typed dict classes pickleable. | |
41 | ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__') | |
42 | except (AttributeError, ValueError): | |
43 | pass | |
44 | ||
45 | return _TypedDictMeta(_typename, (), ns) | |
46 | ||
47 | ||
48 | class _TypedDictMeta(type): | |
49 | def __new__(cls, name, bases, ns, total=True): | |
50 | # Create new typed dict class object. | |
51 | # This method is called directly when TypedDict is subclassed, | |
52 | # or via _typeddict_new when TypedDict is instantiated. This way | |
53 | # TypedDict supports all three syntaxes described in its docstring. | |
54 | # Subclasses and instances of TypedDict return actual dictionaries | |
55 | # via _dict_new. | |
56 | ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new | |
57 | tp_dict = super(_TypedDictMeta, cls).__new__(cls, name, (dict,), ns) | |
58 | ||
59 | anns = ns.get('__annotations__', {}) | |
60 | msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" | |
61 | anns = {n: _type_check(tp, msg) for n, tp in anns.items()} | |
62 | for base in bases: | |
63 | anns.update(base.__dict__.get('__annotations__', {})) | |
64 | tp_dict.__annotations__ = anns | |
65 | if not hasattr(tp_dict, '__total__'): | |
66 | tp_dict.__total__ = total | |
67 | return tp_dict | |
68 | ||
69 | __instancecheck__ = __subclasscheck__ = _check_fails | |
70 | ||
71 | ||
72 | TypedDict = _TypedDictMeta('TypedDict', (dict,), {}) | |
73 | TypedDict.__module__ = __name__ | |
74 | TypedDict.__doc__ = \ | |
75 | """A simple typed name space. At runtime it is equivalent to a plain dict. | |
76 | ||
77 | TypedDict creates a dictionary type that expects all of its | |
78 | instances to have a certain set of keys, with each key | |
79 | associated with a value of a consistent type. This expectation | |
80 | is not checked at runtime but is only enforced by typecheckers. | |
81 | Usage:: | |
82 | ||
83 | Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) | |
84 | a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK | |
85 | b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check | |
86 | assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') | |
87 | ||
88 | The type info could be accessed via Point2D.__annotations__. TypedDict | |
89 | supports two additional equivalent forms:: | |
90 | ||
91 | Point2D = TypedDict('Point2D', x=int, y=int, label=str) | |
92 | ||
93 | class Point2D(TypedDict): | |
94 | x: int | |
95 | y: int | |
96 | label: str | |
97 | ||
98 | The latter syntax is only supported in Python 3.6+, while two other | |
99 | syntax forms work for 3.2+ | |
100 | """ | |
101 | ||
102 | # Argument constructors for making more-detailed Callables. These all just | |
103 | # return their type argument, to make them complete noops in terms of the | |
104 | # `typing` module. | |
105 | ||
106 | ||
107 | def Arg(type=Any, name=None): | |
108 | """A normal positional argument""" | |
109 | return type | |
110 | ||
111 | ||
112 | def DefaultArg(type=Any, name=None): | |
113 | """A positional argument with a default value""" | |
114 | return type | |
115 | ||
116 | ||
117 | def NamedArg(type=Any, name=None): | |
118 | """A keyword-only argument""" | |
119 | return type | |
120 | ||
121 | ||
122 | def DefaultNamedArg(type=Any, name=None): | |
123 | """A keyword-only argument with a default value""" | |
124 | return type | |
125 | ||
126 | ||
127 | def VarArg(type=Any): | |
128 | """A *args-style variadic positional argument""" | |
129 | return type | |
130 | ||
131 | ||
132 | def KwArg(type=Any): | |
133 | """A **kwargs-style variadic keyword argument""" | |
134 | return type | |
135 | ||
136 | ||
137 | # Return type that indicates a function does not return | |
138 | class NoReturn: pass | |
139 | ||
140 | ||
141 | def trait(cls): | |
142 | return cls | |
143 | ||
144 | ||
145 | def mypyc_attr(*attrs, **kwattrs): | |
146 | return lambda x: x | |
147 | ||
148 | ||
149 | # TODO: We may want to try to properly apply this to any type | |
150 | # variables left over... | |
151 | class _FlexibleAliasClsApplied: | |
152 | def __init__(self, val): | |
153 | self.val = val | |
154 | ||
155 | def __getitem__(self, args): | |
156 | return self.val | |
157 | ||
158 | ||
159 | class _FlexibleAliasCls: | |
160 | def __getitem__(self, args): | |
161 | return _FlexibleAliasClsApplied(args[-1]) | |
162 | ||
163 | ||
164 | FlexibleAlias = _FlexibleAliasCls() | |
165 | ||
166 | ||
167 | class _NativeIntMeta(type): | |
168 | def __instancecheck__(cls, inst): | |
169 | return isinstance(inst, int) | |
170 | ||
171 | ||
172 | _sentinel = object() | |
173 | ||
174 | ||
175 | class i64(metaclass=_NativeIntMeta): | |
176 | def __new__(cls, x=0, base=_sentinel): | |
177 | if base is not _sentinel: | |
178 | return int(x, base) | |
179 | return int(x) | |
180 | ||
181 | ||
182 | class i32(metaclass=_NativeIntMeta): | |
183 | def __new__(cls, x=0, base=_sentinel): | |
184 | if base is not _sentinel: | |
185 | return int(x, base) | |
186 | return int(x) | |
187 | ||
188 | ||
189 | class i16(metaclass=_NativeIntMeta): | |
190 | def __new__(cls, x=0, base=_sentinel): | |
191 | if base is not _sentinel: | |
192 | return int(x, base) | |
193 | return int(x) | |
194 | ||
195 | ||
196 | class u8(metaclass=_NativeIntMeta): | |
197 | def __new__(cls, x=0, base=_sentinel): | |
198 | if base is not _sentinel: | |
199 | return int(x, base) | |
200 | return int(x) | |
201 | ||
202 | ||
203 | for _int_type in i64, i32, i16, u8: | |
204 | _int_type.__doc__ = \ | |
205 | """A native fixed-width integer type when used with mypyc. | |
206 | ||
207 | In code not compiled with mypyc, behaves like the 'int' type in these | |
208 | runtime contexts: | |
209 | ||
210 | * {name}(x[, base=n]) converts a number or string to 'int' | |
211 | * isinstance(x, {name}) is the same as isinstance(x, int) | |
212 | """.format(name=_int_type.__name__) | |
213 | del _int_type |