Update 2025-04-24_11:44:19

This commit is contained in:
oib
2025-04-24 11:44:23 +02:00
commit e748c737f4
3408 changed files with 717481 additions and 0 deletions

View File

@ -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)

View File

@ -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

View File

@ -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/)."""