Skip to main content

Bring Pydantic's power to Marshmallow: type annotations, validators, and automatic schema generation

Project description

pydantic-marshmallow

CI Python 3.10+ License: MIT

Bridge Pydantic's power with Marshmallow's ecosystem. Use Pydantic models for validation with full Marshmallow compatibility.

📖 Documentation | 🐙 GitHub

Why pydantic-marshmallow?

Get the best of both worlds: Pydantic's speed with Marshmallow's ecosystem.

Performance

pydantic-marshmallow uses Pydantic's Rust-powered validation engine under the hood, delivering significant performance improvements over native Marshmallow—especially for nested data structures.

Performance Comparison

Operation MA 3.x MA 4.x Bridge (MA 3.x) Bridge (MA 4.x)
Simple load 5.3 µs 4.8 µs 2.0 µs 2.0 µs
Nested model 10.9 µs 10.3 µs 2.4 µs 2.4 µs
Deep nested (4 levels) 31.2 µs 31.7 µs 4.2 µs 4.2 µs
Batch (100 items) 454 µs 424 µs 162 µs 162 µs

What the Numbers Show

  1. MA 3.x → MA 4.x: Marshmallow 4.x improved ~10% over 3.x (5.3 → 4.8 µs for simple loads)
  2. MA 3.x → Bridge: Adding Pydantic delivers 2.7x–7.4x speedup over pure MA 3.x
  3. MA 4.x → Bridge: Still 2.4x–7.5x faster than native MA 4.x
  4. Bridge consistency: ~2.0 µs for simple loads regardless of Marshmallow version

The bridge delegates validation to Pydantic's Rust-powered core, bypassing Marshmallow's field processing. This means bridge performance is independent of Marshmallow version—you get consistent speed whether you're on MA 3.x or 4.x.

Benchmarks: Python 3.11, median of 3 runs with IQR outlier removal. Run python -m benchmarks.run_benchmarks to reproduce.

Full benchmark report: See benchmarks/BENCHMARK_REPORT.md for per-type breakdowns, methodology notes, and environment details.

Why it matters

  • Existing Marshmallow projects: Incrementally adopt Pydantic validation without rewriting your API layer
  • Flask/webargs/apispec users: Keep your integrations, get faster validation
  • Performance-sensitive APIs: Nested model validation is 3-6x faster than native Marshmallow

Features

  • Pydantic Validation: Leverage Pydantic's Rust-powered validation engine
  • Marshmallow Compatibility: Works with Flask-Marshmallow, webargs, apispec, SQLAlchemy, and more
  • Zero Drift: Single source of truth - Pydantic model defines the schema
  • Full Hook Support: @pre_load, @post_load, @pre_dump, @post_dump
  • Validators: @validates("field") and @validates_schema decorators
  • Partial Loading: partial=True or partial=('field1', 'field2')
  • Unknown Fields: unknown=RAISE/EXCLUDE/INCLUDE
  • Computed Fields: Pydantic @computed_field support in serialization
  • Metadata Forwarding: Pydantic Field() metadata (description, title, examples, json_schema_extra, etc.) auto-forwarded to Marshmallow for OpenAPI generation
  • Constraint Mapping: Pydantic constraints (min_length, max_length, ge/le, gt/lt, pattern, etc.) mapped to Marshmallow validators for OpenAPI
  • HybridModel Caching: Thread-safe instance caching for hookless schemas

Installation

pip install pydantic-marshmallow

Requirements: Python 3.10+, Pydantic 2.0+, Marshmallow 3.18+ (including 4.x)

Tested Compatibility

Every release is validated against a matrix of Python, Pydantic, and Marshmallow versions via Docker-based CI.

Dependency Latest Tested Supported Range
Python 3.10 – 3.14 3.10+
Pydantic 2.12.5 >=2.0.0, <3.0.0
Marshmallow 3.26.2 / 4.2.2 >=3.18.0, <5.0.0

Last reconciled: 2026-03-16 · Full matrix details in CAPABILITY_MATRIX.md

Docker Test Matrix

The CI matrix covers boundary and latest versions to catch regressions early:

Python MA 3.18 MA 3.26.2 MA 4.2.2 PD 2.0 PD 2.5 PD 2.12.5
3.10 ✅ (PD 2.0)
3.11
3.12
3.13
3.14

Run the matrix locally with python docker/run_tests.py.

Quick Start

Basic Usage

from pydantic import BaseModel, EmailStr, Field
from pydantic_marshmallow import PydanticSchema

class User(BaseModel):
    name: str = Field(min_length=1)
    email: EmailStr
    age: int = Field(ge=0)

class UserSchema(PydanticSchema[User]):
    class Meta:
        model = User

# Use like any Marshmallow schema
schema = UserSchema()
user = schema.load({"name": "Alice", "email": "alice@example.com", "age": 30})
print(user.name)  # "Alice" - it's a Pydantic User instance!

