feat: #2101 Generate AsyncAPI schema for msgspec Structs - #2977
Open
pelazas wants to merge 1 commit into
Open
Conversation
AsyncAPI generation goes through Pydantic, which cannot describe a
msgspec.Struct and raises rather than degrading. Any handler annotated with a
Struct therefore crashed the whole schema build, so `faststream docs gen` and
`docs serve` were unusable for msgspec users even though 0.6 added msgspec
serialization.
Structs are now kept out of the Pydantic model and their schema is built with
msgspec.json.schema_components() using the same `#/$defs/{name}` reference
template Pydantic emits, so the existing generators hoist nested structs into
components/schemas untouched. The result matches what an equivalent Pydantic
model produces, for arguments and for return annotations alike.
sobolevn
reviewed
Aug 2, 2026
sobolevn
left a comment
Contributor
There was a problem hiding this comment.
I would suggest splitting pydantic / msgspec schema generation. Currently adding structs parameter in many places seems strange :)
| structs live in, using the same `#/$defs/{name}` references Pydantic emits, | ||
| so the generators can hoist them into `components/schemas` unchanged. | ||
| """ | ||
| (schema,), definitions = msgspec.json.schema_components( |
Contributor
There was a problem hiding this comment.
Why not msgspec.json.schema?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2101
Description
AsyncAPI generation builds payload schemas through Pydantic. Pydantic cannot describe a
msgspec.Structand raises rather than degrading, so today any handler annotated with a Struct takes down the whole schema build:Return annotations fail the same way through the publisher path, with
PydanticInvalidForJsonSchemainstead. Sofaststream docs genanddocs serveare unusable for anyone on msgspec, even though 0.6 shipped msgspec serialization.The fix keeps Structs out of the Pydantic model entirely and builds their schema with
msgspec.json.schema_components(), using the same#/$defs/{name}reference template Pydantic emits. That matters because the v2.6.0 and v3.0.0 generators already callmove_pydantic_refs()and hoist$defsintocomponents/schemas— feeding them Pydantic-shaped dicts means nested Structs get hoisted and referenced with no changes to either generator.Output for a Struct is now the same shape as for the equivalent Pydantic model:
Covered: a lone Struct argument (described by its own schema, like a lone Pydantic model), a Struct next to other arguments, nested Structs, Struct return annotations, and with
MsgSpecSerializerconfigured.msgspecstays an optional import behindHAS_MSGSPEC, so nothing changes when it is not installed, and handlers with no Struct take an early exit before any of this runs.Not covered, and worth saying: I only special-case Structs that appear directly as a parameter or return annotation. A Struct nested inside a Pydantic model, or inside a generic like
list[User], still goes through Pydantic and will still raise. Happy to widen it if you'd rather, but that seemed like a bigger design question than the issue was asking.Type of change
Checklist
just lintshows no errors)just static-analysisNotes on the boxes above, so they mean something:
tests/asyncapi/test_msgspec.py, parametrised over AsyncAPI 2.6.0 and 3.0.0. All 12 fail onmainand pass here. The wholetests/asyncapisuite andtests/integrations/msgspecpass unchanged.ruff check,ruff format --check,mypy(510 files) andbanditare all clean;mypyon the touched file is clean onmaintoo, so that is not a pre-existing-error situation.pytestrather thanjust test-coverage, since the justfile runs it inside the Docker Compose stack and I ran locally.🤖 Generated with Claude Code