Inspecting Types

Warning

This module is experimental. While we don’t expect any breaking changes, we also don’t promise not to break things between releases while this interface stabilizes.

msgspec provides type-introspection support, which can be used to build tooling on top of msgspec-compatible types. Possible use cases include:

  • Generating OpenAPI specifications from msgspec-compatible types (note that the builtin JSON Schema support may be a better starting point for this).

  • Generating example instances of types for testing or documentation purposes

  • Integration with hypothesis for testing

The main function here is msgspec.inspect.type_info for converting a type annotation into a corresponding msgspec.inspect.Type object. There’s also msgspec.inspect.multi_type_info which converts an iterable of annotations; this function is more efficient than calling type_info in a loop.

>>> import msgspec

>>> msgspec.inspect.type_info(bool)
BoolType()

>>> msgspec.inspect.type_info(int)
IntType(gt=None, ge=None, lt=None, le=None, multiple_of=None)

>>> msgspec.inspect.type_info(list[int])  # nested types are traversed
ListType(
    item_type=IntType(gt=None, ge=None, lt=None, le=None, multiple_of=None),
    min_length=None,
    max_length=None
)

>>> msgspec.inspect.multi_type_info([bool, int])  # inspect multiple types
(BoolType(), IntType(gt=None, ge=None, lt=None, le=None, multiple_of=None))

Types with Constraints will include the constraint information as well:

>>> from typing import Annotated

>>> from msgspec import Meta

>>> PositiveInt = Annotated[int, Meta(gt=0)]

>>> msgspec.inspect.type_info(PositiveInt)
IntType(gt=0, ge=None, lt=None, le=None, multiple_of=None)

Compound types like Structs are also supported:

>>> class User(msgspec.Struct):
...     name: str
...     groups: list[str] = []
...     email: str | None = None

>>> msgspec.inspect.type_info(User)
StructType(
    cls=User,
    fields=(
        Field(
            name='name',
            encode_name='name',
            type=StrType(min_length=None, max_length=None, pattern=None),
            required=True,
            default=UNSET,
            default_factory=UNSET
        ),
        Field(
            name='groups',
            encode_name='groups',
            type=ListType(
                item_type=StrType(min_length=None, max_length=None, pattern=None),
                min_length=None,
                max_length=None
            ),
            required=False,
            default=[],
            default_factory=UNSET
        ),
        Field(
            name='email',
            encode_name='email',
            type=UnionType(
                types=(
                    StrType(min_length=None, max_length=None, pattern=None),
                    NoneType()
                )
            ),
            required=False,
            default=None,
            default_factory=UNSET
        )
    ),
    tag_field=None,
    tag=None,
    array_like=False,
    forbid_unknown_fields=False
)

Types with additional metadata like extra_json_schema or title will be wrapped in a msgspec.inspect.Metadata object. Note that all JSON schema specific fields are merged into a single extra_json_schema dict.

>>> UnixName = Annotated[
...     str,
...     Meta(
...         min_length=1,
...         max_length=32,
...         pattern="^[a-z_][a-z0-9_-]*$",
...         description="A valid UNIX username"
...     )
... ]

>>> msgspec.inspect.type_info(UnixName)
Metadata(
    type=StrType(
        min_length=1,
        max_length=32,
        pattern='^[a-z_][a-z0-9_-]*$'
    ),
    extra_json_schema={'description': 'A valid UNIX username'}
)

Every type supported by msgspec has a corresponding msgspec.inspect.Type subclass. See the API docs for a complete list of types.

For an example of using these functions, you might find our builtin JSON Schema generator implementation useful - the code for this can be found here. In particular, take a look at the large if-else statement in _to_schema.