# Serialize back
data = schema.dump(user)
# {"name": "Alice", "email": "alice@example.com", "age": 30}

Using the Decorator

from pydantic_marshmallow import pydantic_schema

@pydantic_schema
class User(BaseModel):
    name: str
    email: EmailStr

# .Schema attribute is automatically added
schema = User.Schema()
user = schema.load({"name": "Alice", "email": "alice@example.com"})

Factory Function

from pydantic_marshmallow import schema_for

UserSchema = schema_for(User)
schema = UserSchema()

Marshmallow Hooks

All standard Marshmallow hooks work:

from marshmallow import pre_load, post_load, validates

class UserSchema(PydanticSchema[User]):
    class Meta:
        model = User

    @pre_load
    def normalize_email(self, data, **kwargs):
        if "email" in data:
            data["email"] = data["email"].lower().strip()
        return data

    @post_load
    def log_user(self, user, **kwargs):
        print(f"Loaded user: {user.name}")
        return user

    @validates("name")
    def validate_name(self, value):
        if value.lower() == "admin":
            raise ValidationError("Cannot use 'admin' as name")

Partial Loading

# Allow all missing required fields
user = schema.load({"name": "Alice"}, partial=True)

# Allow specific missing fields
user = schema.load({"name": "Alice"}, partial=("email", "age"))

Unknown Field Handling

from marshmallow import EXCLUDE, INCLUDE, RAISE

# Reject unknown fields (default)
schema = UserSchema(unknown=RAISE)

# Ignore unknown fields
schema = UserSchema(unknown=EXCLUDE)

# Include unknown fields in result
schema = UserSchema(unknown=INCLUDE)

Field Filtering

# Only include specific fields
schema = UserSchema(only=("name", "email"))

# Exclude specific fields
schema = UserSchema(exclude=("age",))

# Load-only fields (not in dump output)
schema = UserSchema(load_only=("password",))

# Dump-only fields (not in load input)
schema = UserSchema(dump_only=("created_at",))

Computed Fields

from pydantic import computed_field

class User(BaseModel):
    first: str
    last: str

    @computed_field  # type: ignore[misc]
    @property
    def full_name(self) -> str:
        return f"{self.first} {self.last}"

schema = schema_for(User)()
user = User(first="Alice", last="Smith")
data = schema.dump(user)
# {"first": "Alice", "last": "Smith", "full_name": "Alice Smith"}

Dump Options

# Exclude None values
schema.dump(user, exclude_none=True)

# Exclude unset fields
schema.dump(user, exclude_unset=True)

# Exclude fields with default values
schema.dump(user, exclude_defaults=True)

JSON Serialization

Direct JSON string support:

# Deserialize from JSON string
user = schema.loads('{"name": "Alice", "email": "alice@example.com", "age": 30}')

# Serialize to JSON string  
json_str = schema.dumps(user)

# Batch operations
users = schema.loads('[{"name": "Alice", ...}, {"name": "Bob", ...}]', many=True)

Flask-Marshmallow Integration

from flask import Flask
from flask_marshmallow import Marshmallow
from pydantic_marshmallow import schema_for

app = Flask(__name__)
ma = Marshmallow(app)

UserSchema = schema_for(User)

@app.route("/users", methods=["POST"])
def create_user():
    schema = UserSchema()
    user = schema.load(request.json)
    # user is a Pydantic User instance
    return schema.dump(user)

webargs Integration

from webargs.flaskparser import use_args
from pydantic_marshmallow import schema_for

UserSchema = schema_for(User)

@app.route("/users", methods=["POST"])
@use_args(UserSchema(), location="json")
def create_user(user):
    # user is a Pydantic User instance
    return {"message": f"Created {user.name}"}

apispec Integration

from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin

spec = APISpec(
    title="My API",
    version="1.0.0",
    openapi_version="3.0.0",
    plugins=[MarshmallowPlugin()],
)

spec.components.schema("User", schema=UserSchema)

flask-smorest Integration

Build REST APIs with automatic OpenAPI documentation:

from flask import Flask
from flask_smorest import Api, Blueprint
from pydantic import BaseModel, Field
from pydantic_marshmallow import schema_for

app = Flask(__name__)
app.config["API_TITLE"] = "My API"
app.config["API_VERSION"] = "v1"
app.config["OPENAPI_VERSION"] = "3.0.2"

api = Api(app)
blp = Blueprint("users", __name__, url_prefix="/users")

class UserCreate(BaseModel):
    name: str = Field(min_length=1)
    email: str

UserCreateSchema = schema_for(UserCreate)
UserSchema = schema_for(User)

@blp.post("/")
@blp.arguments(UserCreateSchema)
@blp.response(201, UserSchema)
def create_user(data):
    # data is a Pydantic UserCreate instance
    user = User(id=1, name=data.name, email=data.email)
    return UserSchema().dump(user)

api.register_blueprint(blp)

flask-rebar Integration

Build REST APIs with automatic Swagger documentation:

