import python-typing-inspect_0.6.0.orig.tar.gz
Louis-Philippe VĂ©ronneau
3 years ago
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 | #!/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 |