feat: support msgspec as another model adapter#479
Conversation
Signed-off-by: Keming <kemingy94@gmail.com>
Signed-off-by: Keming <kemingy94@gmail.com>
Signed-off-by: Keming <kemingy94@gmail.com>
Signed-off-by: Keming <kemingy94@gmail.com>
|
Testing Report Environment: Python 3.14.3, msgspec 0.21.1, Flask 3.1.3, Quart 0.20.0, spectree ✅ What Works CorrectlyRequest validation
OpenAPI schema generation
❌ Bugs and Limitations FoundBug #1 - Query parameters appear as
|
| Behavior | msgspec adapter | pydantic adapter |
|---|---|---|
bool for int field (strict=False) |
Rejected (422) - bool and int are distinct | Accepted (True → 1) |
Array index in error loc |
String "1" |
Integer 1 |
Union[StructA, StructB] without tagging |
TypeError at decoration time (app won't start) |
Works via try/match semantics |
Literal["a", 1, True] (mixed types) |
TypeError at schema gen time |
Supported |
| Shared sub-model across endpoints | Duplicated in components/schemas |
Deduplicated |
| Response validation with dict return | Bypassed (Bug #4) | Enforced |
Notes on Union[StructA, StructB]
msgspec requires all Struct types in a union to be tagged. Without tagging, msgspec.json.schema() raises TypeError at @api.validate() decoration time, meaning the application fails to start. Solution:
class TextBlock(msgspec.Struct, tag=True):
content: str
class ImageBlock(msgspec.Struct, tag=True):
url: str
class Response(msgspec.Struct):
blocks: list[Union[TextBlock, ImageBlock]] # now validNotes on Literal type constraints
Literal["a", 1, True] (mixing str, int, bool in one Literal) is not supported by msgspec.json.schema(). Only homogeneous Literal of None, int, or str values is allowed.
Test Files Produced
| File | Tests | Description |
|---|---|---|
tests/test_flask_msgspec_comprehensive.py |
84 | Full Flask integration: all validation modes, blueprints, hooks, spec |
tests/test_quart_msgspec_comprehensive.py |
52 | Full Quart async integration |
tests/test_msgspec_supplemental.py |
47 | Gap coverage vs independent review findings |
tests/test_msgspec_type_coverage.py |
131 | Exhaustive type coverage: all types, schema accuracy, bugs documented |
Overall Assessment
The integration is fully functional for the primary use case: validating incoming requests and generating an OpenAPI specification for a wide range of types and schemas.
Critical: Bug #4 (response validation bypassed) - breaks the contract of resp=Response(...). One-line fix.
High: Bug #1 (query/header/cookie params → name: null) - makes parameters invisible in generated docs. Requires updating parse_params() in utils.py.
Medium: Bug #2 (wrong Content-Type → crash instead of 422) and Bug #3 (BaseFile dec_hook incompatible with msgspec 0.21.1 post-hook isinstance check).
Low: Bug #5 (schema duplication) - cosmetic, no functional impact.
Tests that I used:
test_flask_spectree_msgspec.py
test_msgspec_supplemental.py
test_msgspec_type_coverage.py
test_quart_spectree_msgspec.py
|
Hi @alexted thanks so much. Recently I'm quite busy. I will fix those bugs as soon as I get some free time. |
|
@kemingy Thanks for the update. No pressure - I understand you are busy. The adapter looks promising, and the bugs I found seem fixable. I am happy to help verify the changes when you get back to it. |
Signed-off-by: Keming <kemingy94@gmail.com>
@alexted Thanks for your help |
|
@kemingy Thank you very much, you are a golden person! |
Signed-off-by: Keming <kemingy94@gmail.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces a model-adapter abstraction that enables using msgspec in addition to pydantic for request/response validation and OpenAPI schema generation (addressing issue #329). It also refactors internal configuration/OpenAPI helper models to be adapter-backed dataclasses and makes pydantic/msgspec installable via extras.
Changes:
- Add a
MsgspecModelAdapterand exposeget_msgspec_model_adapter()alongsideget_pydantic_model_adapter(). - Refactor internal models/config serialization/validation to be adapter-backed (dataclasses) and routed through the configured adapter.
- Update packaging/CI/tests to make
pydanticoptional and to conditionally runmsgspec-dependent tests.
Reviewed changes
Copilot reviewed 42 out of 43 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Adds msgspec and moves pydantic to an optional extra in the lock metadata. |
| pyproject.toml | Makes dependencies empty and introduces pydantic/msgspec optional-dependency extras; adds pytest marker. |
| README.md | Updates install instructions to use extras and documents selecting an adapter. |
| Makefile | Adjusts import/test targets to install pydantic extra explicitly; adds msgspec-aware test target. |
| .github/workflows/pythonpackage.yml | Splits CI to run msgspec tests on CPython and “without msgspec” on PyPy. |
| spectree/model_adapter/protocol.py | Expands adapter protocol (adds basefile, updates is_model_instance signature). |
| spectree/model_adapter/init.py | Adds get_msgspec_model_adapter() and renames/removes default-adapter accessor. |
| spectree/model_adapter/pydantic_adapter.py | Enhances pydantic adapter to support more model shapes and schema generation changes. |
| spectree/model_adapter/msgspec_adapter.py | Introduces msgspec adapter implementation and schema/error shaping. |
| spectree/_types.py | Introduces ModelAdapterType alias and updates hook/JSON types to builtin generics. |
| spectree/utils.py | Updates typing and hook signatures to use ModelAdapterType; tweaks validation logging. |
| spectree/spec.py | Threads the adapter through config validation; updates schema lifting and Tag/server/security dumping. |
| spectree/response.py | Refactors typing to builtin generics and updates adapter binding/types. |
| spectree/plugins/base.py | Updates response-validation flow to use adapter-provided is_model_instance. |
| spectree/plugins/falcon_plugin.py | Minor typing modernizations. |
| spectree/plugins/werkzeug_utils.py | Minor typing modernizations for response unpacking. |
| spectree/models.py | Refactors OpenAPI helper models to adapter-backed dataclasses; changes validation rules/messages. |
| spectree/config.py | Refactors configuration to adapter-backed dataclasses and updates serialization helpers. |
| spectree/errors.py | Adds new internal exception types for validation/field errors. |
| spectree/dataclass_model.py | Adds adapter-backed dataclass normalization/validation/serialization utilities. |
| spectree/dataclass_validator.py | Removes the previous custom dataclass validator implementation. |
| spectree/init.py | Exposes adapter getters from package root and removes exported BaseFile. |
| docs/source/adapter.rst | Documents the msgspec adapter module in Sphinx docs. |
| examples/common.py | Updates BaseFile import path for pydantic adapter. |
| examples/falcon_msgspec_demo.py | Adds a new example demonstrating msgspec models with Falcon. |
| tests/conftest.py | Skips msgspec tests when msgspec isn’t installed. |
| tests/common.py | Updates tests to use the pydantic adapter getter and new BaseFile import. |
| tests/test_utils.py | Updates adapter getter usage and validation-error model references. |
| tests/test_spec.py | Updates validation-error model references; adds tests for naming strategies in refs/components. |
| tests/test_response.py | Updates adapter getter usage and validation-error model used in response spec tests. |
| tests/test_pydantic.py | Updates adapter usage and adapts to protocol changes. |
| tests/test_base_plugin.py | Updates adapter getter usage and response validation invocation. |
| tests/test_plugin_flask.py | Minor decorator formatting simplification. |
| tests/test_config.py | Updates to adapter-backed configuration validation and new exception types. |
| tests/test_msgspec.py | Adds msgspec adapter unit tests and internal config validation tests. |
| tests/test_plugin_with_msgspec.py | Adds Flask plugin integration tests using msgspec models (incl. file upload). |
| tests/test_plugin_falcon_msgspec.py | Adds Falcon plugin integration tests using msgspec models. |
| tests/import_module/test_msgspec_plugin.py | Adds import smoke-test for msgspec adapter/plugin wiring. |
| tests/snapshots/test_plugin/test_plugin_spec[starlette][full_spec].json | Updates snapshots for schema/name changes due to adapter refactor. |
| tests/snapshots/test_plugin/test_plugin_spec[flask][full_spec].json | Updates snapshots for schema/name changes due to adapter refactor. |
| tests/snapshots/test_plugin/test_plugin_spec[flask_view][full_spec].json | Updates snapshots for schema/name changes due to adapter refactor. |
| tests/snapshots/test_plugin/test_plugin_spec[flask_blueprint][full_spec].json | Updates snapshots for schema/name changes due to adapter refactor. |
| tests/snapshots/test_plugin/test_plugin_spec[falcon][full_spec].json | Updates snapshots for schema/name changes due to adapter refactor. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- fix the pydantic json_schema_extra - fix is_model_instance, check if value is instance of model, and model is subclass of the model adapter - fix get_model_key when it's annotated or a list Signed-off-by: Keming <kemingy94@gmail.com>
Signed-off-by: Keming <kemingy94@gmail.com>
Uh oh!
There was an error while loading. Please reload this page.