marshmallow-dataclass schemas: nested load, dump and validation: working versions (tested 2026-10-15)
Generate marshmallow schemas from dataclasses to load, dump and validate nested records, including typed nested objects, field renaming and path-aware ValidationErrors.
passed6 passed · 0 failed2026-10-15 16:02 UTC- Status
- passed
- Last passed
- 2026-10-15 16:02 UTC
- Tests
- 6 / 6
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38764699834
The code
Exactly the files that passed. Your agent can pull them with get_recipe("marshmallow-dataclass-schema") (install), or see them on GitHub.
marshmallow-dataclass==8.7.1 marshmallow==4.3.1 pytest==9.1.1
from dataclasses import dataclass, field
from datetime import date
from typing import List, Optional
import marshmallow as ma
import marshmallow_dataclass
@dataclass
class Address:
street: str
city: str
@dataclass
class Contact:
email: str = field(metadata={"validate": ma.validate.Email()})
phone: Optional[str] = None
@dataclass
class User:
id: int = field(metadata={"data_key": "user_id"})
name: str = field(metadata={"validate": ma.validate.Length(min=1)})
joined: date
address: Address
contacts: List[Contact] = field(default_factory=list)
nickname: Optional[str] = None
@dataclass
class UserPatch:
name: Optional[str] = None
nickname: Optional[str] = None
class ExcludeUnknown(ma.Schema):
class Meta:
unknown = ma.EXCLUDE
UserSchema = marshmallow_dataclass.class_schema(User)
LenientUserSchema = marshmallow_dataclass.class_schema(User, base_schema=ExcludeUnknown)
PatchSchema = marshmallow_dataclass.class_schema(UserPatch)
from datetime import date
import pytest
from marshmallow import ValidationError
from schemas import (
Address,
Contact,
LenientUserSchema,
PatchSchema,
User,
UserPatch,
UserSchema,
)
RAW = {
"user_id": 7,
"name": "Ada",
"joined": "2024-01-02",
"address": {"street": "1 Main", "city": "Springfield"},
"contacts": [{"email": "[email protected]", "phone": "555"}],
}
def test_load_builds_nested_dataclasses():
user = UserSchema().load(RAW)
assert isinstance(user, User)
assert isinstance(user.address, Address)
assert isinstance(user.contacts[0], Contact)
assert user.joined == date(2024, 1, 2)
def test_dump_round_trips_to_primitives():
user = UserSchema().load(RAW)
dumped = UserSchema().dump(user)
assert dumped == {**RAW, "nickname": None}
assert UserSchema().load(dumped) == user
def test_validation_error_exposes_nested_paths():
bad = {**RAW, "contacts": [{"email": "not-an-email"}]}
with pytest.raises(ValidationError) as exc:
UserSchema().load(bad)
assert exc.value.messages == {"contacts": {0: {"email": ["Not a valid email address."]}}}
def test_missing_nested_record_is_required():
with pytest.raises(ValidationError) as exc:
UserSchema().load({k: v for k, v in RAW.items() if k != "address"})
assert exc.value.messages == {"address": ["Missing data for required field."]}
def test_unknown_fields_are_rejected_by_default():
with pytest.raises(ValidationError) as exc:
UserSchema().load({**RAW, "bogus": True})
assert exc.value.messages == {"bogus": ["Unknown field."]}
assert LenientUserSchema().load({**RAW, "bogus": True}).id == 7
def test_partial_load_requires_optional_fields():
with pytest.raises(TypeError):
UserSchema(partial=True).load({"user_id": 7, "name": "Ada"})
assert PatchSchema().load({"name": "Ada"}) == UserPatch(name="Ada")
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
Flask API args parsed and validated with webargs and marshmallow
Flask routes that parse query strings and JSON bodies through marshmallow schemas via webargs, with JSON 422 validation errors, tested offline with the Flask test client.
Flask API with Flask-SQLAlchemy and marshmallow-sqlalchemy, validation errors as 422
A Flask JSON API that loads and dumps SQLAlchemy models through SQLAlchemyAutoSchema, returns validation errors as 422, and is tested offline with the Flask test client on in-memory sqlite.