Skip to content

Spec Adapters

Adapters convert various input specifications into a normalized format for parsing.

Learn how to create custom adapters in the Advanced Usage guide.

The following built-in adapters are not meant to be used directly. They serve more as an example than anything else.

anyschema.adapters

attrs_adapter(spec: AttrsClassType) -> FieldSpecIterable

Adapter for attrs classes.

Extracts field information from an attrs class and converts it into an iterator yielding field information as (field_name, field_type, metadata) tuples.

Parameters:

Name Type Description Default
spec AttrsClassType

An attrs class (not an instance).

required

Yields:

Type Description
FieldSpecIterable

A tuple of (field_name, field_type, metadata) for each field. - field_name: The name of the field as defined in the attrs class - field_type: The type annotation of the field - metadata: A tuple of metadata items from the field's metadata dict

Examples:

>>> from attrs import define
>>>
>>> @define
... class Student:
...     name: str
...     age: int
>>>
>>> list(attrs_adapter(Student))
[('name', <class 'str'>, ()), ('age', <class 'int'>, ())]
Source code in anyschema/adapters.py
def attrs_adapter(spec: AttrsClassType) -> FieldSpecIterable:
    """Adapter for attrs classes.

    Extracts field information from an attrs class and converts it into an iterator
    yielding field information as `(field_name, field_type, metadata)` tuples.

    Arguments:
        spec: An attrs class (not an instance).

    Yields:
        A tuple of `(field_name, field_type, metadata)` for each field.
            - `field_name`: The name of the field as defined in the attrs class
            - `field_type`: The type annotation of the field
            - `metadata`: A tuple of metadata items from the field's metadata dict

    Examples:
        >>> from attrs import define
        >>>
        >>> @define
        ... class Student:
        ...     name: str
        ...     age: int
        >>>
        >>> list(attrs_adapter(Student))
        [('name', <class 'str'>, ()), ('age', <class 'int'>, ())]
    """
    import attrs

    # get_type_hints eagerly evaluates annotations, which alleviates us from
    # needing to evaluate ForwardRef's by hand later on.
    # However, it may fail for classes defined in local scopes (e.g., nested classes in functions)
    # so we fall back to using field.type directly if get_type_hints fails.
    try:
        annot_map = get_type_hints(spec)
    except Exception:  # pragma: no cover  # noqa: BLE001
        # If we can't get type hints, use field.type directly
        annot_map = {}

    attrs_fields = attrs.fields(spec)
    attrs_field_names = {field.name for field in attrs_fields}

    # Check for annotations that aren't attrs fields
    # This can happen when a class inherits from an attrs class but isn't decorated itself
    if annot_map and (missing_fields := tuple(field for field in annot_map if field not in attrs_field_names)):
        missing_str = ", ".join(f"'{f}'" for f in sorted(missing_fields))
        msg = (
            f"Class '{spec.__name__}' has annotations ({missing_str}) that are not attrs fields. "
            f"If this class inherits from an attrs class, you must also decorate it with @attrs.define "
            f"or @attrs.frozen to properly define these fields."
        )
        raise AssertionError(msg)

    for field in attrs_fields:
        field_name = field.name
        field_type = annot_map.get(field_name, field.type)

        # Extract metadata if present - attrs stores it as a mapping
        # We convert it to a tuple for consistency with other adapters
        metadata = tuple(field.metadata.values()) if field.metadata else ()

        yield field_name, field_type, metadata

dataclass_adapter(spec: DataclassType) -> FieldSpecIterable

Adapter for dataclasses.

Converts a dataclass into an iterator yielding field information as (field_name, field_type, metadata) tuples.

Parameters:

Name Type Description Default
spec DataclassType

A dataclass with annotated fields.

required

Yields:

Type Description
FieldSpecIterable

A tuple of (field_name, field_type, metadata) for each field.

FieldSpecIterable

The metadata tuple is always empty () for this adapter.

Examples:

