{"id":286,"library":"typing-inspection","title":"typing-inspection","description":"typing-inspection provides runtime tools to inspect Python type annotations at runtime. Maintained by the Pydantic team, it is split into two submodules: `typing_inspection.typing_objects` (predicate functions like `is_union`, `is_literal`, `is_any`, etc. that correctly handle both `typing` and `typing_extensions` variants) and `typing_inspection.introspection` (higher-level helpers such as `inspect_annotation`, `get_literal_values`, and `is_union_origin`). Current version is 0.4.2 (released 2025-10-01); the project has released frequently since its initial release in February 2025.","status":"active","version":"0.4.2","language":"python","source_language":"en","source_url":"https://github.com/pydantic/typing-inspection","tags":["typing","introspection","annotations","runtime","pydantic","type-hints"],"install":[{"cmd":"pip install typing-inspection","lang":"bash","label":"pip"}],"dependencies":[{"reason":"Required at runtime; typing_objects predicates check against both stdlib typing and typing_extensions variants to handle version differences.","package":"typing_extensions","optional":false}],"imports":[{"note":"All public symbols live in the two explicit submodules (.introspection and .typing_objects); the top-level typing_inspection namespace does not re-export them.","wrong":"from typing_inspection import inspect_annotation","symbol":"inspect_annotation","correct":"from typing_inspection.introspection import inspect_annotation"},{"note":"AnnotationSource enum must be imported from the .introspection submodule directly.","wrong":"from typing_inspection import AnnotationSource","symbol":"AnnotationSource","correct":"from typing_inspection.introspection import AnnotationSource"},{"note":"The sentinel was renamed from INFERRED to UNKNOWN in v0.3.0; importing INFERRED raises ImportError.","wrong":"from typing_inspection.introspection import INFERRED","symbol":"UNKNOWN","correct":"from typing_inspection.introspection import UNKNOWN"},{"note":"All typing_objects predicate functions (is_literal, is_any, is_self, is_union, etc.) are in the .typing_objects submodule.","wrong":"from typing_inspection import is_union","symbol":"is_union","correct":"from typing_inspection.typing_objects import is_union"},{"note":"Prefer get_literal_values() over direct __args__ access; it properly expands PEP 695 type aliases.","symbol":"get_literal_values","correct":"from typing_inspection.introspection import get_literal_values"},{"note":"Only needed for Python < 3.14; since Python 3.14 Union[t1,t2] and t1|t2 are the same class, so is_union() from typing_objects suffices.","symbol":"is_union_origin","correct":"from typing_inspection.introspection import is_union_origin"},{"note":"Exception raised when an invalid type qualifier is used for the given AnnotationSource; catch explicitly if you want to report it as a user error.","symbol":"ForbiddenQualifier","correct":"from typing_inspection.introspection import ForbiddenQualifier"}],"quickstart":{"code":"from typing import ClassVar, Union, get_origin\nfrom typing import Annotated\nfrom typing_inspection.introspection import (\n    AnnotationSource,\n    UNKNOWN,\n    inspect_annotation,\n    get_literal_values,\n    is_union_origin,\n)\nfrom typing_inspection.typing_objects import is_any, is_literal, is_union\n\n# --- Unwrap an annotation expression ---\nresult = inspect_annotation(\n    ClassVar[Annotated[int, \"meta\"]],\n    annotation_source=AnnotationSource.CLASS,\n)\nprint(result)\n# InspectedAnnotation(type=int, qualifiers={'class_var'}, metadata=['meta'])\n\n# Check the UNKNOWN sentinel (bare ClassVar / Final with no inner type)\nbare_result = inspect_annotation(ClassVar, annotation_source=AnnotationSource.CLASS)\nif bare_result.type is UNKNOWN:\n    print(\"No explicit inner type; infer from assignment or default to Any\")\n\n# --- typing_objects predicates handle both typing + typing_extensions ---\nfrom typing import Literal\norigin = get_origin(Literal[1, 2])\nprint(is_literal(origin))   # True\n\n# Safe union detection across typing / typing_extensions / PEP 604\nunion_origin = get_origin(Union[int, str])\nprint(is_union_origin(union_origin))  # True\n\n# Retrieve Literal values (expands PEP 695 type aliases correctly)\nvalues = get_literal_values(Literal[\"a\", \"b\", 1])\nprint(values)  # ('a', 'b', 1)\n","lang":"python","description":"Inspect a ClassVar[Annotated[int, 'meta']] annotation, then use typing_objects predicates on the unwrapped type expression."},"warnings":[{"fix":"Replace `from typing_inspection.introspection import INFERRED` with `from typing_inspection.introspection import UNKNOWN` and update all `is INFERRED` checks to `is UNKNOWN`.","message":"The INFERRED sentinel was renamed to UNKNOWN in v0.3.0. Any code importing or comparing against INFERRED will get an ImportError or a broken identity check.","severity":"breaking","affected_versions":"<0.3.0"},{"fix":"Add a branch for `AnnotationSource.DATACLASS` wherever you enumerate AnnotationSource values. Also import `dataclasses.InitVar` handling if you introspect dataclass fields.","message":"v0.4.0 added a new `DATACLASS` value to the AnnotationSource enum to support `dataclasses.InitVar` as a type qualifier. Exhaustive match/if-elif chains over AnnotationSource members will silently miss it.","severity":"breaking","affected_versions":"<0.4.0"},{"fix":"Use `from typing_inspection.typing_objects import is_union` and call `is_union(get_origin(x))`, which checks both variants automatically.","message":"Never use identity checks like `get_origin(x) is typing.Union` or `get_origin(x) is typing_extensions.Union`; typing_extensions may ship a different Union object than stdlib typing.","severity":"gotcha","affected_versions":"all"},{"fix":"Catch `ForbiddenQualifier` explicitly, or pass `annotation_source=AnnotationSource.ANY` if you want to permit all qualifiers regardless of context.","message":"inspect_annotation() raises ForbiddenQualifier if a type qualifier is not allowed for the given AnnotationSource (e.g. using Required outside a TypedDict context). Not catching this causes unhandled exceptions at runtime.","severity":"gotcha","affected_versions":"all"},{"fix":"Always use `get_literal_values(type_expr)` from `typing_inspection.introspection` instead of accessing `.__args__` directly on Literal forms, and remember to iterate over the returned generator object (e.g., using `list()` or a for-loop) to extract the literal values.","message":"Using `type_expr.__args__` directly to get Literal values silently misses PEP 695 type aliases (Python 3.12+), which are lazily evaluated and may not be expanded. Additionally, `get_literal_values` returns a generator that must be iterated to retrieve values.","severity":"gotcha","affected_versions":"all"},{"fix":"Use `unpack_type_aliases='lenient'` to fall back gracefully to skipping the alias if name resolution fails, instead of raising NameError.","message":"When `unpack_type_aliases='eager'` is passed to inspect_annotation(), any undefined symbol in a PEP 695 type alias raises NameError at runtime. The default is 'skip' (aliases not expanded).","severity":"gotcha","affected_versions":">=0.4.0"},{"fix":"For codebases targeting Python 3.14+ exclusively, replace `is_union_origin(get_origin(x))` with `is_union(get_origin(x))` from `typing_inspection.typing_objects`.","message":"`is_union_origin()` is effectively superseded on Python 3.14+, where both Union[t1,t2] and t1|t2 produce the same typing.Union class. The function remains available but its main use case disappears.","severity":"deprecated","affected_versions":">=0.4.1 on Python 3.14+"}],"env_vars":null,"last_verified":"2026-05-12T12:51:19.790Z","next_check":"2026-06-26T00:00:00.000Z","problems":[],"ecosystem":"pypi","meta_description":null,"install_score":100,"install_tag":"verified","quickstart_score":80,"quickstart_tag":"verified","pypi_latest":null,"install_checks":{"last_tested":"2026-05-12","tag":"verified","tag_description":"installs cleanly on critical runtimes, fast import, recently tested","results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.06,"mem_mb":2,"disk_size":"18.2M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.02,"mem_mb":2,"disk_size":"19M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.09,"mem_mb":2.3,"disk_size":"20.1M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":2.3,"disk_size":"21M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":2.2,"disk_size":"11.9M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":2.2,"disk_size":"12M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":2.4,"disk_size":"11.6M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.06,"mem_mb":2.1,"disk_size":"12M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.04,"mem_mb":2,"disk_size":"17.7M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.03,"mem_mb":2,"disk_size":"18M"}]},"quickstart_checks":{"last_tested":"2026-04-23","tag":"verified","tag_description":"quickstart runs on critical runtimes, recently tested","results":[{"runtime":"python:3.10-alpine","exit_code":0},{"runtime":"python:3.10-slim","exit_code":0},{"runtime":"python:3.11-alpine","exit_code":0},{"runtime":"python:3.11-slim","exit_code":0},{"runtime":"python:3.12-alpine","exit_code":0},{"runtime":"python:3.12-slim","exit_code":0},{"runtime":"python:3.13-alpine","exit_code":0},{"runtime":"python:3.13-slim","exit_code":0},{"runtime":"python:3.9-alpine","exit_code":0},{"runtime":"python:3.9-slim","exit_code":0}]}}