Skip to content

introspection

High-level introspection utilities, used to inspect type annotations.

is_union_origin

is_union_origin(obj: Any) -> bool

Return whether the provided origin is the union form.

>>> is_union_origin(typing.Union)
True
>>> is_union_origin(get_origin(int | str))
True
>>> is_union_origin(types.UnionType)
True

Note

Since Python 3.14, both Union[<t1>, <t2>, ...] and <t1> | <t2> | ... forms create instances of the same typing.Union class. As such, it is recommended to not use this function anymore (provided that you only support Python 3.14 or greater), and instead use the typing_objects.is_union() function directly:

from typing import Union, get_origin

from typing_inspection import typing_objects

typ = int | str  # Or Union[int, str]
origin = get_origin(typ)
if typing_objects.is_union(origin):
    ...

get_literal_values

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 special form.

Parameters:

Name Type Description Default
annotation Any

The Literal special form to unpack.

required
type_check bool

Whether to check if the literal values are legal parameters. Raises a TypeError otherwise.

False
unpack_type_aliases Literal['skip', 'lenient', 'eager']

What to do when encountering PEP 695 type aliases. Can be one of:

  • 'skip': Do not try to parse type aliases. Note that this can lead to incorrect results:

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

    >>> type MyAlias = Literal[1, 2]
    >>> list(get_literal_values(Literal[MyAlias, 3], unpack_type_aliases="eager"))
    [1, 2, 3]
    

'eager'
Note

While None is equivalent to type(None), the runtime implementation of Literal does not de-duplicate them. This function makes sure this de-duplication is applied:

>>> list(get_literal_values(Literal[NoneType, None]))
[None]
Example
>>> 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.

inspect_annotation

inspect_annotation(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 or by Annotated. This function will:

  • Unwrap the type expression, keeping track of the type qualifiers.
  • Unwrap Annotated forms, keeping track of the annotated metadata.

Parameters:

Name Type Description Default
annotation Any

The annotation expression to be inspected.

required
annotation_source AnnotationSource

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.

required
unpack_type_aliases Literal['skip', 'lenient', 'eager']

What to do when encountering PEP 695 type aliases. Can be one of:

  • 'skip': Do not try to parse type aliases (the default):

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

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

'skip'

Returns:

Type Description
InspectedAnnotation

The result of the inspected annotation, where the type expression, used qualifiers and metadata is stored.

Example
>>> 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'])

AnnotationSource

Bases: IntEnum

The source of an annotation, e.g. a class or a function.

Depending on the source, different type qualifiers may be (dis)allowed.

ASSIGNMENT_OR_VARIABLE

ASSIGNMENT_OR_VARIABLE = auto()

An annotation used in an assignment or variable annotation:

x: Final[int] = 1
y: Final[str]

Allowed type qualifiers: Final.

CLASS

CLASS = auto()

An annotation used in the body of a class:

class Test:
    x: Final[int] = 1
    y: ClassVar[str]

Allowed type qualifiers: ClassVar, Final.

DATACLASS

DATACLASS = auto()

An annotation used in the body of a dataclass:

@dataclass
class Test:
    x: Final[int] = 1
    y: InitVar[str] = 'test'

Allowed type qualifiers: ClassVar, Final, InitVar.

TYPED_DICT

TYPED_DICT = auto()

An annotation used in the body of a TypedDict:

class TD(TypedDict):
    x: Required[ReadOnly[int]]
    y: ReadOnly[NotRequired[str]]

Allowed type qualifiers: ReadOnly, Required, NotRequired.

NAMED_TUPLE

NAMED_TUPLE = auto()

An annotation used in the body of a NamedTuple.

class NT(NamedTuple):
    x: int
    y: str

Allowed type qualifiers: none.

FUNCTION

FUNCTION = auto()

An annotation used in a function, either for a parameter or the return value.

def func(a: int) -> str:
    ...

Allowed type qualifiers: none.

ANY

ANY = auto()

An annotation that might come from any source.

Allowed type qualifiers: all.

BARE

BARE = auto()

An annotation that is inspected as is.

Allowed type qualifiers: none.

allowed_qualifiers

allowed_qualifiers: set[Qualifier]

The allowed type qualifiers for this annotation source.

Qualifier

Qualifier: TypeAlias = Literal['required', 'not_required', 'read_only', 'class_var', 'init_var', 'final']

InspectedAnnotation

Bases: NamedTuple

The result of the inspected annotation.

type

type: Any | _UnkownType

The final type expression, with type qualifiers and annotated metadata stripped.

If no type expression is available, the UNKNOWN sentinel value is used instead. This is the case when a type qualifier is used with no type annotation:

ID: Final = 1

class C:
    x: ClassVar = 'test'

qualifiers

qualifiers: set[Qualifier]

The type qualifiers present on the annotation.

metadata

metadata: Sequence[Any]

The annotated metadata.

UNKNOWN

UNKNOWN = UNKNOWN

A sentinel value used when no type expression is present.

ForbiddenQualifier

ForbiddenQualifier(qualifier: Qualifier)

Bases: Exception

The provided type qualifier is forbidden.

qualifier

qualifier: Qualifier = qualifier

The forbidden qualifier.