>>> from dataclasses import dataclass
>>>
>>> @dataclass
... class Student:
...     name: str
...     age: int
>>>
>>> list(dataclass_adapter(Student))
[('name', <class 'str'>, ()), ('age', <class 'int'>, ())]
Source code in anyschema/adapters.py
def dataclass_adapter(spec: DataclassType) -> FieldSpecIterable:
    """Adapter for dataclasses.

    Converts a dataclass into an iterator yielding field information as
    `(field_name, field_type, metadata)` tuples.

    Arguments:
        spec: A dataclass with annotated fields.

    Yields:
        A tuple of `(field_name, field_type, metadata)` for each field.
        The metadata tuple is always empty `()` for this adapter.

    Examples:
        >>> from dataclasses import dataclass
        >>>
        >>> @dataclass
        ... class Student:
        ...     name: str
        ...     age: int
        >>>
        >>> list(dataclass_adapter(Student))
        [('name', <class 'str'>, ()), ('age', <class 'int'>, ())]
    """
    # get_type_hints eagerly evaluates annotations, which alleviates us from
    #  needing to evaluate ForwardRef's by hand later on.
    annot_map = get_type_hints(spec)

    # Get dataclass fields
    dataclass_fields = dc_fields(spec)
    dataclass_field_names = {field.name for field in dataclass_fields}

    # Check for annotations that aren't dataclass fields
    # This can happen when a class inherits from a dataclass but isn't decorated itself
    if missing_fields := tuple(field for field in annot_map if field not in dataclass_field_names):
        missing_str = ", ".join(f"'{f}'" for f in missing_fields)
        msg = (
            f"Class '{spec.__name__}' has annotations ({missing_str}) that are not dataclass fields. "
            f"If this class inherits from a dataclass, you must also decorate it with @dataclass "
            f"to properly define these fields."
        )
        raise AssertionError(msg)

    for field in dataclass_fields:
        # TODO(unassigned): field.metadata is a mapping, however pydantic has different metadata.
        #  These differences should be smoothed over to create consistent API.
        yield field.name, annot_map[field.name], ()

into_ordered_dict_adapter(spec: IntoOrderedDict) -> FieldSpecIterable

Adapter for Python mappings and sequences of field definitions.

Converts a mapping (e.g., dict) or sequence of 2-tuples into an iterator yielding field information as (field_name, field_type, metadata) tuples.

Parameters:

Name Type Description Default
spec IntoOrderedDict

A mapping from field names to types, or a sequence of (name, type) tuples.

required

Yields:

Type Description
FieldSpecIterable

A tuple of (field_name, field_type, metadata) for each field.

FieldSpecIterable

The metadata tuple is always empty () for this adapter.

Examples:

>>> list(into_ordered_dict_adapter({"name": str, "age": int}))
[('name', <class 'str'>, ()), ('age', <class 'int'>, ())]
>>> list(into_ordered_dict_adapter([("age", int), ("name", str)]))
[('age', <class 'int'>, ()), ('name', <class 'str'>, ())]
Source code in anyschema/adapters.py
def into_ordered_dict_adapter(spec: IntoOrderedDict) -> FieldSpecIterable:
    """Adapter for Python mappings and sequences of field definitions.

    Converts a mapping (e.g., `dict`) or sequence of 2-tuples into an iterator yielding field information as
    `(field_name, field_type, metadata)` tuples.

    Arguments:
        spec: A mapping from field names to types, or a sequence of `(name, type)` tuples.

    Yields:
        A tuple of `(field_name, field_type, metadata)` for each field.
        The metadata tuple is always empty `()` for this adapter.

    Examples:
        >>> list(into_ordered_dict_adapter({"name": str, "age": int}))
        [('name', <class 'str'>, ()), ('age', <class 'int'>, ())]

        >>> list(into_ordered_dict_adapter([("age", int), ("name", str)]))
        [('age', <class 'int'>, ()), ('name', <class 'str'>, ())]
    """
    for field_name, field_type in OrderedDict(spec).items():
        yield field_name, field_type, ()

pydantic_adapter(spec: type[BaseModel]) -> FieldSpecIterable

Adapter for Pydantic BaseModel classes.

Extracts field information from a Pydantic model class and converts it into an iterator yielding field information as (field_name, field_type, metadata) tuples.

