Skip to content

feat: #2101 Generate AsyncAPI schema for msgspec Structs - #2977

Open
pelazas wants to merge 1 commit into
ag2ai:mainfrom
pelazas:feat/asyncapi-msgspec
Open

feat: #2101 Generate AsyncAPI schema for msgspec Structs#2977
pelazas wants to merge 1 commit into
ag2ai:mainfrom
pelazas:feat/asyncapi-msgspec

Conversation

@pelazas

@pelazas pelazas commented Aug 2, 2026

Copy link
Copy Markdown

Closes #2101

Description

AsyncAPI generation builds payload schemas through Pydantic. Pydantic cannot describe a msgspec.Struct and raises rather than degrading, so today any handler annotated with a Struct takes down the whole schema build:

class User(msgspec.Struct):
    name: str

@broker.subscriber("test")
async def handler(user: User) -> None: ...

AsyncAPI(broker, schema_version="3.0.0").to_specification()
# pydantic.errors.PydanticSchemaGenerationError: Unable to generate
# pydantic-core schema for <class 'User'>

Return annotations fail the same way through the publisher path, with PydanticInvalidForJsonSchema instead. So faststream docs gen and docs serve are 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 call move_pydantic_refs() and hoist $defs into components/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:

"User": {
  "title": "User", "type": "object",
  "properties": {
    "name": {"type": "string"},
    "address": {"$ref": "#/components/schemas/Address"}
  },
  "required": ["name", "address"]
},
"Address": {"title": "Address", "type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}

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 MsgSpecSerializer configured. msgspec stays an optional import behind HAS_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

  • Bug fix (a non-breaking change that resolves an issue)
  • New feature (a non-breaking change that adds functionality)

Checklist

  • My code adheres to the style guidelines of this project (just lint shows no errors)
  • I have conducted a self-review of my own code
  • I have made the necessary changes to the documentation
  • My changes do not generate any new warnings
  • I have added tests to validate the effectiveness of my fix or the functionality of my new feature
  • Both new and existing unit tests pass successfully on my local environment
  • I have ensured that static analysis tests are passing by running just static-analysis
  • I have included code examples to illustrate the modifications

Notes on the boxes above, so they mean something:

  • 12 new tests in tests/asyncapi/test_msgspec.py, parametrised over AsyncAPI 2.6.0 and 3.0.0. All 12 fail on main and pass here. The whole tests/asyncapi suite and tests/integrations/msgspec pass unchanged.
  • ruff check, ruff format --check, mypy (510 files) and bandit are all clean; mypy on the touched file is clean on main too, so that is not a pre-existing-error situation.
  • Docs unticked because I did not find a page describing which types AsyncAPI can render — the msgspec docs cover serialization only. Point me at the right page and I will add it.
  • I ran tests directly with pytest rather than just test-coverage, since the justfile runs it inside the Docker Compose stack and I ran locally.

🤖 Generated with Claude Code

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.
@pelazas
pelazas requested a review from Lancetnik as a code owner August 2, 2026 14:07
@CLAassistant

CLAassistant commented Aug 2, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@sobolevn sobolevn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not msgspec.json.schema?

@ApusBerliozi ApusBerliozi added enhancement New feature or request Core Issues related to core FastStream functionality and affects to all brokers labels Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Core Issues related to core FastStream functionality and affects to all brokers enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Generate AsyncAPI from msgspec.Struct

4 participants