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