Parameters:

Name Type Description Default
spec type[BaseModel]

A Pydantic BaseModel class (not an instance).

required

Yields:

Type Description
FieldSpecIterable

A tuple of (field_name, field_type, metadata) for each field. - field_name: The name of the field as defined in the model - field_type: The type annotation of the field - metadata: A tuple of metadata items from Annotated types or field constraints

Examples:

>>> from pydantic import BaseModel, Field
>>> from typing import Annotated
>>>
>>> class Student(BaseModel):
...     name: str
...     age: Annotated[int, Field(ge=0)]
>>>
>>> list(pydantic_adapter(Student))
[('name', <class 'str'>, ()), ('age', ForwardRef('Annotated[int, Field(ge=0)]', is_class=True), ())]
Source code in anyschema/adapters.py
def pydantic_adapter(spec: type[BaseModel]) -> FieldSpecIterable:
    """Adapter for Pydantic BaseModel classes.

    Extracts field information from a Pydantic model class and converts it into an iterator
    yielding field information as `(field_name, field_type, metadata)` tuples.

    Arguments:
        spec: A Pydantic `BaseModel` class (not an instance).

    Yields:
        A tuple of `(field_name, field_type, metadata)` for each field.
            - `field_name`: The name of the field as defined in the model
            - `field_type`: The type annotation of the field
            - `metadata`: A tuple of metadata items from `Annotated` types or field constraints

    Examples:
        >>> from pydantic import BaseModel, Field
        >>> from typing import Annotated
        >>>
        >>> class Student(BaseModel):
        ...     name: str
        ...     age: Annotated[int, Field(ge=0)]
        >>>
        >>> list(pydantic_adapter(Student))
        [('name', <class 'str'>, ()), ('age', ForwardRef('Annotated[int, Field(ge=0)]', is_class=True), ())]
    """
    for field_name, field_info in spec.model_fields.items():
        yield field_name, field_info.annotation, tuple(field_info.metadata)  # type: ignore[misc]

typed_dict_adapter(spec: TypedDictType) -> FieldSpecIterable

Adapter for TypedDict classes.

Converts a TypedDict into an iterator yielding field information as (field_name, field_type, metadata) tuples.

Parameters:

Name Type Description Default
spec TypedDictType

A TypedDict class (not an instance).

required

Yields:

Type Description
FieldSpecIterable

A tuple of (field_name, field_type, metadata) for each field.

FieldSpecIterable

The metadata tuple is always empty () for this adapter.

Examples:

>>> from typing_extensions import TypedDict
>>>
>>> class Student(TypedDict):
...     name: str
...     age: int
>>>
>>> list(typed_dict_adapter(Student))
[('name', <class 'str'>, ()), ('age', <class 'int'>, ())]
Source code in anyschema/adapters.py
def typed_dict_adapter(spec: TypedDictType) -> FieldSpecIterable:
    """Adapter for TypedDict classes.

    Converts a TypedDict into an iterator yielding field information as
    `(field_name, field_type, metadata)` tuples.

    Arguments:
        spec: A TypedDict class (not an instance).

    Yields:
        A tuple of `(field_name, field_type, metadata)` for each field.
        The metadata tuple is always empty `()` for this adapter.

    Examples:
        >>> from typing_extensions import TypedDict
        >>>
        >>> class Student(TypedDict):
        ...     name: str
        ...     age: int
        >>>
        >>> list(typed_dict_adapter(Student))
        [('name', <class 'str'>, ()), ('age', <class 'int'>, ())]
    """
    type_hints = get_type_hints(spec)
    for field_name, field_type in type_hints.items():
        yield field_name, field_type, ()

Adapters specification

Adapters must follow this signature:

from typing import Iterator, TypeAlias, Callable, Any, Generator
from anyschema.typing import FieldMetadata, FieldName, FieldType

FieldSpec: TypeAlias = tuple[FieldName, FieldType, FieldMetadata]


def my_custom_adapter(spec: Any) -> Iterator[FieldSpec]:
    ...

They don't need to be functions; any callable is acceptable.