Skip to main content

A Python library for inspecting type annotations and building graph representations of type metadata

Project description

typing-graph

CI PyPI version codecov Python 3.10+ License: MIT

typing-graph is a building block for Python libraries and frameworks that derive runtime behavior from type annotations. If you're building validation frameworks, CLI tools, serializers, ORMs, or similar tools that inspect types to generate code or configure behavior, typing-graph provides the structured type introspection layer so you can focus on your domain logic.

[!WARNING] This project is in early development. APIs may change without notice. Not yet recommended for production use.

Pass any type (generics, Annotated, dataclasses, TypedDict, PEP 695 aliases) and get back a graph of nodes representing the type structure and its metadata. The library handles metadata hoisting (extracting annotations from Annotated wrappers), qualifier detection (ClassVar, Final, Required), forward reference resolution, and caching. Each node exposes a children() method for recursive traversal.

Built on Pydantic's typing-inspection library and designed for compatibility with annotated-types.

Why I built this

After studying how projects like Pydantic, SQLAlchemy, and Typer derive behavior from type annotations, I became fascinated with the pattern and started experimenting with it in my own projects, and after writing similar introspection code across many of them, I decided to extract the common plumbing into a reusable library.

typing-graph provides a consistent graph representation that handles the edge cases of Python's typing system, so projects can focus on their domain logic instead of reinventing type introspection and metadata extraction.

Features

  • Comprehensive type introspection: Inspect any Python type annotation and receive a structured, type-safe node representation
  • Graph-based representation: Every type node exposes a children() method for recursive traversal
  • Metadata hoisting: Automatically extract Annotated metadata and attach it to base types
  • Qualifier extraction: Detect ClassVar, Final, Required, NotRequired, ReadOnly, and InitVar qualifiers
  • Caching: Global cache for efficient repeated introspection
  • Forward reference handling: Configurable evaluation modes for forward references (eager, deferred, or stringified)
  • Modern Python support: PEP 695 type parameters, PEP 696 defaults, PEP 647 TypeGuard, PEP 742 TypeIs
  • Structured type support: dataclass, TypedDict, NamedTuple, Protocol, Enum
  • Metadata querying API: Predicate-based filtering, type-based extraction, scoped queries
  • annotated-types integration: GroupedMetadata flattening and convenience methods for constraint extraction
  • Type inspection controls: Allowlists/blocklists, configurable depth boundaries
  • Graph traversal API: walk() for depth-first traversal, edges() for semantic edge metadata
  • Visitor pattern and path tracking: Type-dispatched visitors, breadth-first traversal
  • attrs support: Field metadata, validators, converters
  • Pydantic support: Field metadata, validators, serializers

See the roadmap for details on planned features.

Installation

Requires Python 3.10 or later.

pip

pip install typing-graph

uv

uv add typing-graph

Poetry

poetry add typing-graph

Quick start

>>> from typing import Annotated
>>> from dataclasses import dataclass
>>> from typing_graph import inspect_type

>>> # Define constraint metadata (like you might in a validation framework)
>>> @dataclass
... class Pattern:
...     regex: str

>>> @dataclass
... class MinLen:
...     value: int

>>> # Define a reusable annotated type alias
>>> URL = Annotated[str, Pattern(r"^https?://")]

>>> # Build a complex nested type
>>> Urls = Annotated[list[URL], MinLen(1)]

>>> # Inspect the type graph
>>> node = inspect_type(Urls)
>>> node  # doctest: +SKIP
SubscriptedGenericNode(metadata=(MinLen(value=1),), origin=GenericTypeNode(cls=list), args=(ConcreteNode(metadata=(Pattern(regex='^https?://'),), cls=str),))

>>> # The outer node is a SubscriptedGenericNode (list) with container-level metadata
>>> node.origin.cls
<class 'list'>
>>> node.metadata
(MinLen(value=1),)

>>> # Traverse to the element type - it carries its own metadata
>>> element = node.args[0]
>>> element.cls
<class 'str'>
>>> element.metadata
(Pattern(regex='^https?://'),)

Each node in the graph carries its own metadata, enabling frameworks to apply different validation or transformation logic at each level of the type structure.

Inspecting functions

from typing_graph import inspect_function

def greet(name: str, times: int = 1) -> str:
    return name * times

func = inspect_function(greet)
print(func.name)  # "greet"
print(func.signature.parameters[0].name)  # "name"
print(func.signature.returns.cls)  # str

Inspecting classes

from dataclasses import dataclass
from typing_graph import inspect_class, DataclassNode

@dataclass(frozen=True, slots=True)
class User:
    name: str
    age: int

node = inspect_class(User)
assert isinstance(node, DataclassNode)
assert node.frozen is True
assert node.slots is True
assert len(node.fields) == 2

Inspecting modules

from typing_graph import inspect_module
import mymodule

