Update 2025-04-24_11:44:19
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,548 @@
|
||||
"""High-level introspection utilities, used to inspect type annotations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import types
|
||||
from collections.abc import Generator, Sequence
|
||||
from dataclasses import InitVar
|
||||
from enum import Enum, IntEnum, auto
|
||||
from typing import Any, Literal, NamedTuple, cast
|
||||
|
||||
from typing_extensions import TypeAlias, assert_never, get_args, get_origin
|
||||
|
||||
from . import typing_objects
|
||||
|
||||
__all__ = (
|
||||
'AnnotationSource',
|
||||
'ForbiddenQualifier',
|
||||
'InspectedAnnotation',
|
||||
'Qualifier',
|
||||
'get_literal_values',
|
||||
'inspect_annotation',
|
||||
'is_union_origin',
|
||||
)
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
|
||||
def is_union_origin(obj: Any, /) -> bool:
|
||||
"""Return whether the provided origin is the union form.
|
||||
|
||||
```pycon
|
||||
>>> is_union_origin(typing.Union)
|
||||
True
|
||||
>>> is_union_origin(get_origin(int | str))
|
||||
True
|
||||
```
|
||||
"""
|
||||
return typing_objects.is_union(obj) or obj is types.UnionType
|
||||
|
||||
else:
|
||||
|
||||
def is_union_origin(obj: Any, /) -> bool:
|
||||
"""Return whether the provided origin is the union form.
|
||||
|
||||
```pycon
|
||||
>>> is_union_origin(typing.Union)
|
||||
True
|
||||
>>> is_union_origin(get_origin(int | str))
|
||||
True
|
||||
```
|
||||
"""
|
||||
return typing_objects.is_union(obj)
|
||||
|
||||
|
||||
def _literal_type_check(value: Any, /) -> None:
|
||||
"""Type check the provided literal value against the legal parameters."""
|
||||
if (
|
||||
not isinstance(value, (int, bytes, str, bool, Enum, typing_objects.NoneType))
|
||||
and value is not typing_objects.NoneType
|
||||
):
|
||||
raise TypeError(f'{value} is not a valid literal value, must be one of: int, bytes, str, Enum, None.')
|
||||
|
||||
|
||||
def get_literal_values(
|
||||
annotation: Any,
|
||||
/,
|
||||
*,
|
||||
type_check: bool = False,
|
||||
unpack_type_aliases: Literal['skip', 'lenient', 'eager'] = 'eager',
|
||||
) -> Generator[Any]:
|
||||
"""Yield the values contained in the provided [`Literal`][typing.Literal] [special form][].
|
||||
|
||||
Args:
|
||||
annotation: The [`Literal`][typing.Literal] [special form][] to unpack.
|
||||
type_check: Whether to check if the literal values are [legal parameters][literal-legal-parameters].
|
||||
Raises a [`TypeError`][] otherwise.
|
||||
unpack_type_aliases: What to do when encountering [PEP 695](https://peps.python.org/pep-0695/)
|
||||
[type aliases][type-aliases]. Can be one of:
|
||||
|
||||
- `'skip'`: Do not try to parse type aliases. Note that this can lead to incorrect results:
|
||||
```pycon
|
||||
>>> type MyAlias = Literal[1, 2]
|
||||
>>> list(get_literal_values(Literal[MyAlias, 3], unpack_type_aliases="skip"))
|
||||
[MyAlias, 3]
|
||||
```
|
||||
|
||||
- `'lenient'`: Try to parse type aliases, and fallback to `'skip'` if the type alias can't be inspected
|
||||
(because of an undefined forward reference).
|
||||
|
||||
- `'eager'`: Parse type aliases and raise any encountered [`NameError`][] exceptions (the default):
|
||||
```pycon
|
||||
>>> type MyAlias = Literal[1, 2]
|
||||
>>> list(get_literal_values(Literal[MyAlias, 3], unpack_type_aliases="eager"))
|
||||
[1, 2, 3]
|
||||
```
|
||||
|
||||
Note:
|
||||
While `None` is [equivalent to][none] `type(None)`, the runtime implementation of [`Literal`][typing.Literal]
|
||||
does not de-duplicate them. This function makes sure this de-duplication is applied:
|
||||
|
||||
```pycon
|
||||
>>> list(get_literal_values(Literal[NoneType, None]))
|
||||
[None]
|
||||
```
|
||||
|
||||
Example:
|
||||
```pycon
|
||||
>>> type Ints = Literal[1, 2]
|
||||
>>> list(get_literal_values(Literal[1, Ints], unpack_type_alias="skip"))
|
||||
["a", Ints]
|
||||
>>> list(get_literal_values(Literal[1, Ints]))
|
||||
[1, 2]
|
||||
>>> list(get_literal_values(Literal[1.0], type_check=True))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: 1.0 is not a valid literal value, must be one of: int, bytes, str, Enum, None.
|
||||
```
|
||||
"""
|
||||
# `literal` is guaranteed to be a `Literal[...]` special form, so use
|
||||
# `__args__` directly instead of calling `get_args()`.
|
||||
|
||||
if unpack_type_aliases == 'skip':
|
||||
_has_none = False
|
||||
# `Literal` parameters are already deduplicated, no need to do it ourselves.
|
||||
# (we only check for `None` and `NoneType`, which should be considered as duplicates).
|
||||
for arg in annotation.__args__:
|
||||
if type_check:
|
||||
_literal_type_check(arg)
|
||||
if arg is None or arg is typing_objects.NoneType:
|
||||
if not _has_none:
|
||||
yield None
|
||||
_has_none = True
|
||||
else:
|
||||
yield arg
|
||||
else:
|
||||
# We'll need to manually deduplicate parameters, see the `Literal` implementation in `typing`.
|
||||
values_and_type: list[tuple[Any, type[Any]]] = []
|
||||
|
||||
for arg in annotation.__args__:
|
||||
# Note: we could also check for generic aliases with a type alias as an origin.
|
||||
# However, it is very unlikely that this happens as type variables can't appear in
|
||||
# `Literal` forms, so the only valid (but unnecessary) use case would be something like:
|
||||
# `type Test[T] = Literal['a']` (and then use `Test[SomeType]`).
|
||||
if typing_objects.is_typealiastype(arg):
|
||||
try:
|
||||
alias_value = arg.__value__
|
||||
except NameError:
|
||||
if unpack_type_aliases == 'eager':
|
||||
raise
|
||||
# unpack_type_aliases == "lenient":
|
||||
if type_check:
|
||||
_literal_type_check(arg)
|
||||
values_and_type.append((arg, type(arg)))
|
||||
else:
|
||||
sub_args = get_literal_values(
|
||||
alias_value, type_check=type_check, unpack_type_aliases=unpack_type_aliases
|
||||
)
|
||||
values_and_type.extend((a, type(a)) for a in sub_args) # pyright: ignore[reportUnknownArgumentType]
|
||||
else:
|
||||
if type_check:
|
||||
_literal_type_check(arg)
|
||||
if arg is typing_objects.NoneType:
|
||||
values_and_type.append((None, typing_objects.NoneType))
|
||||
else:
|
||||
values_and_type.append((arg, type(arg))) # pyright: ignore[reportUnknownArgumentType]
|
||||
|
||||
try:
|
||||
dct = dict.fromkeys(values_and_type)
|
||||
except TypeError:
|
||||
# Unhashable parameters, the Python implementation allows them
|
||||
yield from (p for p, _ in values_and_type)
|
||||
else:
|
||||
yield from (p for p, _ in dct)
|
||||
|
||||
|
||||
Qualifier: TypeAlias = Literal['required', 'not_required', 'read_only', 'class_var', 'init_var', 'final']
|
||||
"""A [type qualifier][]."""
|
||||
|
||||
_all_qualifiers: set[Qualifier] = set(get_args(Qualifier))
|
||||
|
||||
|
||||
# TODO at some point, we could switch to an enum flag, so that multiple sources
|
||||
# can be combined. However, is there a need for this?
|
||||
class AnnotationSource(IntEnum):
|
||||
# TODO if/when https://peps.python.org/pep-0767/ is accepted, add 'read_only'
|
||||
# to CLASS and NAMED_TUPLE (even though for named tuples it is redundant).
|
||||
|
||||
"""The source of an annotation, e.g. a class or a function.
|
||||
|
||||
Depending on the source, different [type qualifiers][type qualifier] may be (dis)allowed.
|
||||
"""
|
||||
|
||||
ASSIGNMENT_OR_VARIABLE = auto()
|
||||
"""An annotation used in an assignment or variable annotation:
|
||||
|
||||
```python
|
||||
x: Final[int] = 1
|
||||
y: Final[str]
|
||||
```
|
||||
|
||||
**Allowed type qualifiers:** [`Final`][typing.Final].
|
||||
"""
|
||||
|
||||
CLASS = auto()
|
||||
"""An annotation used in the body of a class:
|
||||
|
||||
```python
|
||||
class Test:
|
||||
x: Final[int] = 1
|
||||
y: ClassVar[str]
|
||||
```
|
||||
|
||||
**Allowed type qualifiers:** [`ClassVar`][typing.ClassVar], [`Final`][typing.Final].
|
||||
"""
|
||||
|
||||
DATACLASS = auto()
|
||||
"""An annotation used in the body of a dataclass:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Test:
|
||||
x: Final[int] = 1
|
||||
y: InitVar[str] = 'test'
|
||||
```
|
||||
|
||||
**Allowed type qualifiers:** [`ClassVar`][typing.ClassVar], [`Final`][typing.Final], [`InitVar`][dataclasses-init-only-variables].
|
||||
""" # noqa: E501
|
||||
|
||||
TYPED_DICT = auto()
|
||||
"""An annotation used in the body of a [`TypedDict`][typing.TypedDict]:
|
||||
|
||||
```python
|
||||
class TD(TypedDict):
|
||||
x: Required[ReadOnly[int]]
|
||||
y: ReadOnly[NotRequired[str]]
|
||||
```
|
||||
|
||||
**Allowed type qualifiers:** [`ReadOnly`][typing.ReadOnly], [`Required`][typing.Required],
|
||||
[`NotRequired`][typing.NotRequired].
|
||||
"""
|
||||
|
||||
NAMED_TUPLE = auto()
|
||||
"""An annotation used in the body of a [`NamedTuple`][typing.NamedTuple].
|
||||
|
||||
```python
|
||||
class NT(NamedTuple):
|
||||
x: int
|
||||
y: str
|
||||
```
|
||||
|
||||
**Allowed type qualifiers:** none.
|
||||
"""
|
||||
|
||||
FUNCTION = auto()
|
||||
"""An annotation used in a function, either for a parameter or the return value.
|
||||
|
||||
```python
|
||||
def func(a: int) -> str:
|
||||
...
|
||||
```
|
||||
|
||||
**Allowed type qualifiers:** none.
|
||||
"""
|
||||
|
||||
ANY = auto()
|
||||
"""An annotation that might come from any source.
|
||||
|
||||
**Allowed type qualifiers:** all.
|
||||
"""
|
||||
|
||||
BARE = auto()
|
||||
"""An annotation that is inspected as is.
|
||||
|
||||
**Allowed type qualifiers:** none.
|
||||
"""
|
||||
|
||||
@property
|
||||
def allowed_qualifiers(self) -> set[Qualifier]:
|
||||
"""The allowed [type qualifiers][type qualifier] for this annotation source."""
|
||||
# TODO use a match statement when Python 3.9 support is dropped.
|
||||
if self is AnnotationSource.ASSIGNMENT_OR_VARIABLE:
|
||||
return {'final'}
|
||||
elif self is AnnotationSource.CLASS:
|
||||
return {'final', 'class_var'}
|
||||
elif self is AnnotationSource.DATACLASS:
|
||||
return {'final', 'class_var', 'init_var'}
|
||||
elif self is AnnotationSource.TYPED_DICT:
|
||||
return {'required', 'not_required', 'read_only'}
|
||||
elif self in (AnnotationSource.NAMED_TUPLE, AnnotationSource.FUNCTION, AnnotationSource.BARE):
|
||||
return set()
|
||||
elif self is AnnotationSource.ANY:
|
||||
return _all_qualifiers
|
||||
else: # pragma: no cover
|
||||
assert_never(self)
|
||||
|
||||
|
||||
class ForbiddenQualifier(Exception):
|
||||
"""The provided [type qualifier][] is forbidden."""
|
||||
|
||||
qualifier: Qualifier
|
||||
"""The forbidden qualifier."""
|
||||
|
||||
def __init__(self, qualifier: Qualifier, /) -> None:
|
||||
self.qualifier = qualifier
|
||||
|
||||
|
||||
class _UnknownTypeEnum(Enum):
|
||||
UNKNOWN = auto()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return 'UNKNOWN'
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return '<UNKNOWN>'
|
||||
|
||||
|
||||
UNKNOWN = _UnknownTypeEnum.UNKNOWN
|
||||
"""A sentinel value used when no [type expression][] is present."""
|
||||
|
||||
_UnkownType: TypeAlias = Literal[_UnknownTypeEnum.UNKNOWN]
|
||||
"""The type of the [`UNKNOWN`][typing_inspection.introspection.UNKNOWN] sentinel value."""
|
||||
|
||||
|
||||
class InspectedAnnotation(NamedTuple):
|
||||
"""The result of the inspected annotation."""
|
||||
|
||||
type: Any | _UnkownType
|
||||
"""The final [type expression][], with [type qualifiers][type qualifier] and annotated metadata stripped.
|
||||
|
||||
If no type expression is available, the [`UNKNOWN`][typing_inspection.introspection.UNKNOWN] sentinel
|
||||
value is used instead. This is the case when a [type qualifier][] is used with no type annotation:
|
||||
|
||||
```python
|
||||
ID: Final = 1
|
||||
|
||||
class C:
|
||||
x: ClassVar = 'test'
|
||||
```
|
||||
"""
|
||||
|
||||
qualifiers: set[Qualifier]
|
||||
"""The [type qualifiers][type qualifier] present on the annotation."""
|
||||
|
||||
metadata: Sequence[Any]
|
||||
"""The annotated metadata."""
|
||||
|
||||
|
||||
def inspect_annotation( # noqa: PLR0915
|
||||
annotation: Any,
|
||||
/,
|
||||
*,
|
||||
annotation_source: AnnotationSource,
|
||||
unpack_type_aliases: Literal['skip', 'lenient', 'eager'] = 'skip',
|
||||
) -> InspectedAnnotation:
|
||||
"""Inspect an [annotation expression][], extracting any [type qualifier][] and metadata.
|
||||
|
||||
An [annotation expression][] is a [type expression][] optionally surrounded by one or more
|
||||
[type qualifiers][type qualifier] or by [`Annotated`][typing.Annotated]. This function will:
|
||||
|
||||
- Unwrap the type expression, keeping track of the type qualifiers.
|
||||
- Unwrap [`Annotated`][typing.Annotated] forms, keeping track of the annotated metadata.
|
||||
|
||||
Args:
|
||||
annotation: The annotation expression to be inspected.
|
||||
annotation_source: The source of the annotation. Depending on the source (e.g. a class), different type
|
||||
qualifiers may be (dis)allowed. To allow any type qualifier, use
|
||||
[`AnnotationSource.ANY`][typing_inspection.introspection.AnnotationSource.ANY].
|
||||
unpack_type_aliases: What to do when encountering [PEP 695](https://peps.python.org/pep-0695/)
|
||||
[type aliases][type-aliases]. Can be one of:
|
||||
|
||||
- `'skip'`: Do not try to parse type aliases (the default):
|
||||
```pycon
|
||||
>>> type MyInt = Annotated[int, 'meta']
|
||||
>>> inspect_annotation(MyInt, annotation_source=AnnotationSource.BARE, unpack_type_aliases='skip')
|
||||
InspectedAnnotation(type=MyInt, qualifiers={}, metadata=[])
|
||||
```
|
||||
|
||||
- `'lenient'`: Try to parse type aliases, and fallback to `'skip'` if the type alias
|
||||
can't be inspected (because of an undefined forward reference):
|
||||
```pycon
|
||||
>>> type MyInt = Annotated[Undefined, 'meta']
|
||||
>>> inspect_annotation(MyInt, annotation_source=AnnotationSource.BARE, unpack_type_aliases='lenient')
|
||||
InspectedAnnotation(type=MyInt, qualifiers={}, metadata=[])
|
||||
>>> Undefined = int
|
||||
>>> inspect_annotation(MyInt, annotation_source=AnnotationSource.BARE, unpack_type_aliases='lenient')
|
||||
InspectedAnnotation(type=int, qualifiers={}, metadata=['meta'])
|
||||
```
|
||||
|
||||
- `'eager'`: Parse type aliases and raise any encountered [`NameError`][] exceptions.
|
||||
|
||||
Returns:
|
||||
The result of the inspected annotation, where the type expression, used qualifiers and metadata is stored.
|
||||
|
||||
Example:
|
||||
```pycon
|
||||
>>> inspect_annotation(
|
||||
... Final[Annotated[ClassVar[Annotated[int, 'meta_1']], 'meta_2']],
|
||||
... annotation_source=AnnotationSource.CLASS,
|
||||
... )
|
||||
...
|
||||
InspectedAnnotation(type=int, qualifiers={'class_var', 'final'}, metadata=['meta_1', 'meta_2'])
|
||||
```
|
||||
"""
|
||||
allowed_qualifiers = annotation_source.allowed_qualifiers
|
||||
qualifiers: set[Qualifier] = set()
|
||||
metadata: list[Any] = []
|
||||
|
||||
while True:
|
||||
annotation, _meta = _unpack_annotated(annotation, unpack_type_aliases=unpack_type_aliases)
|
||||
if _meta:
|
||||
metadata = _meta + metadata
|
||||
continue
|
||||
|
||||
origin = get_origin(annotation)
|
||||
if origin is not None:
|
||||
if typing_objects.is_classvar(origin):
|
||||
if 'class_var' not in allowed_qualifiers:
|
||||
raise ForbiddenQualifier('class_var')
|
||||
qualifiers.add('class_var')
|
||||
annotation = annotation.__args__[0]
|
||||
elif typing_objects.is_final(origin):
|
||||
if 'final' not in allowed_qualifiers:
|
||||
raise ForbiddenQualifier('final')
|
||||
qualifiers.add('final')
|
||||
annotation = annotation.__args__[0]
|
||||
elif typing_objects.is_required(origin):
|
||||
if 'required' not in allowed_qualifiers:
|
||||
raise ForbiddenQualifier('required')
|
||||
qualifiers.add('required')
|
||||
annotation = annotation.__args__[0]
|
||||
elif typing_objects.is_notrequired(origin):
|
||||
if 'not_required' not in allowed_qualifiers:
|
||||
raise ForbiddenQualifier('not_required')
|
||||
qualifiers.add('not_required')
|
||||
annotation = annotation.__args__[0]
|
||||
elif typing_objects.is_readonly(origin):
|
||||
if 'read_only' not in allowed_qualifiers:
|
||||
raise ForbiddenQualifier('not_required')
|
||||
qualifiers.add('read_only')
|
||||
annotation = annotation.__args__[0]
|
||||
else:
|
||||
# origin is not None but not a type qualifier nor `Annotated` (e.g. `list[int]`):
|
||||
break
|
||||
elif isinstance(annotation, InitVar):
|
||||
if 'init_var' not in allowed_qualifiers:
|
||||
raise ForbiddenQualifier('init_var')
|
||||
qualifiers.add('init_var')
|
||||
annotation = cast(Any, annotation.type)
|
||||
else:
|
||||
break
|
||||
|
||||
# `Final`, `ClassVar` and `InitVar` are type qualifiers allowed to be used as a bare annotation:
|
||||
if typing_objects.is_final(annotation):
|
||||
if 'final' not in allowed_qualifiers:
|
||||
raise ForbiddenQualifier('final')
|
||||
qualifiers.add('final')
|
||||
annotation = UNKNOWN
|
||||
elif typing_objects.is_classvar(annotation):
|
||||
if 'class_var' not in allowed_qualifiers:
|
||||
raise ForbiddenQualifier('class_var')
|
||||
qualifiers.add('class_var')
|
||||
annotation = UNKNOWN
|
||||
elif annotation is InitVar:
|
||||
if 'init_var' not in allowed_qualifiers:
|
||||
raise ForbiddenQualifier('init_var')
|
||||
qualifiers.add('init_var')
|
||||
annotation = UNKNOWN
|
||||
|
||||
return InspectedAnnotation(annotation, qualifiers, metadata)
|
||||
|
||||
|
||||
def _unpack_annotated_inner(
|
||||
annotation: Any, unpack_type_aliases: Literal['lenient', 'eager'], check_annotated: bool
|
||||
) -> tuple[Any, list[Any]]:
|
||||
origin = get_origin(annotation)
|
||||
if check_annotated and typing_objects.is_annotated(origin):
|
||||
annotated_type = annotation.__origin__
|
||||
metadata = list(annotation.__metadata__)
|
||||
|
||||
# The annotated type might be a PEP 695 type alias, so we need to recursively
|
||||
# unpack it. Because Python already flattens `Annotated[Annotated[<type>, ...], ...]` forms,
|
||||
# we can skip the `is_annotated()` check in the next call:
|
||||
annotated_type, sub_meta = _unpack_annotated_inner(
|
||||
annotated_type, unpack_type_aliases=unpack_type_aliases, check_annotated=False
|
||||
)
|
||||
metadata = sub_meta + metadata
|
||||
return annotated_type, metadata
|
||||
elif typing_objects.is_typealiastype(annotation):
|
||||
try:
|
||||
value = annotation.__value__
|
||||
except NameError:
|
||||
if unpack_type_aliases == 'eager':
|
||||
raise
|
||||
else:
|
||||
typ, metadata = _unpack_annotated_inner(
|
||||
value, unpack_type_aliases=unpack_type_aliases, check_annotated=True
|
||||
)
|
||||
if metadata:
|
||||
# Having metadata means the type alias' `__value__` was an `Annotated` form
|
||||
# (or, recursively, a type alias to an `Annotated` form). It is important to check
|
||||
# for this, as we don't want to unpack other type aliases (e.g. `type MyInt = int`).
|
||||
return typ, metadata
|
||||
return annotation, []
|
||||
elif typing_objects.is_typealiastype(origin):
|
||||
# When parameterized, PEP 695 type aliases become generic aliases
|
||||
# (e.g. with `type MyList[T] = Annotated[list[T], ...]`, `MyList[int]`
|
||||
# is a generic alias).
|
||||
try:
|
||||
value = origin.__value__
|
||||
except NameError:
|
||||
if unpack_type_aliases == 'eager':
|
||||
raise
|
||||
else:
|
||||
# While Python already handles type variable replacement for simple `Annotated` forms,
|
||||
# we need to manually apply the same logic for PEP 695 type aliases:
|
||||
# - With `MyList = Annotated[list[T], ...]`, `MyList[int] == Annotated[list[int], ...]`
|
||||
# - With `type MyList[T] = Annotated[list[T], ...]`, `MyList[int].__value__ == Annotated[list[T], ...]`.
|
||||
|
||||
try:
|
||||
# To do so, we emulate the parameterization of the value with the arguments:
|
||||
# with `type MyList[T] = Annotated[list[T], ...]`, to emulate `MyList[int]`,
|
||||
# we do `Annotated[list[T], ...][int]` (which gives `Annotated[list[T], ...]`):
|
||||
value = value[annotation.__args__]
|
||||
except TypeError:
|
||||
# Might happen if the type alias is parameterized, but its value doesn't have any
|
||||
# type variables, e.g. `type MyInt[T] = int`.
|
||||
pass
|
||||
typ, metadata = _unpack_annotated_inner(
|
||||
value, unpack_type_aliases=unpack_type_aliases, check_annotated=True
|
||||
)
|
||||
if metadata:
|
||||
return typ, metadata
|
||||
return annotation, []
|
||||
|
||||
return annotation, []
|
||||
|
||||
|
||||
# This could eventually be made public:
|
||||
def _unpack_annotated(
|
||||
annotation: Any, /, *, unpack_type_aliases: Literal['skip', 'lenient', 'eager'] = 'eager'
|
||||
) -> tuple[Any, list[Any]]:
|
||||
if unpack_type_aliases == 'skip':
|
||||
if typing_objects.is_annotated(get_origin(annotation)):
|
||||
return annotation.__origin__, list(annotation.__metadata__)
|
||||
else:
|
||||
return annotation, []
|
||||
|
||||
return _unpack_annotated_inner(annotation, unpack_type_aliases=unpack_type_aliases, check_annotated=True)
|
@ -0,0 +1,582 @@
|
||||
"""Low-level introspection utilities for [`typing`][] members.
|
||||
|
||||
The provided functions in this module check against both the [`typing`][] and [`typing_extensions`][]
|
||||
variants, if they exists and are different.
|
||||
"""
|
||||
# ruff: noqa: UP006
|
||||
|
||||
import collections.abc
|
||||
import contextlib
|
||||
import re
|
||||
import sys
|
||||
import typing
|
||||
import warnings
|
||||
from textwrap import dedent
|
||||
from types import FunctionType, GenericAlias
|
||||
from typing import Any, Final
|
||||
|
||||
import typing_extensions
|
||||
from typing_extensions import LiteralString, TypeAliasType, TypeIs, deprecated
|
||||
|
||||
__all__ = (
|
||||
'DEPRECATED_ALIASES',
|
||||
'NoneType',
|
||||
'is_annotated',
|
||||
'is_any',
|
||||
'is_classvar',
|
||||
'is_concatenate',
|
||||
'is_deprecated',
|
||||
'is_final',
|
||||
'is_generic',
|
||||
'is_literal',
|
||||
'is_literalstring',
|
||||
'is_namedtuple',
|
||||
'is_never',
|
||||
'is_newtype',
|
||||
'is_nodefault',
|
||||
'is_noreturn',
|
||||
'is_notrequired',
|
||||
'is_paramspec',
|
||||
'is_paramspecargs',
|
||||
'is_paramspeckwargs',
|
||||
'is_readonly',
|
||||
'is_required',
|
||||
'is_self',
|
||||
'is_typealias',
|
||||
'is_typealiastype',
|
||||
'is_typeguard',
|
||||
'is_typeis',
|
||||
'is_typevar',
|
||||
'is_typevartuple',
|
||||
'is_union',
|
||||
'is_unpack',
|
||||
)
|
||||
|
||||
_IS_PY310 = sys.version_info[:2] == (3, 10)
|
||||
|
||||
|
||||
def _compile_identity_check_function(member: LiteralString, function_name: LiteralString) -> FunctionType:
|
||||
"""Create a function checking that the function argument is the (unparameterized) typing :paramref:`member`.
|
||||
|
||||
The function will make sure to check against both the `typing` and `typing_extensions`
|
||||
variants as depending on the Python version, the `typing_extensions` variant might be different.
|
||||
For instance, on Python 3.9:
|
||||
|
||||
```pycon
|
||||
>>> from typing import Literal as t_Literal
|
||||
>>> from typing_extensions import Literal as te_Literal, get_origin
|
||||
|
||||
>>> t_Literal is te_Literal
|
||||
False
|
||||
>>> get_origin(t_Literal[1])
|
||||
typing.Literal
|
||||
>>> get_origin(te_Literal[1])
|
||||
typing_extensions.Literal
|
||||
```
|
||||
"""
|
||||
in_typing = hasattr(typing, member)
|
||||
in_typing_extensions = hasattr(typing_extensions, member)
|
||||
|
||||
if in_typing and in_typing_extensions:
|
||||
if getattr(typing, member) is getattr(typing_extensions, member):
|
||||
check_code = f'obj is typing.{member}'
|
||||
else:
|
||||
check_code = f'obj is typing.{member} or obj is typing_extensions.{member}'
|
||||
elif in_typing and not in_typing_extensions:
|
||||
check_code = f'obj is typing.{member}'
|
||||
elif not in_typing and in_typing_extensions:
|
||||
check_code = f'obj is typing_extensions.{member}'
|
||||
else:
|
||||
check_code = 'False'
|
||||
|
||||
func_code = dedent(f"""
|
||||
def {function_name}(obj: Any, /) -> bool:
|
||||
return {check_code}
|
||||
""")
|
||||
|
||||
locals_: dict[str, Any] = {}
|
||||
globals_: dict[str, Any] = {'Any': Any, 'typing': typing, 'typing_extensions': typing_extensions}
|
||||
exec(func_code, globals_, locals_)
|
||||
return locals_[function_name]
|
||||
|
||||
|
||||
def _compile_isinstance_check_function(member: LiteralString, function_name: LiteralString) -> FunctionType:
|
||||
"""Create a function checking that the function is an instance of the typing `member`.
|
||||
|
||||
The function will make sure to check against both the `typing` and `typing_extensions`
|
||||
variants as depending on the Python version, the `typing_extensions` variant might be different.
|
||||
"""
|
||||
in_typing = hasattr(typing, member)
|
||||
in_typing_extensions = hasattr(typing_extensions, member)
|
||||
|
||||
if in_typing and in_typing_extensions:
|
||||
if getattr(typing, member) is getattr(typing_extensions, member):
|
||||
check_code = f'isinstance(obj, typing.{member})'
|
||||
else:
|
||||
check_code = f'isinstance(obj, (typing.{member}, typing_extensions.{member}))'
|
||||
elif in_typing and not in_typing_extensions:
|
||||
check_code = f'isinstance(obj, typing.{member})'
|
||||
elif not in_typing and in_typing_extensions:
|
||||
check_code = f'isinstance(obj, typing_extensions.{member})'
|
||||
else:
|
||||
check_code = 'False'
|
||||
|
||||
func_code = dedent(f"""
|
||||
def {function_name}(obj: Any, /) -> 'TypeIs[{member}]':
|
||||
return {check_code}
|
||||
""")
|
||||
|
||||
locals_: dict[str, Any] = {}
|
||||
globals_: dict[str, Any] = {'Any': Any, 'typing': typing, 'typing_extensions': typing_extensions}
|
||||
exec(func_code, globals_, locals_)
|
||||
return locals_[function_name]
|
||||
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from types import NoneType
|
||||
else:
|
||||
NoneType = type(None)
|
||||
|
||||
# Keep this ordered, as per `typing.__all__`:
|
||||
|
||||
is_annotated = _compile_identity_check_function('Annotated', 'is_annotated')
|
||||
is_annotated.__doc__ = """
|
||||
Return whether the argument is the [`Annotated`][typing.Annotated] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_annotated(Annotated)
|
||||
True
|
||||
>>> is_annotated(Annotated[int, ...])
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
is_any = _compile_identity_check_function('Any', 'is_any')
|
||||
is_any.__doc__ = """
|
||||
Return whether the argument is the [`Any`][typing.Any] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_any(Any)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
is_classvar = _compile_identity_check_function('ClassVar', 'is_classvar')
|
||||
is_classvar.__doc__ = """
|
||||
Return whether the argument is the [`ClassVar`][typing.ClassVar] [type qualifier][].
|
||||
|
||||
```pycon
|
||||
>>> is_classvar(ClassVar)
|
||||
True
|
||||
>>> is_classvar(ClassVar[int])
|
||||
>>> False
|
||||
```
|
||||
"""
|
||||
|
||||
is_concatenate = _compile_identity_check_function('Concatenate', 'is_concatenate')
|
||||
is_concatenate.__doc__ = """
|
||||
Return whether the argument is the [`Concatenate`][typing.Concatenate] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_concatenate(Concatenate)
|
||||
True
|
||||
>>> is_concatenate(Concatenate[int, P])
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
is_final = _compile_identity_check_function('Final', 'is_final')
|
||||
is_final.__doc__ = """
|
||||
Return whether the argument is the [`Final`][typing.Final] [type qualifier][].
|
||||
|
||||
```pycon
|
||||
>>> is_final(Final)
|
||||
True
|
||||
>>> is_final(Final[int])
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
# ForwardRef?
|
||||
|
||||
is_generic = _compile_identity_check_function('Generic', 'is_generic')
|
||||
is_generic.__doc__ = """
|
||||
Return whether the argument is the [`Generic`][typing.Generic] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_generic(Generic)
|
||||
True
|
||||
>>> is_generic(Generic[T])
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
is_literal = _compile_identity_check_function('Literal', 'is_literal')
|
||||
is_literal.__doc__ = """
|
||||
Return whether the argument is the [`Literal`][typing.Literal] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_literal(Literal)
|
||||
True
|
||||
>>> is_literal(Literal["a"])
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
# `get_origin(Optional[int]) is Union`, so `is_optional()` isn't implemented.
|
||||
|
||||
is_paramspec = _compile_isinstance_check_function('ParamSpec', 'is_paramspec')
|
||||
is_paramspec.__doc__ = """
|
||||
Return whether the argument is an instance of [`ParamSpec`][typing.ParamSpec].
|
||||
|
||||
```pycon
|
||||
>>> P = ParamSpec('P')
|
||||
>>> is_paramspec(P)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
# Protocol?
|
||||
|
||||
is_typevar = _compile_isinstance_check_function('TypeVar', 'is_typevar')
|
||||
is_typevar.__doc__ = """
|
||||
Return whether the argument is an instance of [`TypeVar`][typing.TypeVar].
|
||||
|
||||
```pycon
|
||||
>>> T = TypeVar('T')
|
||||
>>> is_typevar(T)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
is_typevartuple = _compile_isinstance_check_function('TypeVarTuple', 'is_typevartuple')
|
||||
is_typevartuple.__doc__ = """
|
||||
Return whether the argument is an instance of [`TypeVarTuple`][typing.TypeVarTuple].
|
||||
|
||||
```pycon
|
||||
>>> Ts = TypeVarTuple('Ts')
|
||||
>>> is_typevartuple(Ts)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
is_union = _compile_identity_check_function('Union', 'is_union')
|
||||
is_union.__doc__ = """
|
||||
Return whether the argument is the [`Union`][typing.Union] [special form][].
|
||||
|
||||
This function can also be used to check for the [`Optional`][typing.Optional] [special form][],
|
||||
as at runtime, `Optional[int]` is equivalent to `Union[int, None]`.
|
||||
|
||||
```pycon
|
||||
>>> is_union(Union)
|
||||
True
|
||||
>>> is_union(Union[int, str])
|
||||
False
|
||||
```
|
||||
|
||||
!!! warning
|
||||
This does not check for unions using the [new syntax][types-union] (e.g. `int | str`).
|
||||
"""
|
||||
|
||||
|
||||
def is_namedtuple(obj: Any, /) -> bool:
|
||||
"""Return whether the argument is a named tuple type.
|
||||
|
||||
This includes [`NamedTuple`][typing.NamedTuple] subclasses and classes created from the
|
||||
[`collections.namedtuple`][] factory function.
|
||||
|
||||
```pycon
|
||||
>>> class User(NamedTuple):
|
||||
... name: str
|
||||
...
|
||||
>>> is_namedtuple(User)
|
||||
True
|
||||
>>> City = collections.namedtuple('City', [])
|
||||
>>> is_namedtuple(City)
|
||||
True
|
||||
>>> is_namedtuple(NamedTuple)
|
||||
False
|
||||
```
|
||||
"""
|
||||
return isinstance(obj, type) and issubclass(obj, tuple) and hasattr(obj, '_fields') # pyright: ignore[reportUnknownArgumentType]
|
||||
|
||||
|
||||
# TypedDict?
|
||||
|
||||
# BinaryIO? IO? TextIO?
|
||||
|
||||
is_literalstring = _compile_identity_check_function('LiteralString', 'is_literalstring')
|
||||
is_literalstring.__doc__ = """
|
||||
Return whether the argument is the [`LiteralString`][typing.LiteralString] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_literalstring(LiteralString)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
is_never = _compile_identity_check_function('Never', 'is_never')
|
||||
is_never.__doc__ = """
|
||||
Return whether the argument is the [`Never`][typing.Never] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_never(Never)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
is_newtype = _compile_isinstance_check_function('NewType', 'is_newtype')
|
||||
else: # On Python 3.10, `NewType` is a function.
|
||||
|
||||
def is_newtype(obj: Any, /) -> bool:
|
||||
return hasattr(obj, '__supertype__')
|
||||
|
||||
|
||||
is_newtype.__doc__ = """
|
||||
Return whether the argument is a [`NewType`][typing.NewType].
|
||||
|
||||
```pycon
|
||||
>>> UserId = NewType("UserId", int)
|
||||
>>> is_newtype(UserId)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
is_nodefault = _compile_identity_check_function('NoDefault', 'is_nodefault')
|
||||
is_nodefault.__doc__ = """
|
||||
Return whether the argument is the [`NoDefault`][typing.NoDefault] sentinel object.
|
||||
|
||||
```pycon
|
||||
>>> is_nodefault(NoDefault)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
is_noreturn = _compile_identity_check_function('NoReturn', 'is_noreturn')
|
||||
is_noreturn.__doc__ = """
|
||||
Return whether the argument is the [`NoReturn`][typing.NoReturn] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_noreturn(NoReturn)
|
||||
True
|
||||
>>> is_noreturn(Never)
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
is_notrequired = _compile_identity_check_function('NotRequired', 'is_notrequired')
|
||||
is_notrequired.__doc__ = """
|
||||
Return whether the argument is the [`NotRequired`][typing.NotRequired] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_notrequired(NotRequired)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
is_paramspecargs = _compile_isinstance_check_function('ParamSpecArgs', 'is_paramspecargs')
|
||||
is_paramspecargs.__doc__ = """
|
||||
Return whether the argument is an instance of [`ParamSpecArgs`][typing.ParamSpecArgs].
|
||||
|
||||
```pycon
|
||||
>>> P = ParamSpec('P')
|
||||
>>> is_paramspecargs(P.args)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
is_paramspeckwargs = _compile_isinstance_check_function('ParamSpecKwargs', 'is_paramspeckwargs')
|
||||
is_paramspeckwargs.__doc__ = """
|
||||
Return whether the argument is an instance of [`ParamSpecKwargs`][typing.ParamSpecKwargs].
|
||||
|
||||
```pycon
|
||||
>>> P = ParamSpec('P')
|
||||
>>> is_paramspeckwargs(P.kwargs)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
is_readonly = _compile_identity_check_function('ReadOnly', 'is_readonly')
|
||||
is_readonly.__doc__ = """
|
||||
Return whether the argument is the [`ReadOnly`][typing.ReadOnly] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_readonly(ReadOnly)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
is_required = _compile_identity_check_function('Required', 'is_required')
|
||||
is_required.__doc__ = """
|
||||
Return whether the argument is the [`Required`][typing.Required] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_required(Required)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
is_self = _compile_identity_check_function('Self', 'is_self')
|
||||
is_self.__doc__ = """
|
||||
Return whether the argument is the [`Self`][typing.Self] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_self(Self)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
# TYPE_CHECKING?
|
||||
|
||||
is_typealias = _compile_identity_check_function('TypeAlias', 'is_typealias')
|
||||
is_typealias.__doc__ = """
|
||||
Return whether the argument is the [`TypeAlias`][typing.TypeAlias] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_typealias(TypeAlias)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
is_typeguard = _compile_identity_check_function('TypeGuard', 'is_typeguard')
|
||||
is_typeguard.__doc__ = """
|
||||
Return whether the argument is the [`TypeGuard`][typing.TypeGuard] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_typeguard(TypeGuard)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
is_typeis = _compile_identity_check_function('TypeIs', 'is_typeis')
|
||||
is_typeis.__doc__ = """
|
||||
Return whether the argument is the [`TypeIs`][typing.TypeIs] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_typeis(TypeIs)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
_is_typealiastype_inner = _compile_isinstance_check_function('TypeAliasType', '_is_typealiastype_inner')
|
||||
|
||||
|
||||
if _IS_PY310:
|
||||
# Parameterized PEP 695 type aliases are instances of `types.GenericAlias` in typing_extensions>=4.13.0.
|
||||
# On Python 3.10, with `Alias[int]` being such an instance of `GenericAlias`,
|
||||
# `isinstance(Alias[int], TypeAliasType)` returns `True`.
|
||||
# See https://github.com/python/cpython/issues/89828.
|
||||
def is_typealiastype(obj: Any, /) -> 'TypeIs[TypeAliasType]':
|
||||
return type(obj) is not GenericAlias and _is_typealiastype_inner(obj)
|
||||
else:
|
||||
is_typealiastype = _compile_isinstance_check_function('TypeAliasType', 'is_typealiastype')
|
||||
|
||||
is_typealiastype.__doc__ = """
|
||||
Return whether the argument is a [`TypeAliasType`][typing.TypeAliasType] instance.
|
||||
|
||||
```pycon
|
||||
>>> type MyInt = int
|
||||
>>> is_typealiastype(MyInt)
|
||||
True
|
||||
>>> MyStr = TypeAliasType("MyStr", str)
|
||||
>>> is_typealiastype(MyStr):
|
||||
True
|
||||
>>> type MyList[T] = list[T]
|
||||
>>> is_typealiastype(MyList[int])
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
is_unpack = _compile_identity_check_function('Unpack', 'is_unpack')
|
||||
is_unpack.__doc__ = """
|
||||
Return whether the argument is the [`Unpack`][typing.Unpack] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_unpack(Unpack)
|
||||
True
|
||||
>>> is_unpack(Unpack[Ts])
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
|
||||
def is_deprecated(obj: Any, /) -> 'TypeIs[deprecated]':
|
||||
return isinstance(obj, (warnings.deprecated, typing_extensions.deprecated))
|
||||
|
||||
else:
|
||||
|
||||
def is_deprecated(obj: Any, /) -> 'TypeIs[deprecated]':
|
||||
return isinstance(obj, typing_extensions.deprecated)
|
||||
|
||||
|
||||
is_deprecated.__doc__ = """
|
||||
Return whether the argument is a [`deprecated`][warnings.deprecated] instance.
|
||||
|
||||
This also includes the [`typing_extensions` backport][typing_extensions.deprecated].
|
||||
|
||||
```pycon
|
||||
>>> is_deprecated(warnings.deprecated('message'))
|
||||
True
|
||||
>>> is_deprecated(typing_extensions('deprecated'))
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
# Aliases defined in the `typing` module using `typing._SpecialGenericAlias` (itself aliases as `alias()`):
|
||||
DEPRECATED_ALIASES: Final[dict[Any, type[Any]]] = {
|
||||
typing.Hashable: collections.abc.Hashable,
|
||||
typing.Awaitable: collections.abc.Awaitable,
|
||||
typing.Coroutine: collections.abc.Coroutine,
|
||||
typing.AsyncIterable: collections.abc.AsyncIterable,
|
||||
typing.AsyncIterator: collections.abc.AsyncIterator,
|
||||
typing.Iterable: collections.abc.Iterable,
|
||||
typing.Iterator: collections.abc.Iterator,
|
||||
typing.Reversible: collections.abc.Reversible,
|
||||
typing.Sized: collections.abc.Sized,
|
||||
typing.Container: collections.abc.Container,
|
||||
typing.Collection: collections.abc.Collection,
|
||||
# type ignore reason: https://github.com/python/typeshed/issues/6257:
|
||||
typing.Callable: collections.abc.Callable, # pyright: ignore[reportAssignmentType, reportUnknownMemberType]
|
||||
typing.AbstractSet: collections.abc.Set,
|
||||
typing.MutableSet: collections.abc.MutableSet,
|
||||
typing.Mapping: collections.abc.Mapping,
|
||||
typing.MutableMapping: collections.abc.MutableMapping,
|
||||
typing.Sequence: collections.abc.Sequence,
|
||||
typing.MutableSequence: collections.abc.MutableSequence,
|
||||
typing.Tuple: tuple,
|
||||
typing.List: list,
|
||||
typing.Deque: collections.deque,
|
||||
typing.Set: set,
|
||||
typing.FrozenSet: frozenset,
|
||||
typing.MappingView: collections.abc.MappingView,
|
||||
typing.KeysView: collections.abc.KeysView,
|
||||
typing.ItemsView: collections.abc.ItemsView,
|
||||
typing.ValuesView: collections.abc.ValuesView,
|
||||
typing.Dict: dict,
|
||||
typing.DefaultDict: collections.defaultdict,
|
||||
typing.OrderedDict: collections.OrderedDict,
|
||||
typing.Counter: collections.Counter,
|
||||
typing.ChainMap: collections.ChainMap,
|
||||
typing.Generator: collections.abc.Generator,
|
||||
typing.AsyncGenerator: collections.abc.AsyncGenerator,
|
||||
typing.Type: type,
|
||||
# Defined in `typing.__getattr__`:
|
||||
typing.Pattern: re.Pattern,
|
||||
typing.Match: re.Match,
|
||||
typing.ContextManager: contextlib.AbstractContextManager,
|
||||
typing.AsyncContextManager: contextlib.AbstractAsyncContextManager,
|
||||
# Skipped: `ByteString` (deprecated, removed in 3.14)
|
||||
}
|
||||
"""A mapping between the deprecated typing aliases to their replacement, as per [PEP 585](https://peps.python.org/pep-0585/)."""
|
||||
|
||||
|
||||
# Add the `typing_extensions` aliases:
|
||||
for alias, target in list(DEPRECATED_ALIASES.items()):
|
||||
# Use `alias.__name__` when we drop support for Python 3.9
|
||||
if (te_alias := getattr(typing_extensions, alias._name, None)) is not None:
|
||||
DEPRECATED_ALIASES[te_alias] = target
|
@ -0,0 +1,396 @@
|
||||
# Stub file generated using:
|
||||
# `stubgen --inspect-mode --include-docstrings -m typing_inspection.typing_objects`
|
||||
# (manual edits need to be applied).
|
||||
"""Low-level introspection utilities for [`typing`][] members.
|
||||
|
||||
The provided functions in this module check against both the [`typing`][] and [`typing_extensions`][]
|
||||
variants, if they exists and are different.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Any, Final, NewType, TypeVar
|
||||
|
||||
from typing_extensions import ParamSpec, ParamSpecArgs, ParamSpecKwargs, TypeAliasType, TypeIs, TypeVarTuple, deprecated
|
||||
|
||||
__all__ = [
|
||||
'DEPRECATED_ALIASES',
|
||||
'NoneType',
|
||||
'is_annotated',
|
||||
'is_any',
|
||||
'is_classvar',
|
||||
'is_concatenate',
|
||||
'is_deprecated',
|
||||
'is_final',
|
||||
'is_generic',
|
||||
'is_literal',
|
||||
'is_literalstring',
|
||||
'is_namedtuple',
|
||||
'is_never',
|
||||
'is_newtype',
|
||||
'is_nodefault',
|
||||
'is_noreturn',
|
||||
'is_notrequired',
|
||||
'is_paramspec',
|
||||
'is_paramspecargs',
|
||||
'is_paramspeckwargs',
|
||||
'is_readonly',
|
||||
'is_required',
|
||||
'is_self',
|
||||
'is_typealias',
|
||||
'is_typealiastype',
|
||||
'is_typeguard',
|
||||
'is_typeis',
|
||||
'is_typevar',
|
||||
'is_typevartuple',
|
||||
'is_union',
|
||||
'is_unpack',
|
||||
]
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from types import NoneType
|
||||
else:
|
||||
NoneType = type(None)
|
||||
|
||||
def is_annotated(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`Annotated`][typing.Annotated] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_annotated(Annotated)
|
||||
True
|
||||
>>> is_annotated(Annotated[int, ...])
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
def is_any(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`Any`][typing.Any] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_any(Any)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_classvar(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`ClassVar`][typing.ClassVar] [type qualifier][].
|
||||
|
||||
```pycon
|
||||
>>> is_classvar(ClassVar)
|
||||
True
|
||||
>>> is_classvar(ClassVar[int])
|
||||
>>> False
|
||||
```
|
||||
"""
|
||||
|
||||
def is_concatenate(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`Concatenate`][typing.Concatenate] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_concatenate(Concatenate)
|
||||
True
|
||||
>>> is_concatenate(Concatenate[int, P])
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
def is_final(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`Final`][typing.Final] [type qualifier][].
|
||||
|
||||
```pycon
|
||||
>>> is_final(Final)
|
||||
True
|
||||
>>> is_final(Final[int])
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
def is_generic(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`Generic`][typing.Generic] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_generic(Generic)
|
||||
True
|
||||
>>> is_generic(Generic[T])
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
def is_literal(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`Literal`][typing.Literal] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_literal(Literal)
|
||||
True
|
||||
>>> is_literal(Literal["a"])
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
def is_paramspec(obj: Any, /) -> TypeIs[ParamSpec]:
|
||||
"""
|
||||
Return whether the argument is an instance of [`ParamSpec`][typing.ParamSpec].
|
||||
|
||||
```pycon
|
||||
>>> P = ParamSpec('P')
|
||||
>>> is_paramspec(P)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_typevar(obj: Any, /) -> TypeIs[TypeVar]:
|
||||
"""
|
||||
Return whether the argument is an instance of [`TypeVar`][typing.TypeVar].
|
||||
|
||||
```pycon
|
||||
>>> T = TypeVar('T')
|
||||
>>> is_typevar(T)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_typevartuple(obj: Any, /) -> TypeIs[TypeVarTuple]:
|
||||
"""
|
||||
Return whether the argument is an instance of [`TypeVarTuple`][typing.TypeVarTuple].
|
||||
|
||||
```pycon
|
||||
>>> Ts = TypeVarTuple('Ts')
|
||||
>>> is_typevartuple(Ts)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_union(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`Union`][typing.Union] [special form][].
|
||||
|
||||
This function can also be used to check for the [`Optional`][typing.Optional] [special form][],
|
||||
as at runtime, `Optional[int]` is equivalent to `Union[int, None]`.
|
||||
|
||||
```pycon
|
||||
>>> is_union(Union)
|
||||
True
|
||||
>>> is_union(Union[int, str])
|
||||
False
|
||||
```
|
||||
|
||||
!!! warning
|
||||
This does not check for unions using the [new syntax][types-union] (e.g. `int | str`).
|
||||
"""
|
||||
|
||||
def is_namedtuple(obj: Any, /) -> bool:
|
||||
"""Return whether the argument is a named tuple type.
|
||||
|
||||
This includes [`NamedTuple`][typing.NamedTuple] subclasses and classes created from the
|
||||
[`collections.namedtuple`][] factory function.
|
||||
|
||||
```pycon
|
||||
>>> class User(NamedTuple):
|
||||
... name: str
|
||||
...
|
||||
>>> is_namedtuple(User)
|
||||
True
|
||||
>>> City = collections.namedtuple('City', [])
|
||||
>>> is_namedtuple(City)
|
||||
True
|
||||
>>> is_namedtuple(NamedTuple)
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
def is_literalstring(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`LiteralString`][typing.LiteralString] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_literalstring(LiteralString)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_never(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`Never`][typing.Never] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_never(Never)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_newtype(obj: Any, /) -> TypeIs[NewType]:
|
||||
"""
|
||||
Return whether the argument is a [`NewType`][typing.NewType].
|
||||
|
||||
```pycon
|
||||
>>> UserId = NewType("UserId", int)
|
||||
>>> is_newtype(UserId)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_nodefault(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`NoDefault`][typing.NoDefault] sentinel object.
|
||||
|
||||
```pycon
|
||||
>>> is_nodefault(NoDefault)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_noreturn(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`NoReturn`][typing.NoReturn] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_noreturn(NoReturn)
|
||||
True
|
||||
>>> is_noreturn(Never)
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
def is_notrequired(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`NotRequired`][typing.NotRequired] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_notrequired(NotRequired)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_paramspecargs(obj: Any, /) -> TypeIs[ParamSpecArgs]:
|
||||
"""
|
||||
Return whether the argument is an instance of [`ParamSpecArgs`][typing.ParamSpecArgs].
|
||||
|
||||
```pycon
|
||||
>>> P = ParamSpec('P')
|
||||
>>> is_paramspecargs(P.args)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_paramspeckwargs(obj: Any, /) -> TypeIs[ParamSpecKwargs]:
|
||||
"""
|
||||
Return whether the argument is an instance of [`ParamSpecKwargs`][typing.ParamSpecKwargs].
|
||||
|
||||
```pycon
|
||||
>>> P = ParamSpec('P')
|
||||
>>> is_paramspeckwargs(P.kwargs)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_readonly(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`ReadOnly`][typing.ReadOnly] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_readonly(ReadOnly)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_required(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`Required`][typing.Required] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_required(Required)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_self(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`Self`][typing.Self] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_self(Self)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_typealias(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`TypeAlias`][typing.TypeAlias] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_typealias(TypeAlias)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_typeguard(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`TypeGuard`][typing.TypeGuard] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_typeguard(TypeGuard)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_typeis(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`TypeIs`][typing.TypeIs] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_typeis(TypeIs)
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
def is_typealiastype(obj: Any, /) -> TypeIs[TypeAliasType]:
|
||||
"""
|
||||
Return whether the argument is a [`TypeAliasType`][typing.TypeAliasType] instance.
|
||||
|
||||
```pycon
|
||||
>>> type MyInt = int
|
||||
>>> is_typealiastype(MyInt)
|
||||
True
|
||||
>>> MyStr = TypeAliasType("MyStr", str)
|
||||
>>> is_typealiastype(MyStr):
|
||||
True
|
||||
>>> type MyList[T] = list[T]
|
||||
>>> is_typealiastype(MyList[int])
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
def is_unpack(obj: Any, /) -> bool:
|
||||
"""
|
||||
Return whether the argument is the [`Unpack`][typing.Unpack] [special form][].
|
||||
|
||||
```pycon
|
||||
>>> is_unpack(Unpack)
|
||||
True
|
||||
>>> is_unpack(Unpack[Ts])
|
||||
False
|
||||
```
|
||||
"""
|
||||
|
||||
def is_deprecated(obj: Any, /) -> TypeIs[deprecated]:
|
||||
"""
|
||||
Return whether the argument is a [`deprecated`][warnings.deprecated] instance.
|
||||
|
||||
This also includes the [`typing_extensions` backport][typing_extensions.deprecated].
|
||||
|
||||
```pycon
|
||||
>>> is_deprecated(warnings.deprecated('message'))
|
||||
True
|
||||
>>> is_deprecated(typing_extensions.deprecated('deprecated'))
|
||||
True
|
||||
```
|
||||
"""
|
||||
|
||||
DEPRECATED_ALIASES: Final[dict[Any, type[Any]]]
|
||||
"""A mapping between the deprecated typing aliases to their replacement, as per [PEP 585](https://peps.python.org/pep-0585/)."""
|
Reference in New Issue
Block a user