Codebase list python-typing-inspect / 26e41ea
import python-typing-inspect_0.6.0.orig.tar.gz Louis-Philippe VĂ©ronneau 3 years ago
13 changed file(s) with 1414 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 [flake8]
1 max-line-length = 90
2 ignore =
3 B3,
4 DW12,
5 W504
6 exclude =
7 test_typing_inspect.py
0 [flake8]
1 builtins = basestring, unicode
2 max-line-length = 110
3 ignore =
4 E306,
5 E701,
6 E704,
7 F811,
8 B3,
9 DW12
10 exclude =
11 typing_inspect.py
0 MANIFEST
1 build/
2 dist/
3 .tox/
4 .idea/
5 .cache/
6 __pycache__/
7 tmp/
8 *.swp
9 *.pyc
10 venv/
0 sudo: false
1 language: python
2
3 matrix:
4 fast_finish: true
5 include:
6 - python: 3.8
7 - python: 3.7
8 - python: 3.6
9 - python: 3.5
10 - python: 3.5.3
11 - python: 3.5.2
12 - python: 3.5.1
13 dist: trusty
14 sudo: true
15 - python: 3.5.0
16 dist: trusty
17 sudo: true
18 - python: 3.4
19 - python: 2.7
20
21 install:
22 - pip install -r test-requirements.txt
23
24 script:
25 - py.test
26 - if [[ $TRAVIS_PYTHON_VERSION == '3.6' ]]; then flake8 --config=.flake8 typing_inspect.py; fi
27 - if [[ $TRAVIS_PYTHON_VERSION == '3.6' ]]; then flake8 --config=.flake8-tests test_typing_inspect.py; fi
0 The MIT License (MIT)
1
2 Copyright (c) 2017-2019 Ivan Levkivskyi
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy of
5 this software and associated documentation files (the "Software"), to deal in
6 the Software without restriction, including without limitation the rights to
7 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8 the Software, and to permit persons to whom the Software is furnished to do so,
9 subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16 FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
0 include LICENSE
1 include README.md
2 include setup.py
3 include typing_inspect.py
4 include test_typing_inspect.py
0 Typing Inspect
1 ==============
2
3 [![Build Status](https://travis-ci.org/ilevkivskyi/typing_inspect.svg)](https://travis-ci.org/ilevkivskyi/typing_inspect)
4
5 The ``typing_inspect`` module defines experimental API for runtime
6 inspection of types defined in the Python standard ``typing`` module.
7 Works with ``typing`` version ``3.7.4`` and later. Example usage:
8
9 ```python
10 from typing import Generic, TypeVar, Iterable, Mapping, Union
11 from typing_inspect import is_generic_type
12
13 T = TypeVar('T')
14
15 class MyCollection(Generic[T]):
16 content: T
17
18 assert is_generic_type(Mapping)
19 assert is_generic_type(Iterable[int])
20 assert is_generic_type(MyCollection[T])
21
22 assert not is_generic_type(int)
23 assert not is_generic_type(Union[int, T])
24 ```
25
26 **Note**: The API is still experimental, if you have ideas/suggestions please
27 open an issue on [tracker](https://github.com/ilevkivskyi/typing_inspect/issues).
28 Currently ``typing_inspect`` only supports latest version of ``typing``. This
29 limitation may be lifted if such requests will appear frequently.
30
31 Currently provided functions (see functions docstrings for examples of usage):
32 * ``is_generic_type(tp)``:
33 Test if ``tp`` is a generic type. This includes ``Generic`` itself,
34 but excludes special typing constructs such as ``Union``, ``Tuple``,
35 ``Callable``, ``ClassVar``.
36 * ``is_callable_type(tp)``:
37 Test ``tp`` is a generic callable type, including subclasses
38 excluding non-generic types and callables.
39 * ``is_tuple_type(tp)``:
40 Test if ``tp`` is a generic tuple type, including subclasses excluding
41 non-generic classes.
42 * ``is_union_type(tp)``:
43 Test if ``tp`` is a union type.
44 * ``is_optional_type(tp)``:
45 Test if ``tp`` is an optional type (either ``type(None)`` or a direct union to it such as in ``Optional[int]``). Nesting and ``TypeVar``s are not unfolded/inspected in this process.
46 * ``is_typevar(tp)``:
47 Test if ``tp`` represents a type variable.
48 * ``is_new_type(tp)``:
49 Test if ``tp`` represents a distinct type.
50 * ``is_classvar(tp)``:
51 Test if ``tp`` represents a class variable.
52 * ``get_origin(tp)``:
53 Get the unsubscripted version of ``tp``. Supports generic types, ``Union``,
54 ``Callable``, and ``Tuple``. Returns ``None`` for unsupported types.
55 * ``get_last_origin(tp)``:
56 Get the last base of (multiply) subscripted type ``tp``. Supports generic
57 types, ``Union``, ``Callable``, and ``Tuple``. Returns ``None`` for
58 unsupported types.
59 * ``get_parameters(tp)``:
60 Return type parameters of a parameterizable type ``tp`` as a tuple
61 in lexicographic order. Parameterizable types are generic types,
62 unions, tuple types and callable types.
63 * ``get_args(tp, evaluate=False)``:
64 Get type arguments of ``tp`` with all substitutions performed. For unions,
65 basic simplifications used by ``Union`` constructor are performed.
66 If ``evaluate`` is ``False`` (default), report result as nested tuple,
67 this matches the internal representation of types. If ``evaluate`` is
68 ``True``, then all type parameters are applied (this could be time and
69 memory expensive).
70 * ``get_last_args(tp)``:
71 Get last arguments of (multiply) subscripted type ``tp``.
72 Parameters for ``Callable`` are flattened.
73 * ``get_generic_type(obj)``:
74 Get the generic type of ``obj`` if possible, or its runtime class otherwise.
75 * ``get_generic_bases(tp)``:
76 Get generic base types of ``tp`` or empty tuple if not possible.
77 * ``typed_dict_keys(td)``:
78 Get ``TypedDict`` keys and their types, or None if ``td`` is not a typed dict.
0 typing >= 3.7.4
1 mypy_extensions >= 0.3.0
2 typing_extensions >= 3.7.4
0 #!/usr/bin/env python
1
2 # NOTE: This package must support Python 2.7 in addition to Python 3.x
3
4 import sys
5 from setuptools import setup
6
7 version = '0.6.0'
8 description = 'Runtime inspection utilities for typing module.'
9 long_description = '''
10 Typing Inspect
11 ==============
12
13 The "typing_inspect" module defines experimental API for runtime
14 inspection of types defined in the standard "typing" module.
15 '''.lstrip()
16
17 classifiers = [
18 'Development Status :: 3 - Alpha',
19 'Environment :: Console',
20 'Intended Audience :: Developers',
21 'License :: OSI Approved :: MIT License',
22 'Operating System :: OS Independent',
23 'Programming Language :: Python :: 2',
24 'Programming Language :: Python :: 2.7',
25 'Programming Language :: Python :: 3',
26 'Programming Language :: Python :: 3.3',
27 'Programming Language :: Python :: 3.4',
28 'Programming Language :: Python :: 3.5',
29 'Programming Language :: Python :: 3.6',
30 'Programming Language :: Python :: 3.7',
31 'Topic :: Software Development',
32 ]
33
34 install_requires = [
35 'mypy_extensions >= 0.3.0',
36 'typing >= 3.7.4;python_version<"3.5"',
37 'typing_extensions >= 3.7.4',
38 ]
39
40 setup(
41 name='typing_inspect',
42 version=version,
43 description=description,
44 long_description=long_description,
45 author='Ivan Levkivskyi',
46 author_email='levkivskyi@gmail.com',
47 url='https://github.com/ilevkivskyi/typing_inspect',
48 license='MIT',
49 keywords='typing function annotations type hints hinting checking '
50 'checker typehints typehinting typechecking inspect '
51 'reflection introspection',
52 py_modules=['typing_inspect'],
53 classifiers=classifiers,
54 install_requires=install_requires,
55 )
0 flake8; python_version >= '3.6'
1 flake8-bugbear; python_version >= '3.6'
2 pytest>=2.8; python_version >= '3.3'
3 typing >= 3.7.4
4 mypy_extensions >= 0.3.0
5 typing_extensions >= 3.7.4
0 import sys
1 from typing_inspect import (
2 is_generic_type, is_callable_type, is_new_type, is_tuple_type, is_union_type,
3 is_optional_type, is_literal_type, is_typevar, is_classvar, is_forward_ref,
4 get_origin, get_parameters, get_last_args, get_args, get_bound, get_constraints,
5 get_generic_type, get_generic_bases, get_last_origin, typed_dict_keys,
6 get_forward_arg, WITH_LITERAL, LEGACY_TYPING)
7 from unittest import TestCase, main, skipIf, skipUnless
8 from typing import (
9 Union, Callable, Optional, TypeVar, Sequence, AnyStr, Mapping,
10 MutableMapping, Iterable, Generic, List, Any, Dict, Tuple, NamedTuple,
11 )
12
13 from mypy_extensions import TypedDict
14 from typing_extensions import Literal
15
16 # Does this raise an exception ?
17 # from typing import NewType
18 if sys.version_info < (3, 5, 2):
19 WITH_NEWTYPE = False
20 else:
21 from typing import NewType
22 WITH_NEWTYPE = True
23
24
25 # Does this raise an exception ?
26 # from typing import ClassVar
27 if sys.version_info < (3, 5, 3):
28 WITH_CLASSVAR = False
29 CLASSVAR_GENERIC = []
30 CLASSVAR_TYPEVAR = []
31 else:
32 from typing import ClassVar
33 WITH_CLASSVAR = True
34 CLASSVAR_GENERIC = [ClassVar[List[int]], ClassVar]
35 CLASSVAR_TYPEVAR = [ClassVar[int]]
36
37
38 # Does this raise an exception ?
39 # class Foo(Callable[[int], int]):
40 # pass
41 if sys.version_info < (3, 5, 3):
42 SUBCLASSABLE_CALLABLES = False
43 else:
44 SUBCLASSABLE_CALLABLES = True
45
46
47 # Does this raise an exception ?
48 # class MyClass(Tuple[str, int]):
49 # pass
50 if sys.version_info < (3, 5, 3):
51 SUBCLASSABLE_TUPLES = False
52 else:
53 SUBCLASSABLE_TUPLES = True
54
55
56 # Does this raise an exception ?
57 # T = TypeVar('T')
58 # Union[T, str][int]
59 if sys.version_info < (3, 5, 3):
60 EXISTING_UNIONS_SUBSCRIPTABLE = False
61 else:
62 EXISTING_UNIONS_SUBSCRIPTABLE = True
63
64
65 # Does this raise an exception ?
66 # Union[callable, Callable[..., int]]
67 if sys.version_info[:3] == (3, 5, 3) or sys.version_info[:3] < (3, 5, 2):
68 UNION_SUPPORTS_BUILTIN_CALLABLE = False
69 else:
70 UNION_SUPPORTS_BUILTIN_CALLABLE = True
71
72
73 # Does this raise an exception ?
74 # Tuple[T][int]
75 # List[Tuple[T]][int]
76 if sys.version_info[:3] == (3, 5, 3) or sys.version_info[:3] < (3, 5, 2):
77 GENERIC_TUPLE_PARAMETRIZABLE = False
78 else:
79 GENERIC_TUPLE_PARAMETRIZABLE = True
80
81
82 # Does this raise an exception ?
83 # Dict[T, T][int]
84 # Dict[int, Tuple[T, T]][int]
85 if sys.version_info[:3] == (3, 5, 3) or sys.version_info[:3] < (3, 5, 2):
86 GENERIC_WITH_MULTIPLE_IDENTICAL_PARAMETERS_CAN_BE_PARAMETRIZED_ONCE = False
87 else:
88 GENERIC_WITH_MULTIPLE_IDENTICAL_PARAMETERS_CAN_BE_PARAMETRIZED_ONCE = True
89
90
91 # Does this raise an exception ?
92 # Callable[[T], int][int]
93 # Callable[[], T][int]
94 if sys.version_info[:3] < (3, 5, 4):
95 CALLABLE_CAN_BE_PARAMETRIZED = False
96 else:
97 CALLABLE_CAN_BE_PARAMETRIZED = True
98
99
100 NEW_TYPING = sys.version_info[:3] >= (3, 7, 0) # PEP 560
101
102 PY36_TESTS = """
103 class TD(TypedDict):
104 x: int
105 y: int
106 class Other(dict):
107 x: int
108 y: int
109 """
110
111 PY36 = sys.version_info[:3] >= (3, 6, 0)
112 if PY36:
113 exec(PY36_TESTS)
114 else:
115 TD = Other = object # for linters
116
117
118 class IsUtilityTestCase(TestCase):
119 def sample_test(self, fun, samples, nonsamples):
120 msg = "Error asserting that %s(%s) is %s"
121 for s in samples:
122 self.assertTrue(fun(s), msg=msg % (fun.__name__, str(s), 'True'))
123 for s in nonsamples:
124 self.assertFalse(fun(s), msg=msg % (fun.__name__, str(s), 'False'))
125
126 def test_generic(self):
127 T = TypeVar('T')
128 samples = [Generic, Generic[T], Iterable[int], Mapping,
129 MutableMapping[T, List[int]], Sequence[Union[str, bytes]]]
130 nonsamples = [int, Union[int, str], Union[int, T], Callable[..., T],
131 Optional, bytes, list] + CLASSVAR_GENERIC
132 self.sample_test(is_generic_type, samples, nonsamples)
133
134 def test_callable(self):
135 samples = [Callable, Callable[..., int],
136 Callable[[int, int], Iterable[str]]]
137 nonsamples = [int, type, 42, [], List[int]]
138 if UNION_SUPPORTS_BUILTIN_CALLABLE:
139 nonsamples.append(Union[callable, Callable[..., int]])
140 self.sample_test(is_callable_type, samples, nonsamples)
141 if SUBCLASSABLE_CALLABLES:
142 class MyClass(Callable[[int], int]):
143 pass
144 self.assertTrue(is_callable_type(MyClass))
145
146 def test_tuple(self):
147 samples = [Tuple, Tuple[str, int], Tuple[Iterable, ...]]
148 nonsamples = [int, tuple, 42, List[int], NamedTuple('N', [('x', int)])]
149 self.sample_test(is_tuple_type, samples, nonsamples)
150 if SUBCLASSABLE_TUPLES:
151 class MyClass(Tuple[str, int]):
152 pass
153 self.assertTrue(is_tuple_type(MyClass))
154
155 def test_union(self):
156 T = TypeVar('T')
157 S = TypeVar('S')
158 samples = [Union, Union[T, int], Union[int, Union[T, S]]]
159 nonsamples = [int, Union[int, int], [], Iterable[Any]]
160 self.sample_test(is_union_type, samples, nonsamples)
161
162 def test_optional_type(self):
163 T = TypeVar('T')
164 samples = [type(None), # none type
165 Optional[int], # direct union to none type 1
166 Optional[T], # direct union to none type 2
167 Union[int, type(None)], # direct union to none type 4
168 ]
169 if EXISTING_UNIONS_SUBSCRIPTABLE:
170 samples += [Optional[T][int], # direct union to none type 3
171 Union[str, T][type(None)] # direct union to none type 5
172 ]
173
174 # nested unions are supported
175 samples += [Union[str, Optional[int]]] # nested Union 1
176 if EXISTING_UNIONS_SUBSCRIPTABLE:
177 samples += [Union[T, str][Optional[int]]] # nested Union 2
178
179 nonsamples = [int, Union[int, int], [], Iterable[Any], T]
180 if EXISTING_UNIONS_SUBSCRIPTABLE:
181 nonsamples += [Union[T, str][int]]
182
183 # unfortunately current definition sets these ones as non samples too
184 S1 = TypeVar('S1', bound=Optional[int])
185 S2 = TypeVar('S2', type(None), str)
186 S3 = TypeVar('S3', Optional[int], str)
187 S4 = TypeVar('S4', bound=Union[str, Optional[int]])
188 nonsamples += [S1, S2, S3, # typevar bound or constrained to optional
189 Union[S1, int], S4 # combinations of the above
190 ]
191 self.sample_test(is_optional_type, samples, nonsamples)
192
193 @skipIf(not WITH_LITERAL, "Literal is not available")
194 def test_literal_type(self):
195 samples = [
196 Literal,
197 Literal["v"],
198 Literal[1, 2, 3],
199 ]
200 nonsamples = [
201 "v",
202 (1, 2, 3),
203 int,
204 str,
205 Union["u", "v"],
206 ]
207 self.sample_test(is_literal_type, samples, nonsamples)
208
209 def test_typevar(self):
210 T = TypeVar('T')
211 S_co = TypeVar('S_co', covariant=True)
212 samples = [T, S_co]
213 nonsamples = [int, Union[T, int], Union[T, S_co], type] + CLASSVAR_TYPEVAR
214 self.sample_test(is_typevar, samples, nonsamples)
215
216 @skipIf(not WITH_CLASSVAR, "ClassVar is not present")
217 def test_classvar(self):
218 T = TypeVar('T')
219 samples = [ClassVar, ClassVar[int], ClassVar[List[T]]]
220 nonsamples = [int, 42, Iterable, List[int], type, T]
221 self.sample_test(is_classvar, samples, nonsamples)
222
223 @skipIf(not WITH_NEWTYPE, "NewType is not present")
224 def test_new_type(self):
225 T = TypeVar('T')
226 samples = [
227 NewType('A', int),
228 NewType('B', complex),
229 NewType('C', List[int]),
230 NewType('D', Union['p', 'y', 't', 'h', 'o', 'n']),
231 NewType('E', List[Dict[str, float]]),
232 NewType('F', NewType('F_', int)),
233 ]
234 nonsamples = [
235 int,
236 42,
237 Iterable,
238 List[int],
239 Union["u", "v"],
240 type,
241 T,
242 ]
243 self.sample_test(is_new_type, samples, nonsamples)
244
245 def test_is_forward_ref(self):
246 samples = []
247 nonsamples = []
248 for tp in (
249 Union["FowardReference", Dict[str, List[int]]],
250 Union["FR", List["FR"]],
251 Optional["Fref"],
252 Union["fRef", int],
253 Union["fR", AnyStr],
254 ):
255 fr, not_fr = get_args(tp)
256 samples.append(fr)
257 nonsamples.append(not_fr)
258 self.sample_test(is_forward_ref, samples, nonsamples)
259
260
261 class GetUtilityTestCase(TestCase):
262
263 @skipIf(NEW_TYPING, "Not supported in Python 3.7")
264 def test_last_origin(self):
265 T = TypeVar('T')
266 self.assertEqual(get_last_origin(int), None)
267 if WITH_CLASSVAR:
268 self.assertEqual(get_last_origin(ClassVar[int]), None)
269 self.assertEqual(get_last_origin(Generic[T]), Generic)
270 if EXISTING_UNIONS_SUBSCRIPTABLE:
271 self.assertEqual(get_last_origin(Union[T, int][str]), Union[T, int])
272 if GENERIC_TUPLE_PARAMETRIZABLE:
273 tp = List[Tuple[T, T]][int]
274 self.assertEqual(get_last_origin(tp), List[Tuple[T, T]])
275 self.assertEqual(get_last_origin(List), List)
276
277 def test_origin(self):
278 T = TypeVar('T')
279 self.assertEqual(get_origin(int), None)
280 if WITH_CLASSVAR:
281 self.assertEqual(get_origin(ClassVar[int]), None)
282 self.assertEqual(get_origin(Generic), Generic)
283 self.assertEqual(get_origin(Generic[T]), Generic)
284 if GENERIC_TUPLE_PARAMETRIZABLE:
285 tp = List[Tuple[T, T]][int]
286 self.assertEqual(get_origin(tp), list if NEW_TYPING else List)
287
288 def test_parameters(self):
289 T = TypeVar('T')
290 S_co = TypeVar('S_co', covariant=True)
291 U = TypeVar('U')
292 self.assertEqual(get_parameters(int), ())
293 self.assertEqual(get_parameters(Generic), ())
294 self.assertEqual(get_parameters(Union), ())
295 if not LEGACY_TYPING:
296 self.assertEqual(get_parameters(List[int]), ())
297 else:
298 # in 3.5.3 a list has no __args__ and instead they are used in __parameters__
299 # in 3.5.1 the behaviour is normal again.
300 pass
301 self.assertEqual(get_parameters(Generic[T]), (T,))
302 self.assertEqual(get_parameters(Tuple[List[T], List[S_co]]), (T, S_co))
303 if EXISTING_UNIONS_SUBSCRIPTABLE:
304 self.assertEqual(get_parameters(Union[S_co, Tuple[T, T]][int, U]), (U,))
305 self.assertEqual(get_parameters(Mapping[T, Tuple[S_co, T]]), (T, S_co))
306
307 @skipIf(NEW_TYPING, "Not supported in Python 3.7")
308 def test_last_args(self):
309 T = TypeVar('T')
310 S = TypeVar('S')
311 self.assertEqual(get_last_args(int), ())
312 self.assertEqual(get_last_args(Union), ())
313 if WITH_CLASSVAR:
314 self.assertEqual(get_last_args(ClassVar[int]), (int,))
315 self.assertEqual(get_last_args(Union[T, int]), (T, int))
316 self.assertEqual(get_last_args(Union[str, int]), (str, int))
317 self.assertEqual(get_last_args(Tuple[T, int]), (T, int))
318 self.assertEqual(get_last_args(Tuple[str, int]), (str, int))
319 self.assertEqual(get_last_args(Generic[T]), (T, ))
320 if GENERIC_TUPLE_PARAMETRIZABLE:
321 tp = Iterable[Tuple[T, S]][int, T]
322 self.assertEqual(get_last_args(tp), (int, T))
323 if LEGACY_TYPING:
324 self.assertEqual(get_last_args(Callable[[T, S], int]), (T, S))
325 self.assertEqual(get_last_args(Callable[[], int]), ())
326 else:
327 self.assertEqual(get_last_args(Callable[[T, S], int]), (T, S, int))
328 self.assertEqual(get_last_args(Callable[[], int]), (int,))
329
330 @skipIf(NEW_TYPING, "Not supported in Python 3.7")
331 def test_args(self):
332 if EXISTING_UNIONS_SUBSCRIPTABLE:
333 T = TypeVar('T')
334 self.assertEqual(get_args(Union[int, Tuple[T, int]][str]),
335 (int, (Tuple, str, int)))
336 self.assertEqual(get_args(Union[int, Union[T, int], str][int]),
337 (int, str))
338 self.assertEqual(get_args(int), ())
339
340 def test_args_evaluated(self):
341 T = TypeVar('T')
342 if EXISTING_UNIONS_SUBSCRIPTABLE:
343 self.assertEqual(get_args(Union[int, Tuple[T, int]][str], evaluate=True),
344 (int, Tuple[str, int]))
345 if GENERIC_WITH_MULTIPLE_IDENTICAL_PARAMETERS_CAN_BE_PARAMETRIZED_ONCE:
346 tp = Dict[int, Tuple[T, T]][Optional[int]]
347 self.assertEqual(get_args(tp, evaluate=True),
348 (int, Tuple[Optional[int], Optional[int]]))
349 if CALLABLE_CAN_BE_PARAMETRIZED:
350 tp = Callable[[], T][int]
351 self.assertEqual(get_args(tp, evaluate=True), ([], int,))
352
353 self.assertEqual(get_args(Union[int, Callable[[Tuple[T, ...]], str]], evaluate=True),
354 (int, Callable[[Tuple[T, ...]], str]))
355
356 # ClassVar special-casing
357 if WITH_CLASSVAR:
358 self.assertEqual(get_args(ClassVar, evaluate=True), ())
359 self.assertEqual(get_args(ClassVar[int], evaluate=True), (int,))
360
361 # Literal special-casing
362 if WITH_LITERAL:
363 self.assertEqual(get_args(Literal, evaluate=True), ())
364 self.assertEqual(get_args(Literal["value"], evaluate=True), ("value",))
365 self.assertEqual(get_args(Literal[1, 2, 3], evaluate=True), (1, 2, 3))
366
367 def test_bound(self):
368 T = TypeVar('T')
369 TB = TypeVar('TB', bound=int)
370 self.assertEqual(get_bound(T), None)
371 self.assertEqual(get_bound(TB), int)
372
373 def test_constraints(self):
374 T = TypeVar('T')
375 TC = TypeVar('TC', int, str)
376 self.assertEqual(get_constraints(T), ())
377 self.assertEqual(get_constraints(TC), (int, str))
378
379 def test_generic_type(self):
380 T = TypeVar('T')
381 class Node(Generic[T]): pass
382 self.assertIs(get_generic_type(Node()), Node)
383 if not LEGACY_TYPING:
384 self.assertIs(get_generic_type(Node[int]()), Node[int])
385 self.assertIs(get_generic_type(Node[T]()), Node[T],)
386 else:
387 # Node[int]() was creating an object of NEW type Node[~T]
388 # and Node[T]() was creating an object of NEW type Node[~T]
389 pass
390 self.assertIs(get_generic_type(1), int)
391
392 def test_generic_bases(self):
393 class MyClass(List[int], Mapping[str, List[int]]): pass
394 self.assertEqual(get_generic_bases(MyClass),
395 (List[int], Mapping[str, List[int]]))
396 self.assertEqual(get_generic_bases(int), ())
397
398 @skipUnless(PY36, "Python 3.6 required")
399 def test_typed_dict(self):
400 TDOld = TypedDict("TDOld", {'x': int, 'y': int})
401 self.assertEqual(typed_dict_keys(TD), {'x': int, 'y': int})
402 self.assertEqual(typed_dict_keys(TDOld), {'x': int, 'y': int})
403 self.assertIs(typed_dict_keys(dict), None)
404 self.assertIs(typed_dict_keys(Other), None)
405 self.assertIsNot(typed_dict_keys(TD), TD.__annotations__)
406
407 @skipIf(
408 (3, 5, 2) > sys.version_info[:3] >= (3, 5, 0),
409 "get_args doesn't work in Python 3.5.0 and 3.5.1 for type"
410 " List and ForwardRef arg"
411 )
412 def test_get_forward_arg(self):
413 tp = List["FRef"]
414 fr = get_args(tp)[0]
415 self.assertEqual(get_forward_arg(fr), "FRef")
416 self.assertEqual(get_forward_arg(tp), None)
417
418
419 if __name__ == '__main__':
420 main()
0 [tox]
1 envlist = py27, py32, py33, py34
2
3 [testenv]
4 deps = typing
5 commands = python -m unittest discover
6
7 [flake8]
8 # fake builtins for python2/*
9 builtins = basestring, unicode
10 max-line-length = 90
11 ignore =
12 # irrelevant plugins
13 B3,
14 DW12
15 exclude =
16 # tests have more relaxed formatting rules
17 # and its own specific config in .flake8-tests
18 test_typing_inspect.py
0 """Defines experimental API for runtime inspection of types defined
1 in the standard "typing" module.
2
3 Example usage::
4 from typing_inspect import is_generic_type
5 """
6
7 # NOTE: This module must support Python 2.7 in addition to Python 3.x
8
9 import sys
10 from mypy_extensions import _TypedDictMeta
11
12 NEW_TYPING = sys.version_info[:3] >= (3, 7, 0) # PEP 560
13 if NEW_TYPING:
14 import collections.abc
15
16 WITH_LITERAL = True
17 WITH_CLASSVAR = True
18 LEGACY_TYPING = False
19
20 if NEW_TYPING:
21 from typing import (
22 Generic, Callable, Union, TypeVar, ClassVar, Tuple, _GenericAlias, ForwardRef
23 )
24 from typing_extensions import Literal
25 else:
26 from typing import (
27 Callable, CallableMeta, Union, Tuple, TupleMeta, TypeVar, GenericMeta, _ForwardRef
28 )
29 try:
30 from typing import _Union, _ClassVar
31 except ImportError:
32 # support for very old typing module <=3.5.3
33 _Union = type(Union)
34 WITH_CLASSVAR = False
35 LEGACY_TYPING = True
36
37 try: # python 3.6
38 from typing_extensions import _Literal
39 except ImportError: # python 2.7
40 try:
41 from typing import _Literal
42 except ImportError:
43 WITH_LITERAL = False
44
45
46 def _gorg(cls):
47 """This function exists for compatibility with old typing versions."""
48 assert isinstance(cls, GenericMeta)
49 if hasattr(cls, '_gorg'):
50 return cls._gorg
51 while cls.__origin__ is not None:
52 cls = cls.__origin__
53 return cls
54
55
56 def is_generic_type(tp):
57 """Test if the given type is a generic type. This includes Generic itself, but
58 excludes special typing constructs such as Union, Tuple, Callable, ClassVar.
59 Examples::
60
61 is_generic_type(int) == False
62 is_generic_type(Union[int, str]) == False
63 is_generic_type(Union[int, T]) == False
64 is_generic_type(ClassVar[List[int]]) == False
65 is_generic_type(Callable[..., T]) == False
66
67 is_generic_type(Generic) == True
68 is_generic_type(Generic[T]) == True
69 is_generic_type(Iterable[int]) == True
70 is_generic_type(Mapping) == True
71 is_generic_type(MutableMapping[T, List[int]]) == True
72 is_generic_type(Sequence[Union[str, bytes]]) == True
73 """
74 if NEW_TYPING:
75 return (isinstance(tp, type) and issubclass(tp, Generic) or
76 isinstance(tp, _GenericAlias) and
77 tp.__origin__ not in (Union, tuple, ClassVar, collections.abc.Callable))
78 return (isinstance(tp, GenericMeta) and not
79 isinstance(tp, (CallableMeta, TupleMeta)))
80
81
82 def is_callable_type(tp):
83 """Test if the type is a generic callable type, including subclasses
84 excluding non-generic types and callables.
85 Examples::
86
87 is_callable_type(int) == False
88 is_callable_type(type) == False
89 is_callable_type(Callable) == True
90 is_callable_type(Callable[..., int]) == True
91 is_callable_type(Callable[[int, int], Iterable[str]]) == True
92 class MyClass(Callable[[int], int]):
93 ...
94 is_callable_type(MyClass) == True
95
96 For more general tests use callable(), for more precise test
97 (excluding subclasses) use::
98
99 get_origin(tp) is collections.abc.Callable # Callable prior to Python 3.7
100 """
101 if NEW_TYPING:
102 return (tp is Callable or isinstance(tp, _GenericAlias) and
103 tp.__origin__ is collections.abc.Callable or
104 isinstance(tp, type) and issubclass(tp, Generic) and
105 issubclass(tp, collections.abc.Callable))
106 return type(tp) is CallableMeta
107
108
109 def is_tuple_type(tp):
110 """Test if the type is a generic tuple type, including subclasses excluding
111 non-generic classes.
112 Examples::
113
114 is_tuple_type(int) == False
115 is_tuple_type(tuple) == False
116 is_tuple_type(Tuple) == True
117 is_tuple_type(Tuple[str, int]) == True
118 class MyClass(Tuple[str, int]):
119 ...
120 is_tuple_type(MyClass) == True
121
122 For more general tests use issubclass(..., tuple), for more precise test
123 (excluding subclasses) use::
124
125 get_origin(tp) is tuple # Tuple prior to Python 3.7
126 """
127 if NEW_TYPING:
128 return (tp is Tuple or isinstance(tp, _GenericAlias) and
129 tp.__origin__ is tuple or
130 isinstance(tp, type) and issubclass(tp, Generic) and
131 issubclass(tp, tuple))
132 return type(tp) is TupleMeta
133
134
135 def is_optional_type(tp):
136 """Test if the type is type(None), or is a direct union with it, such as Optional[T].
137
138 NOTE: this method inspects nested `Union` arguments but not `TypeVar` definition
139 bounds and constraints. So it will return `False` if
140 - `tp` is a `TypeVar` bound, or constrained to, an optional type
141 - `tp` is a `Union` to a `TypeVar` bound or constrained to an optional type,
142 - `tp` refers to a *nested* `Union` containing an optional type or one of the above.
143
144 Users wishing to check for optionality in types relying on type variables might wish
145 to use this method in combination with `get_constraints` and `get_bound`
146 """
147
148 if tp is type(None): # noqa
149 return True
150 elif is_union_type(tp):
151 return any(is_optional_type(tt) for tt in get_args(tp, evaluate=True))
152 else:
153 return False
154
155
156 def is_union_type(tp):
157 """Test if the type is a union type. Examples::
158
159 is_union_type(int) == False
160 is_union_type(Union) == True
161 is_union_type(Union[int, int]) == False
162 is_union_type(Union[T, int]) == True
163 """
164 if NEW_TYPING:
165 return (tp is Union or
166 isinstance(tp, _GenericAlias) and tp.__origin__ is Union)
167 return type(tp) is _Union
168
169
170 def is_literal_type(tp):
171 if NEW_TYPING:
172 return (tp is Literal or
173 isinstance(tp, _GenericAlias) and tp.__origin__ is Literal)
174 return WITH_LITERAL and type(tp) is _Literal
175
176
177 def is_typevar(tp):
178 """Test if the type represents a type variable. Examples::
179
180 is_typevar(int) == False
181 is_typevar(T) == True
182 is_typevar(Union[T, int]) == False
183 """
184
185 return type(tp) is TypeVar
186
187
188 def is_classvar(tp):
189 """Test if the type represents a class variable. Examples::
190
191 is_classvar(int) == False
192 is_classvar(ClassVar) == True
193 is_classvar(ClassVar[int]) == True
194 is_classvar(ClassVar[List[T]]) == True
195 """
196 if NEW_TYPING:
197 return (tp is ClassVar or
198 isinstance(tp, _GenericAlias) and tp.__origin__ is ClassVar)
199 elif WITH_CLASSVAR:
200 return type(tp) is _ClassVar
201 else:
202 return False
203
204
205 def is_new_type(tp):
206 """Tests if the type represents a distinct type. Examples::
207
208 is_new_type(int) == False
209 is_new_type(NewType('Age', int)) == True
210 is_new_type(NewType('Scores', List[Dict[str, float]])) == True
211 """
212 return getattr(tp, '__supertype__', None) is not None
213
214
215 def is_forward_ref(tp):
216 """Tests if the type is a :class:`typing.ForwardRef`. Examples::
217
218 u = Union["Milk", Way]
219 args = get_args(u)
220 is_forward_ref(args[0]) == True
221 is_forward_ref(args[1]) == False
222 """
223 if not NEW_TYPING:
224 return isinstance(tp, _ForwardRef)
225 return isinstance(tp, ForwardRef)
226
227
228 def get_last_origin(tp):
229 """Get the last base of (multiply) subscripted type. Supports generic types,
230 Union, Callable, and Tuple. Returns None for unsupported types.
231 Examples::
232
233 get_last_origin(int) == None
234 get_last_origin(ClassVar[int]) == None
235 get_last_origin(Generic[T]) == Generic
236 get_last_origin(Union[T, int][str]) == Union[T, int]
237 get_last_origin(List[Tuple[T, T]][int]) == List[Tuple[T, T]]
238 get_last_origin(List) == List
239 """
240 if NEW_TYPING:
241 raise ValueError('This function is only supported in Python 3.6,'
242 ' use get_origin instead')
243 sentinel = object()
244 origin = getattr(tp, '__origin__', sentinel)
245 if origin is sentinel:
246 return None
247 if origin is None:
248 return tp
249 return origin
250
251
252 def get_origin(tp):
253 """Get the unsubscripted version of a type. Supports generic types, Union,
254 Callable, and Tuple. Returns None for unsupported types. Examples::
255
256 get_origin(int) == None
257 get_origin(ClassVar[int]) == None
258 get_origin(Generic) == Generic
259 get_origin(Generic[T]) == Generic
260 get_origin(Union[T, int]) == Union
261 get_origin(List[Tuple[T, T]][int]) == list # List prior to Python 3.7
262 """
263 if NEW_TYPING:
264 if isinstance(tp, _GenericAlias):
265 return tp.__origin__ if tp.__origin__ is not ClassVar else None
266 if tp is Generic:
267 return Generic
268 return None
269 if isinstance(tp, GenericMeta):
270 return _gorg(tp)
271 if is_union_type(tp):
272 return Union
273 if is_tuple_type(tp):
274 return Tuple
275
276 return None
277
278
279 def get_parameters(tp):
280 """Return type parameters of a parameterizable type as a tuple
281 in lexicographic order. Parameterizable types are generic types,
282 unions, tuple types and callable types. Examples::
283
284 get_parameters(int) == ()
285 get_parameters(Generic) == ()
286 get_parameters(Union) == ()
287 get_parameters(List[int]) == ()
288
289 get_parameters(Generic[T]) == (T,)
290 get_parameters(Tuple[List[T], List[S_co]]) == (T, S_co)
291 get_parameters(Union[S_co, Tuple[T, T]][int, U]) == (U,)
292 get_parameters(Mapping[T, Tuple[S_co, T]]) == (T, S_co)
293 """
294 if LEGACY_TYPING:
295 # python <= 3.5.2
296 if is_union_type(tp):
297 params = []
298 for arg in (tp.__union_params__ if tp.__union_params__ is not None else ()):
299 params += get_parameters(arg)
300 return tuple(params)
301 elif is_tuple_type(tp):
302 params = []
303 for arg in (tp.__tuple_params__ if tp.__tuple_params__ is not None else ()):
304 params += get_parameters(arg)
305 return tuple(params)
306 elif is_generic_type(tp):
307 params = []
308 base_params = tp.__parameters__
309 if base_params is None:
310 return ()
311 for bp_ in base_params:
312 for bp in (get_args(bp_) if is_tuple_type(bp_) else (bp_,)):
313 if _has_type_var(bp) and not isinstance(bp, TypeVar):
314 raise TypeError(
315 "Cannot inherit from a generic class "
316 "parameterized with "
317 "non-type-variable %s" % bp)
318 if params is None:
319 params = []
320 if bp not in params:
321 params.append(bp)
322 if params is not None:
323 return tuple(params)
324 else:
325 return ()
326 else:
327 return ()
328 elif NEW_TYPING:
329 if (isinstance(tp, _GenericAlias) or
330 isinstance(tp, type) and issubclass(tp, Generic) and
331 tp is not Generic):
332 return tp.__parameters__
333 else:
334 return ()
335 elif (
336 is_generic_type(tp) or is_union_type(tp) or
337 is_callable_type(tp) or is_tuple_type(tp)
338 ):
339 return tp.__parameters__ if tp.__parameters__ is not None else ()
340 else:
341 return ()
342
343
344 def get_last_args(tp):
345 """Get last arguments of (multiply) subscripted type.
346 Parameters for Callable are flattened. Examples::
347
348 get_last_args(int) == ()
349 get_last_args(Union) == ()
350 get_last_args(ClassVar[int]) == (int,)
351 get_last_args(Union[T, int]) == (T, int)
352 get_last_args(Iterable[Tuple[T, S]][int, T]) == (int, T)
353 get_last_args(Callable[[T], int]) == (T, int)
354 get_last_args(Callable[[], int]) == (int,)
355 """
356 if NEW_TYPING:
357 raise ValueError('This function is only supported in Python 3.6,'
358 ' use get_args instead')
359 elif is_classvar(tp):
360 return (tp.__type__,) if tp.__type__ is not None else ()
361 elif is_generic_type(tp):
362 try:
363 if tp.__args__ is not None and len(tp.__args__) > 0:
364 return tp.__args__
365 except AttributeError:
366 # python 3.5.1
367 pass
368 return tp.__parameters__ if tp.__parameters__ is not None else ()
369 elif is_union_type(tp):
370 try:
371 return tp.__args__ if tp.__args__ is not None else ()
372 except AttributeError:
373 # python 3.5.2
374 return tp.__union_params__ if tp.__union_params__ is not None else ()
375 elif is_callable_type(tp):
376 return tp.__args__ if tp.__args__ is not None else ()
377 elif is_tuple_type(tp):
378 try:
379 return tp.__args__ if tp.__args__ is not None else ()
380 except AttributeError:
381 # python 3.5.2
382 return tp.__tuple_params__ if tp.__tuple_params__ is not None else ()
383 else:
384 return ()
385
386
387 def _eval_args(args):
388 """Internal helper for get_args."""
389 res = []
390 for arg in args:
391 if not isinstance(arg, tuple):
392 res.append(arg)
393 elif is_callable_type(arg[0]):
394 callable_args = _eval_args(arg[1:])
395 if len(arg) == 2:
396 res.append(Callable[[], callable_args[0]])
397 elif arg[1] is Ellipsis:
398 res.append(Callable[..., callable_args[1]])
399 else:
400 res.append(Callable[list(callable_args[:-1]), callable_args[-1]])
401 else:
402 res.append(type(arg[0]).__getitem__(arg[0], _eval_args(arg[1:])))
403 return tuple(res)
404
405
406 def get_args(tp, evaluate=None):
407 """Get type arguments with all substitutions performed. For unions,
408 basic simplifications used by Union constructor are performed.
409 On versions prior to 3.7 if `evaluate` is False (default),
410 report result as nested tuple, this matches
411 the internal representation of types. If `evaluate` is True
412 (or if Python version is 3.7 or greater), then all
413 type parameters are applied (this could be time and memory expensive).
414 Examples::
415
416 get_args(int) == ()
417 get_args(Union[int, Union[T, int], str][int]) == (int, str)
418 get_args(Union[int, Tuple[T, int]][str]) == (int, (Tuple, str, int))
419
420 get_args(Union[int, Tuple[T, int]][str], evaluate=True) == \
421 (int, Tuple[str, int])
422 get_args(Dict[int, Tuple[T, T]][Optional[int]], evaluate=True) == \
423 (int, Tuple[Optional[int], Optional[int]])
424 get_args(Callable[[], T][int], evaluate=True) == ([], int,)
425 """
426 if NEW_TYPING:
427 if evaluate is not None and not evaluate:
428 raise ValueError('evaluate can only be True in Python >= 3.7')
429 if isinstance(tp, _GenericAlias):
430 res = tp.__args__
431 if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis:
432 res = (list(res[:-1]), res[-1])
433 return res
434 return ()
435 if is_classvar(tp):
436 return (tp.__type__,) if tp.__type__ is not None else ()
437 if is_literal_type(tp):
438 return tp.__values__ or ()
439 if (
440 is_generic_type(tp) or is_union_type(tp) or
441 is_callable_type(tp) or is_tuple_type(tp)
442 ):
443 try:
444 tree = tp._subs_tree()
445 except AttributeError:
446 # Old python typing module <= 3.5.3
447 if is_union_type(tp):
448 # backport of union's subs_tree
449 tree = _union_subs_tree(tp)
450 elif is_generic_type(tp):
451 # backport of genericmeta's subs_tree
452 tree = _generic_subs_tree(tp)
453 elif is_tuple_type(tp):
454 # ad-hoc (inspired by union)
455 tree = _tuple_subs_tree(tp)
456 else:
457 # tree = _subs_tree(tp)
458 return ()
459
460 if isinstance(tree, tuple) and len(tree) > 1:
461 if not evaluate:
462 return tree[1:]
463 res = _eval_args(tree[1:])
464 if get_origin(tp) is Callable and res[0] is not Ellipsis:
465 res = (list(res[:-1]), res[-1])
466 return res
467
468 return ()
469
470
471 def get_bound(tp):
472 """Return the type bound to a `TypeVar` if any.
473
474 It the type is not a `TypeVar`, a `TypeError` is raised.
475 Examples::
476
477 get_bound(TypeVar('T')) == None
478 get_bound(TypeVar('T', bound=int)) == int
479 """
480
481 if is_typevar(tp):
482 return getattr(tp, '__bound__', None)
483 else:
484 raise TypeError("type is not a `TypeVar`: " + str(tp))
485
486
487 def get_constraints(tp):
488 """Returns the constraints of a `TypeVar` if any.
489
490 It the type is not a `TypeVar`, a `TypeError` is raised
491 Examples::
492
493 get_constraints(TypeVar('T')) == ()
494 get_constraints(TypeVar('T', int, str)) == (int, str)
495 """
496
497 if is_typevar(tp):
498 return getattr(tp, '__constraints__', ())
499 else:
500 raise TypeError("type is not a `TypeVar`: " + str(tp))
501
502
503 def get_generic_type(obj):
504 """Get the generic type of an object if possible, or runtime class otherwise.
505 Examples::
506
507 class Node(Generic[T]):
508 ...
509 type(Node[int]()) == Node
510 get_generic_type(Node[int]()) == Node[int]
511 get_generic_type(Node[T]()) == Node[T]
512 get_generic_type(1) == int
513 """
514
515 gen_type = getattr(obj, '__orig_class__', None)
516 return gen_type if gen_type is not None else type(obj)
517
518
519 def get_generic_bases(tp):
520 """Get generic base types of a type or empty tuple if not possible.
521 Example::
522
523 class MyClass(List[int], Mapping[str, List[int]]):
524 ...
525 MyClass.__bases__ == (List, Mapping)
526 get_generic_bases(MyClass) == (List[int], Mapping[str, List[int]])
527 """
528 if LEGACY_TYPING:
529 return tuple(t for t in tp.__bases__ if isinstance(t, GenericMeta))
530 else:
531 return getattr(tp, '__orig_bases__', ())
532
533
534 def typed_dict_keys(td):
535 """If td is a TypedDict class, return a dictionary mapping the typed keys to types.
536 Otherwise, return None. Examples::
537
538 class TD(TypedDict):
539 x: int
540 y: int
541 class Other(dict):
542 x: int
543 y: int
544
545 typed_dict_keys(TD) == {'x': int, 'y': int}
546 typed_dict_keys(dict) == None
547 typed_dict_keys(Other) == None
548 """
549 if isinstance(td, _TypedDictMeta):
550 return td.__annotations__.copy()
551 return None
552
553
554 def get_forward_arg(fr):
555 """
556 If fr is a ForwardRef, return the string representation of the forward reference.
557 Otherwise return None. Examples::
558
559 tp = List["FRef"]
560 fr = get_args(tp)[0]
561 get_forward_arg(fr) == "FRef"
562 get_forward_arg(tp) == None
563 """
564 return fr.__forward_arg__ if is_forward_ref(fr) else None
565
566
567 # A few functions backported and adapted for the LEGACY_TYPING context, and used above
568
569 def _replace_arg(arg, tvars, args):
570 """backport of _replace_arg"""
571 if tvars is None:
572 tvars = []
573 # if hasattr(arg, '_subs_tree') and isinstance(arg, (GenericMeta, _TypingBase)):
574 # return arg._subs_tree(tvars, args)
575 if is_union_type(arg):
576 return _union_subs_tree(arg, tvars, args)
577 if is_tuple_type(arg):
578 return _tuple_subs_tree(arg, tvars, args)
579 if is_generic_type(arg):
580 return _generic_subs_tree(arg, tvars, args)
581 if isinstance(arg, TypeVar):
582 for i, tvar in enumerate(tvars):
583 if arg == tvar:
584 return args[i]
585 return arg
586
587
588 def _remove_dups_flatten(parameters):
589 """backport of _remove_dups_flatten"""
590
591 # Flatten out Union[Union[...], ...].
592 params = []
593 for p in parameters:
594 if isinstance(p, _Union): # and p.__origin__ is Union:
595 params.extend(p.__union_params__) # p.__args__)
596 elif isinstance(p, tuple) and len(p) > 0 and p[0] is Union:
597 params.extend(p[1:])
598 else:
599 params.append(p)
600 # Weed out strict duplicates, preserving the first of each occurrence.
601 all_params = set(params)
602 if len(all_params) < len(params):
603 new_params = []
604 for t in params:
605 if t in all_params:
606 new_params.append(t)
607 all_params.remove(t)
608 params = new_params
609 assert not all_params, all_params
610 # Weed out subclasses.
611 # E.g. Union[int, Employee, Manager] == Union[int, Employee].
612 # If object is present it will be sole survivor among proper classes.
613 # Never discard type variables.
614 # (In particular, Union[str, AnyStr] != AnyStr.)
615 all_params = set(params)
616 for t1 in params:
617 if not isinstance(t1, type):
618 continue
619 if any(isinstance(t2, type) and issubclass(t1, t2)
620 for t2 in all_params - {t1}
621 if (not (isinstance(t2, GenericMeta) and
622 get_origin(t2) is not None) and
623 not isinstance(t2, TypeVar))):
624 all_params.remove(t1)
625 return tuple(t for t in params if t in all_params)
626
627
628 def _subs_tree(cls, tvars=None, args=None):
629 """backport of typing._subs_tree, adapted for legacy versions """
630 def _get_origin(cls):
631 try:
632 return cls.__origin__
633 except AttributeError:
634 return None
635
636 current = _get_origin(cls)
637 if current is None:
638 if not is_union_type(cls) and not is_tuple_type(cls):
639 return cls
640
641 # Make of chain of origins (i.e. cls -> cls.__origin__)
642 orig_chain = []
643 while _get_origin(current) is not None:
644 orig_chain.append(current)
645 current = _get_origin(current)
646
647 # Replace type variables in __args__ if asked ...
648 tree_args = []
649
650 def _get_args(cls):
651 if is_union_type(cls):
652 cls_args = cls.__union_params__
653 elif is_tuple_type(cls):
654 cls_args = cls.__tuple_params__
655 else:
656 try:
657 cls_args = cls.__args__
658 except AttributeError:
659 cls_args = ()
660 return cls_args if cls_args is not None else ()
661
662 for arg in _get_args(cls):
663 tree_args.append(_replace_arg(arg, tvars, args))
664 # ... then continue replacing down the origin chain.
665 for ocls in orig_chain:
666 new_tree_args = []
667 for arg in _get_args(ocls):
668 new_tree_args.append(_replace_arg(arg, get_parameters(ocls), tree_args))
669 tree_args = new_tree_args
670 return tree_args
671
672
673 def _union_subs_tree(tp, tvars=None, args=None):
674 """ backport of Union._subs_tree """
675 if tp is Union:
676 return Union # Nothing to substitute
677 tree_args = _subs_tree(tp, tvars, args)
678 # tree_args = tp.__union_params__ if tp.__union_params__ is not None else ()
679 tree_args = _remove_dups_flatten(tree_args)
680 if len(tree_args) == 1:
681 return tree_args[0] # Union of a single type is that type
682 return (Union,) + tree_args
683
684
685 def _generic_subs_tree(tp, tvars=None, args=None):
686 """ backport of GenericMeta._subs_tree """
687 if tp.__origin__ is None:
688 return tp
689 tree_args = _subs_tree(tp, tvars, args)
690 return (_gorg(tp),) + tuple(tree_args)
691
692
693 def _tuple_subs_tree(tp, tvars=None, args=None):
694 """ ad-hoc function (inspired by union) for legacy typing """
695 if tp is Tuple:
696 return Tuple # Nothing to substitute
697 tree_args = _subs_tree(tp, tvars, args)
698 return (Tuple,) + tuple(tree_args)
699
700
701 def _has_type_var(t):
702 if t is None:
703 return False
704 elif is_union_type(t):
705 return _union_has_type_var(t)
706 elif is_tuple_type(t):
707 return _tuple_has_type_var(t)
708 elif is_generic_type(t):
709 return _generic_has_type_var(t)
710 elif is_callable_type(t):
711 return _callable_has_type_var(t)
712 else:
713 return False
714
715
716 def _union_has_type_var(tp):
717 if tp.__union_params__:
718 for t in tp.__union_params__:
719 if _has_type_var(t):
720 return True
721 return False
722
723
724 def _tuple_has_type_var(tp):
725 if tp.__tuple_params__:
726 for t in tp.__tuple_params__:
727 if _has_type_var(t):
728 return True
729 return False
730
731
732 def _callable_has_type_var(tp):
733 if tp.__args__:
734 for t in tp.__args__:
735 if _has_type_var(t):
736 return True
737 return _has_type_var(tp.__result__)
738
739
740 def _generic_has_type_var(tp):
741 if tp.__parameters__:
742 for t in tp.__parameters__:
743 if _has_type_var(t):
744 return True
745 return False