types = inspect_module(mymodule)
print(types.classes)      # Dict of class names to inspection results
print(types.functions)    # Dict of function names to FunctionNodes
print(types.type_aliases) # Dict of type alias names to alias nodes

Type node hierarchy

All type representations inherit from TypeNode, which provides:

  • source: Optional source location where the code defines the type
  • metadata: Tuple of metadata from Annotated wrappers (when hoisted)
  • qualifiers: Set of type qualifiers (ClassVar, Final, etc.)
  • children(): Method returning child nodes for graph traversal

Core type nodes

Node Represents
ConcreteNode Non-generic nominal types (int, str, custom classes)
GenericTypeNode Unsubscripted generics (list, Dict)
SubscriptedGenericNode Applied type arguments (list[int], Dict[str, T])
UnionNode Union types (Union[A, B], A | B)
TupleNode Tuple types (heterogeneous and homogeneous)
CallableNode Callable types with parameter and return type info
AnnotatedNode Annotated[T, ...] when metadata is not hoisted

Special forms

Node Represents
AnyNode typing.Any
NeverNode typing.Never / typing.NoReturn
SelfNode typing.Self
LiteralNode Literal[...] with specific values
LiteralStringNode typing.LiteralString

Type variables

Node Represents
TypeVarNode Type variables with bounds, constraints, and variance
ParamSpecNode Parameter specification variables (PEP 612)
TypeVarTupleNode Variadic type variables (PEP 646)

Structured types

Node Represents
DataclassNode Dataclasses with field metadata
TypedDictNode TypedDict with field definitions
NamedTupleNode NamedTuple with named fields
ProtocolNode Protocol with methods and attributes
EnumNode Enum with typed members
AttrsNode attrs classes
PydanticModelNode Pydantic models

Configuration

Control inspection behavior with InspectConfig:

from typing_graph import inspect_type, InspectConfig, EvalMode

config = InspectConfig(
    eval_mode=EvalMode.DEFERRED,  # How to handle forward references
    max_depth=50,                  # Maximum recursion depth
    hoist_metadata=True,           # Move Annotated metadata to base type
    include_source_locations=False, # Track where types are defined
)

node = inspect_type(SomeType, config=config)

Forward reference evaluation modes

  • EvalMode.EAGER: Fully resolve annotations; fail on errors
  • EvalMode.DEFERRED: Use ForwardRefNode for unresolvable annotations (default)
  • EvalMode.STRINGIFIED: Keep annotations as strings, resolve lazily

Inspection functions

Function Purpose
inspect_type() Inspect any type annotation
inspect_function() Inspect a function's signature and metadata
inspect_signature() Inspect a callable's signature
inspect_class() Auto-detect and inspect a class
inspect_dataclass() Inspect a dataclass specifically
inspect_enum() Inspect an Enum specifically
inspect_module() Discover all public types in a module
inspect_type_alias() Inspect a type alias
to_runtime_type() Convert a node back to runtime type hints
cache_clear() Clear the global inspection cache
cache_info() Get cache statistics

Documentation

Full documentation is available at typing-graph.tbhb.dev.

Acknowledgments

Pydantic's approach to type introspection and metadata extraction inspired this library. typing-graph builds on Pydantic's typing-inspection library for low-level type introspection.

AI help

This project uses Claude Code as a development tool for:

  • Rubber ducking and exploring architecture and design alternatives
  • Drafting documentation and docstrings
  • Generating test scaffolding and boilerplate
  • Code cleanup and refactoring suggestions
  • Researching Python typing edge cases
  • Running benchmarks and mutation testing

All contributions undergo review and testing before inclusion, regardless of origin.

License

MIT License. See LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

typing_graph-0.3.0.tar.gz (45.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

typing_graph-0.3.0-py3-none-any.whl (49.8 kB view details)

Uploaded Python 3

File details

Details for the file typing_graph-0.3.0.tar.gz.

File metadata

  • Download URL: typing_graph-0.3.0.tar.gz
  • Upload date:
  • Size: 45.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for typing_graph-0.3.0.tar.gz
Algorithm Hash digest
SHA256 b9d4a9a5e47ccdca9bdae61d400bee5cb2b466ce2d86167343c3e53afcb09d9a
MD5 6f6dcddc86ba987a723901e74cf0d404
BLAKE2b-256 44357f8eab20edaa3ddc86611893c7c5723b58111572ceb34327640bf1a57384

See more details on using hashes here.

File details

Details for the file typing_graph-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: typing_graph-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 49.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for typing_graph-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9319b0552b9b35996e691637434dde6ec1b127346c3e062ef49ac8ca50da6530
MD5 d6ad1266b5de1903374bbc4090c8807c
BLAKE2b-256 288d5590e1243f63a4599e0da7278439b858c099ab45b3e233b63d215ff79a65

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page