diff --git a/docs/source/api.rst b/docs/source/api.rst index 31289c9e..c9609075 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -73,6 +73,9 @@ JSON Schema .. autofunction:: schema_components + +.. _inspect-api: + Inspect ------- diff --git a/docs/source/index.rst b/docs/source/index.rst index 9705fc8e..6e915ef0 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -164,6 +164,7 @@ is required. :caption: Advanced extending.rst + inspect.rst perf-tips.rst .. toctree:: diff --git a/docs/source/inspect.rst b/docs/source/inspect.rst new file mode 100644 index 00000000..4c89b0b4 --- /dev/null +++ b/docs/source/inspect.rst @@ -0,0 +1,150 @@ +Inspecting Types +---------------- + +.. currentmodule:: msgspec.inspect + +.. 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 :doc:`jsonschema` 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. + +.. code-block:: python + + >>> 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 :doc:`constraints` will include the constraint information as well: + +.. code-block:: python + + >>> 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 :doc:`structs` are also supported: + +.. code-block:: python + + >>> 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. + +.. code-block:: python + + >>> 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 :ref:`API docs ` for a complete list of types. + +For an example of using these functions, you might find our builtin +:doc:`jsonschema` 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``. + + +.. _OpenAPI: https://www.openapis.org/ +.. _hypothesis: https://hypothesis.readthedocs.io/en/latest/ diff --git a/msgspec/inspect.py b/msgspec/inspect.py index 84bff56f..705101da 100644 --- a/msgspec/inspect.py +++ b/msgspec/inspect.py @@ -555,6 +555,19 @@ def multi_type_info( Returns ------- tuple[Type, ...] + + Examples + -------- + >>> msgspec.inspect.type_info([int, float, list[str]]) + ( + IntType(gt=None, ge=None, lt=None, le=None, multiple_of=None), + FloatType(gt=None, ge=None, lt=None, le=None, multiple_of=None), + ListType( + item_type=StrType(min_length=None, max_length=None, pattern=None), + min_length=None, + max_length=None + ) + ) """ return _Translator(types, protocol).run() @@ -579,6 +592,21 @@ def type_info(type: Any, *, protocol: Literal[None, "msgpack", "json"] = None) - Returns ------- Type + + Examples + -------- + >>> 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]) + ListType( + item_type=IntType(gt=None, ge=None, lt=None, le=None, multiple_of=None), + min_length=None, + max_length=None + ) """ return multi_type_info([type], protocol=protocol)[0]