typing_objects.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. """Low-level introspection utilities for [`typing`][] members.
  2. The provided functions in this module check against both the [`typing`][] and [`typing_extensions`][]
  3. variants, if they exists and are different.
  4. """
  5. # ruff: noqa: UP006
  6. import collections.abc
  7. import contextlib
  8. import re
  9. import sys
  10. import typing
  11. import warnings
  12. from textwrap import dedent
  13. from types import FunctionType, GenericAlias
  14. from typing import Any, Final
  15. import typing_extensions
  16. from typing_extensions import LiteralString, TypeAliasType, TypeIs, deprecated
  17. __all__ = (
  18. 'DEPRECATED_ALIASES',
  19. 'NoneType',
  20. 'is_annotated',
  21. 'is_any',
  22. 'is_classvar',
  23. 'is_concatenate',
  24. 'is_deprecated',
  25. 'is_final',
  26. 'is_forwardref',
  27. 'is_generic',
  28. 'is_literal',
  29. 'is_literalstring',
  30. 'is_namedtuple',
  31. 'is_never',
  32. 'is_newtype',
  33. 'is_nodefault',
  34. 'is_noextraitems',
  35. 'is_noreturn',
  36. 'is_notrequired',
  37. 'is_paramspec',
  38. 'is_paramspecargs',
  39. 'is_paramspeckwargs',
  40. 'is_readonly',
  41. 'is_required',
  42. 'is_self',
  43. 'is_typealias',
  44. 'is_typealiastype',
  45. 'is_typeguard',
  46. 'is_typeis',
  47. 'is_typevar',
  48. 'is_typevartuple',
  49. 'is_union',
  50. 'is_unpack',
  51. )
  52. _IS_PY310 = sys.version_info[:2] == (3, 10)
  53. def _compile_identity_check_function(member: LiteralString, function_name: LiteralString) -> FunctionType:
  54. """Create a function checking that the function argument is the (unparameterized) typing `member`.
  55. The function will make sure to check against both the `typing` and `typing_extensions`
  56. variants as depending on the Python version, the `typing_extensions` variant might be different.
  57. For instance, on Python 3.9:
  58. ```pycon
  59. >>> from typing import Literal as t_Literal
  60. >>> from typing_extensions import Literal as te_Literal, get_origin
  61. >>> t_Literal is te_Literal
  62. False
  63. >>> get_origin(t_Literal[1])
  64. typing.Literal
  65. >>> get_origin(te_Literal[1])
  66. typing_extensions.Literal
  67. ```
  68. """
  69. in_typing = hasattr(typing, member)
  70. in_typing_extensions = hasattr(typing_extensions, member)
  71. if in_typing and in_typing_extensions:
  72. if getattr(typing, member) is getattr(typing_extensions, member):
  73. check_code = f'obj is typing.{member}'
  74. else:
  75. check_code = f'obj is typing.{member} or obj is typing_extensions.{member}'
  76. elif in_typing and not in_typing_extensions:
  77. check_code = f'obj is typing.{member}'
  78. elif not in_typing and in_typing_extensions:
  79. check_code = f'obj is typing_extensions.{member}'
  80. else:
  81. check_code = 'False'
  82. func_code = dedent(f"""
  83. def {function_name}(obj: Any, /) -> bool:
  84. return {check_code}
  85. """)
  86. locals_: dict[str, Any] = {}
  87. globals_: dict[str, Any] = {'Any': Any, 'typing': typing, 'typing_extensions': typing_extensions}
  88. exec(func_code, globals_, locals_)
  89. return locals_[function_name]
  90. def _compile_isinstance_check_function(member: LiteralString, function_name: LiteralString) -> FunctionType:
  91. """Create a function checking that the function is an instance of the typing `member`.
  92. The function will make sure to check against both the `typing` and `typing_extensions`
  93. variants as depending on the Python version, the `typing_extensions` variant might be different.
  94. """
  95. in_typing = hasattr(typing, member)
  96. in_typing_extensions = hasattr(typing_extensions, member)
  97. if in_typing and in_typing_extensions:
  98. if getattr(typing, member) is getattr(typing_extensions, member):
  99. check_code = f'isinstance(obj, typing.{member})'
  100. else:
  101. check_code = f'isinstance(obj, (typing.{member}, typing_extensions.{member}))'
  102. elif in_typing and not in_typing_extensions:
  103. check_code = f'isinstance(obj, typing.{member})'
  104. elif not in_typing and in_typing_extensions:
  105. check_code = f'isinstance(obj, typing_extensions.{member})'
  106. else:
  107. check_code = 'False'
  108. func_code = dedent(f"""
  109. def {function_name}(obj: Any, /) -> 'TypeIs[{member}]':
  110. return {check_code}
  111. """)
  112. locals_: dict[str, Any] = {}
  113. globals_: dict[str, Any] = {'Any': Any, 'typing': typing, 'typing_extensions': typing_extensions}
  114. exec(func_code, globals_, locals_)
  115. return locals_[function_name]
  116. if sys.version_info >= (3, 10):
  117. from types import NoneType
  118. else:
  119. NoneType = type(None)
  120. # Keep this ordered, as per `typing.__all__`:
  121. is_annotated = _compile_identity_check_function('Annotated', 'is_annotated')
  122. is_annotated.__doc__ = """
  123. Return whether the argument is the [`Annotated`][typing.Annotated] [special form][].
  124. ```pycon
  125. >>> is_annotated(Annotated)
  126. True
  127. >>> is_annotated(Annotated[int, ...])
  128. False
  129. ```
  130. """
  131. is_any = _compile_identity_check_function('Any', 'is_any')
  132. is_any.__doc__ = """
  133. Return whether the argument is the [`Any`][typing.Any] [special form][].
  134. ```pycon
  135. >>> is_any(Any)
  136. True
  137. ```
  138. """
  139. is_classvar = _compile_identity_check_function('ClassVar', 'is_classvar')
  140. is_classvar.__doc__ = """
  141. Return whether the argument is the [`ClassVar`][typing.ClassVar] [type qualifier][].
  142. ```pycon
  143. >>> is_classvar(ClassVar)
  144. True
  145. >>> is_classvar(ClassVar[int])
  146. >>> False
  147. ```
  148. """
  149. is_concatenate = _compile_identity_check_function('Concatenate', 'is_concatenate')
  150. is_concatenate.__doc__ = """
  151. Return whether the argument is the [`Concatenate`][typing.Concatenate] [special form][].
  152. ```pycon
  153. >>> is_concatenate(Concatenate)
  154. True
  155. >>> is_concatenate(Concatenate[int, P])
  156. False
  157. ```
  158. """
  159. is_final = _compile_identity_check_function('Final', 'is_final')
  160. is_final.__doc__ = """
  161. Return whether the argument is the [`Final`][typing.Final] [type qualifier][].
  162. ```pycon
  163. >>> is_final(Final)
  164. True
  165. >>> is_final(Final[int])
  166. False
  167. ```
  168. """
  169. # Unlikely to have a different version in `typing-extensions`, but keep it consistent.
  170. # Also note that starting in 3.14, this is an alias to `annotationlib.ForwardRef`, but
  171. # accessing it from `typing` doesn't seem to be deprecated.
  172. is_forwardref = _compile_isinstance_check_function('ForwardRef', 'is_forwardref')
  173. is_forwardref.__doc__ = """
  174. Return whether the argument is an instance of [`ForwardRef`][typing.ForwardRef].
  175. ```pycon
  176. >>> is_forwardref(ForwardRef('T'))
  177. True
  178. ```
  179. """
  180. is_generic = _compile_identity_check_function('Generic', 'is_generic')
  181. is_generic.__doc__ = """
  182. Return whether the argument is the [`Generic`][typing.Generic] [special form][].
  183. ```pycon
  184. >>> is_generic(Generic)
  185. True
  186. >>> is_generic(Generic[T])
  187. False
  188. ```
  189. """
  190. is_literal = _compile_identity_check_function('Literal', 'is_literal')
  191. is_literal.__doc__ = """
  192. Return whether the argument is the [`Literal`][typing.Literal] [special form][].
  193. ```pycon
  194. >>> is_literal(Literal)
  195. True
  196. >>> is_literal(Literal["a"])
  197. False
  198. ```
  199. """
  200. # `get_origin(Optional[int]) is Union`, so `is_optional()` isn't implemented.
  201. is_paramspec = _compile_isinstance_check_function('ParamSpec', 'is_paramspec')
  202. is_paramspec.__doc__ = """
  203. Return whether the argument is an instance of [`ParamSpec`][typing.ParamSpec].
  204. ```pycon
  205. >>> P = ParamSpec('P')
  206. >>> is_paramspec(P)
  207. True
  208. ```
  209. """
  210. # Protocol?
  211. is_typevar = _compile_isinstance_check_function('TypeVar', 'is_typevar')
  212. is_typevar.__doc__ = """
  213. Return whether the argument is an instance of [`TypeVar`][typing.TypeVar].
  214. ```pycon
  215. >>> T = TypeVar('T')
  216. >>> is_typevar(T)
  217. True
  218. ```
  219. """
  220. is_typevartuple = _compile_isinstance_check_function('TypeVarTuple', 'is_typevartuple')
  221. is_typevartuple.__doc__ = """
  222. Return whether the argument is an instance of [`TypeVarTuple`][typing.TypeVarTuple].
  223. ```pycon
  224. >>> Ts = TypeVarTuple('Ts')
  225. >>> is_typevartuple(Ts)
  226. True
  227. ```
  228. """
  229. is_union = _compile_identity_check_function('Union', 'is_union')
  230. is_union.__doc__ = """
  231. Return whether the argument is the [`Union`][typing.Union] [special form][].
  232. This function can also be used to check for the [`Optional`][typing.Optional] [special form][],
  233. as at runtime, `Optional[int]` is equivalent to `Union[int, None]`.
  234. ```pycon
  235. >>> is_union(Union)
  236. True
  237. >>> is_union(Union[int, str])
  238. False
  239. ```
  240. !!! warning
  241. This does not check for unions using the [new syntax][types-union] (e.g. `int | str`).
  242. """
  243. def is_namedtuple(obj: Any, /) -> bool:
  244. """Return whether the argument is a named tuple type.
  245. This includes [`NamedTuple`][typing.NamedTuple] subclasses and classes created from the
  246. [`collections.namedtuple`][] factory function.
  247. ```pycon
  248. >>> class User(NamedTuple):
  249. ... name: str
  250. ...
  251. >>> is_namedtuple(User)
  252. True
  253. >>> City = collections.namedtuple('City', [])
  254. >>> is_namedtuple(City)
  255. True
  256. >>> is_namedtuple(NamedTuple)
  257. False
  258. ```
  259. """
  260. return isinstance(obj, type) and issubclass(obj, tuple) and hasattr(obj, '_fields') # pyright: ignore[reportUnknownArgumentType]
  261. # TypedDict?
  262. # BinaryIO? IO? TextIO?
  263. is_literalstring = _compile_identity_check_function('LiteralString', 'is_literalstring')
  264. is_literalstring.__doc__ = """
  265. Return whether the argument is the [`LiteralString`][typing.LiteralString] [special form][].
  266. ```pycon
  267. >>> is_literalstring(LiteralString)
  268. True
  269. ```
  270. """
  271. is_never = _compile_identity_check_function('Never', 'is_never')
  272. is_never.__doc__ = """
  273. Return whether the argument is the [`Never`][typing.Never] [special form][].
  274. ```pycon
  275. >>> is_never(Never)
  276. True
  277. ```
  278. """
  279. if sys.version_info >= (3, 10):
  280. is_newtype = _compile_isinstance_check_function('NewType', 'is_newtype')
  281. else: # On Python 3.10, `NewType` is a function.
  282. def is_newtype(obj: Any, /) -> bool:
  283. return hasattr(obj, '__supertype__')
  284. is_newtype.__doc__ = """
  285. Return whether the argument is a [`NewType`][typing.NewType].
  286. ```pycon
  287. >>> UserId = NewType("UserId", int)
  288. >>> is_newtype(UserId)
  289. True
  290. ```
  291. """
  292. is_nodefault = _compile_identity_check_function('NoDefault', 'is_nodefault')
  293. is_nodefault.__doc__ = """
  294. Return whether the argument is the [`NoDefault`][typing.NoDefault] sentinel object.
  295. ```pycon
  296. >>> is_nodefault(NoDefault)
  297. True
  298. ```
  299. """
  300. is_noextraitems = _compile_identity_check_function('NoExtraItems', 'is_noextraitems')
  301. is_noextraitems.__doc__ = """
  302. Return whether the argument is the `NoExtraItems` sentinel object.
  303. ```pycon
  304. >>> is_noextraitems(NoExtraItems)
  305. True
  306. ```
  307. """
  308. is_noreturn = _compile_identity_check_function('NoReturn', 'is_noreturn')
  309. is_noreturn.__doc__ = """
  310. Return whether the argument is the [`NoReturn`][typing.NoReturn] [special form][].
  311. ```pycon
  312. >>> is_noreturn(NoReturn)
  313. True
  314. >>> is_noreturn(Never)
  315. False
  316. ```
  317. """
  318. is_notrequired = _compile_identity_check_function('NotRequired', 'is_notrequired')
  319. is_notrequired.__doc__ = """
  320. Return whether the argument is the [`NotRequired`][typing.NotRequired] [special form][].
  321. ```pycon
  322. >>> is_notrequired(NotRequired)
  323. True
  324. ```
  325. """
  326. is_paramspecargs = _compile_isinstance_check_function('ParamSpecArgs', 'is_paramspecargs')
  327. is_paramspecargs.__doc__ = """
  328. Return whether the argument is an instance of [`ParamSpecArgs`][typing.ParamSpecArgs].
  329. ```pycon
  330. >>> P = ParamSpec('P')
  331. >>> is_paramspecargs(P.args)
  332. True
  333. ```
  334. """
  335. is_paramspeckwargs = _compile_isinstance_check_function('ParamSpecKwargs', 'is_paramspeckwargs')
  336. is_paramspeckwargs.__doc__ = """
  337. Return whether the argument is an instance of [`ParamSpecKwargs`][typing.ParamSpecKwargs].
  338. ```pycon
  339. >>> P = ParamSpec('P')
  340. >>> is_paramspeckwargs(P.kwargs)
  341. True
  342. ```
  343. """
  344. is_readonly = _compile_identity_check_function('ReadOnly', 'is_readonly')
  345. is_readonly.__doc__ = """
  346. Return whether the argument is the [`ReadOnly`][typing.ReadOnly] [special form][].
  347. ```pycon
  348. >>> is_readonly(ReadOnly)
  349. True
  350. ```
  351. """
  352. is_required = _compile_identity_check_function('Required', 'is_required')
  353. is_required.__doc__ = """
  354. Return whether the argument is the [`Required`][typing.Required] [special form][].
  355. ```pycon
  356. >>> is_required(Required)
  357. True
  358. ```
  359. """
  360. is_self = _compile_identity_check_function('Self', 'is_self')
  361. is_self.__doc__ = """
  362. Return whether the argument is the [`Self`][typing.Self] [special form][].
  363. ```pycon
  364. >>> is_self(Self)
  365. True
  366. ```
  367. """
  368. # TYPE_CHECKING?
  369. is_typealias = _compile_identity_check_function('TypeAlias', 'is_typealias')
  370. is_typealias.__doc__ = """
  371. Return whether the argument is the [`TypeAlias`][typing.TypeAlias] [special form][].
  372. ```pycon
  373. >>> is_typealias(TypeAlias)
  374. True
  375. ```
  376. """
  377. is_typeguard = _compile_identity_check_function('TypeGuard', 'is_typeguard')
  378. is_typeguard.__doc__ = """
  379. Return whether the argument is the [`TypeGuard`][typing.TypeGuard] [special form][].
  380. ```pycon
  381. >>> is_typeguard(TypeGuard)
  382. True
  383. ```
  384. """
  385. is_typeis = _compile_identity_check_function('TypeIs', 'is_typeis')
  386. is_typeis.__doc__ = """
  387. Return whether the argument is the [`TypeIs`][typing.TypeIs] [special form][].
  388. ```pycon
  389. >>> is_typeis(TypeIs)
  390. True
  391. ```
  392. """
  393. _is_typealiastype_inner = _compile_isinstance_check_function('TypeAliasType', '_is_typealiastype_inner')
  394. if _IS_PY310:
  395. # Parameterized PEP 695 type aliases are instances of `types.GenericAlias` in typing_extensions>=4.13.0.
  396. # On Python 3.10, with `Alias[int]` being such an instance of `GenericAlias`,
  397. # `isinstance(Alias[int], TypeAliasType)` returns `True`.
  398. # See https://github.com/python/cpython/issues/89828.
  399. def is_typealiastype(obj: Any, /) -> 'TypeIs[TypeAliasType]':
  400. return type(obj) is not GenericAlias and _is_typealiastype_inner(obj)
  401. else:
  402. is_typealiastype = _compile_isinstance_check_function('TypeAliasType', 'is_typealiastype')
  403. is_typealiastype.__doc__ = """
  404. Return whether the argument is a [`TypeAliasType`][typing.TypeAliasType] instance.
  405. ```pycon
  406. >>> type MyInt = int
  407. >>> is_typealiastype(MyInt)
  408. True
  409. >>> MyStr = TypeAliasType("MyStr", str)
  410. >>> is_typealiastype(MyStr):
  411. True
  412. >>> type MyList[T] = list[T]
  413. >>> is_typealiastype(MyList[int])
  414. False
  415. ```
  416. """
  417. is_unpack = _compile_identity_check_function('Unpack', 'is_unpack')
  418. is_unpack.__doc__ = """
  419. Return whether the argument is the [`Unpack`][typing.Unpack] [special form][].
  420. ```pycon
  421. >>> is_unpack(Unpack)
  422. True
  423. >>> is_unpack(Unpack[Ts])
  424. False
  425. ```
  426. """
  427. if sys.version_info >= (3, 13):
  428. def is_deprecated(obj: Any, /) -> 'TypeIs[deprecated]':
  429. return isinstance(obj, (warnings.deprecated, typing_extensions.deprecated))
  430. else:
  431. def is_deprecated(obj: Any, /) -> 'TypeIs[deprecated]':
  432. return isinstance(obj, typing_extensions.deprecated)
  433. is_deprecated.__doc__ = """
  434. Return whether the argument is a [`deprecated`][warnings.deprecated] instance.
  435. This also includes the [`typing_extensions` backport][typing_extensions.deprecated].
  436. ```pycon
  437. >>> is_deprecated(warnings.deprecated('message'))
  438. True
  439. >>> is_deprecated(typing_extensions.deprecated('message'))
  440. True
  441. ```
  442. """
  443. # Aliases defined in the `typing` module using `typing._SpecialGenericAlias` (itself aliased as `alias()`):
  444. DEPRECATED_ALIASES: Final[dict[Any, type[Any]]] = {
  445. typing.Hashable: collections.abc.Hashable,
  446. typing.Awaitable: collections.abc.Awaitable,
  447. typing.Coroutine: collections.abc.Coroutine,
  448. typing.AsyncIterable: collections.abc.AsyncIterable,
  449. typing.AsyncIterator: collections.abc.AsyncIterator,
  450. typing.Iterable: collections.abc.Iterable,
  451. typing.Iterator: collections.abc.Iterator,
  452. typing.Reversible: collections.abc.Reversible,
  453. typing.Sized: collections.abc.Sized,
  454. typing.Container: collections.abc.Container,
  455. typing.Collection: collections.abc.Collection,
  456. # type ignore reason: https://github.com/python/typeshed/issues/6257:
  457. typing.Callable: collections.abc.Callable, # pyright: ignore[reportAssignmentType, reportUnknownMemberType]
  458. typing.AbstractSet: collections.abc.Set,
  459. typing.MutableSet: collections.abc.MutableSet,
  460. typing.Mapping: collections.abc.Mapping,
  461. typing.MutableMapping: collections.abc.MutableMapping,
  462. typing.Sequence: collections.abc.Sequence,
  463. typing.MutableSequence: collections.abc.MutableSequence,
  464. typing.Tuple: tuple,
  465. typing.List: list,
  466. typing.Deque: collections.deque,
  467. typing.Set: set,
  468. typing.FrozenSet: frozenset,
  469. typing.MappingView: collections.abc.MappingView,
  470. typing.KeysView: collections.abc.KeysView,
  471. typing.ItemsView: collections.abc.ItemsView,
  472. typing.ValuesView: collections.abc.ValuesView,
  473. typing.Dict: dict,
  474. typing.DefaultDict: collections.defaultdict,
  475. typing.OrderedDict: collections.OrderedDict,
  476. typing.Counter: collections.Counter,
  477. typing.ChainMap: collections.ChainMap,
  478. typing.Generator: collections.abc.Generator,
  479. typing.AsyncGenerator: collections.abc.AsyncGenerator,
  480. typing.Type: type,
  481. # Defined in `typing.__getattr__`:
  482. typing.Pattern: re.Pattern,
  483. typing.Match: re.Match,
  484. typing.ContextManager: contextlib.AbstractContextManager,
  485. typing.AsyncContextManager: contextlib.AbstractAsyncContextManager,
  486. # Skipped: `ByteString` (deprecated, removed in 3.14)
  487. }
  488. """A mapping between the deprecated typing aliases to their replacement, as per [PEP 585](https://peps.python.org/pep-0585/)."""
  489. # Add the `typing_extensions` aliases:
  490. for alias, target in list(DEPRECATED_ALIASES.items()):
  491. # Use `alias.__name__` when we drop support for Python 3.9
  492. if (te_alias := getattr(typing_extensions, alias._name, None)) is not None:
  493. DEPRECATED_ALIASES[te_alias] = target