from flask import Flask
from flask_rebar import Rebar, get_validated_body
from pydantic_marshmallow import schema_for

app = Flask(__name__)
rebar = Rebar()
registry = rebar.create_handler_registry()

UserCreateSchema = schema_for(UserCreate)
UserSchema = schema_for(User)

@registry.handles(
    rule="/users",
    method="POST",
    request_body_schema=UserCreateSchema(),
    response_body_schema=UserSchema(),
)
def create_user():
    data = get_validated_body()  # Pydantic UserCreate instance
    user = User(id=1, name=data.name, email=data.email)
    return UserSchema().dump(user)

rebar.init_app(app)

SQLAlchemy Pattern

Use Pydantic for API validation alongside SQLAlchemy ORM:

from sqlalchemy.orm import Session
from pydantic_marshmallow import schema_for

# Pydantic model for API validation
class UserCreate(BaseModel):
    name: str = Field(min_length=1)
    email: str

UserCreateSchema = schema_for(UserCreate)

def create_user(session: Session, data: dict):
    # Validate API input with Pydantic
    schema = UserCreateSchema()
    validated = schema.load(data)  # Returns Pydantic model
    
    # Create ORM object from validated data
    orm_user = UserModel(name=validated.name, email=validated.email)
    session.add(orm_user)
    session.commit()
    return orm_user

HybridModel

For models that need both Pydantic and Marshmallow APIs:

from pydantic_marshmallow import HybridModel

class User(HybridModel):
    name: str
    email: EmailStr

# Use as Pydantic model
user = User(name="Alice", email="alice@example.com")

# Use Marshmallow-style loading
user = User.ma_load({"name": "Alice", "email": "alice@example.com"})

# Get the Marshmallow schema class
schema_class = User.marshmallow_schema()

Error Handling

Validation errors are raised as BridgeValidationError, which extends Marshmallow's ValidationError:

from pydantic_marshmallow import BridgeValidationError

try:
    user = schema.load({"name": "", "email": "invalid"})
except BridgeValidationError as e:
    print(e.messages)
    # {'name': ['String should have at least 1 character'],
    #  'email': ['value is not a valid email address']}
    print(e.valid_data)
    # {} - fields that passed validation

API Reference

PydanticSchema

A Marshmallow schema backed by a Pydantic model.

Class Methods:

  • from_model(model, **meta_options) - Create a schema class from a Pydantic model

Instance Methods:

  • load(data, *, many, partial, unknown, return_instance) - Deserialize data
  • dump(obj, *, many, exclude_none, exclude_unset, exclude_defaults) - Serialize data
  • validate(data, *, many, partial) - Validate without deserializing

Hooks:

  • on_bind_field(field_name, field_obj) - Called when a field is bound
  • handle_error(error, data, *, many) - Custom error handling

Factory Functions

  • schema_for(model, **meta_options) - Create a schema class from a model
  • pydantic_schema - Decorator that adds .Schema to a model

HybridModel

A Pydantic model with built-in Marshmallow support.

Class Methods:

  • marshmallow_schema() - Get the Marshmallow schema class
  • ma_load(data, **kwargs) - Load using Marshmallow
  • ma_loads(json_str, **kwargs) - Load from JSON string

Instance Methods:

  • ma_dump(**kwargs) - Dump using Marshmallow
  • ma_dumps(**kwargs) - Dump to JSON string

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pydantic_marshmallow-1.2.0.tar.gz (318.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pydantic_marshmallow-1.2.0-py3-none-any.whl (33.4 kB view details)

Uploaded Python 3

File details

Details for the file pydantic_marshmallow-1.2.0.tar.gz.

File metadata

  • Download URL: pydantic_marshmallow-1.2.0.tar.gz
  • Upload date:
  • Size: 318.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pydantic_marshmallow-1.2.0.tar.gz
Algorithm Hash digest
SHA256 a9ab4ce5134c411af2907048f6122e380513d6121f468c8e13513ed09c7046e3
MD5 5caad18c74caf994133fbe129cc9ab3b
BLAKE2b-256 ba890be0c56c80dc7462d1732fe3972fb3656d66581a0aeaeb8a86529805a953

See more details on using hashes here.

Provenance

The following attestation bundles were made for pydantic_marshmallow-1.2.0.tar.gz:

Publisher: release.yml on mockodin/pydantic-marshmallow

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pydantic_marshmallow-1.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pydantic_marshmallow-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c158df2125734a3fdcc327272f6b7b58b7c911e6164921983aab246e07c98935
MD5 526e0c4945016f6449602fa9595ddc92
BLAKE2b-256 3a96b703a0fe6c84f3628e53fe2e9a1bcfc532631e830bfa6a745eb0bc1f272b

See more details on using hashes here.

Provenance

The following attestation bundles were made for pydantic_marshmallow-1.2.0-py3-none-any.whl:

Publisher: release.yml on mockodin/pydantic-marshmallow

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page