Skip to main content

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

Project description

pydantic-marshmallow

CI PyPI version 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:

Operation pydantic-marshmallow Marshmallow Speedup
Simple load 3.0 µs 5.3 µs 1.8x faster
Nested model 3.5 µs 11.6 µs 3.3x faster
Deep nested (4 levels) 5.6 µs 32.3 µs 5.8x faster
Batch (100 items) 255 µs 474 µs 1.9x faster

Benchmarks run on Python 3.11. Run python -m benchmarks.run_benchmarks to reproduce.

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

Installation

pip install pydantic-marshmallow

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.0.1.tar.gz (272.6 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.0.1-py3-none-any.whl (27.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pydantic_marshmallow-1.0.1.tar.gz
  • Upload date:
  • Size: 272.6 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.0.1.tar.gz
Algorithm Hash digest
SHA256 ed490bbeea7afdd9d6c34131caaa2387f8f8b184128f4248fb85525889ebc217
MD5 9d20c7e09ce8f46c18cb27a4ffc90d63
BLAKE2b-256 79a74b17349d93ba9fd2c979a5d9dcf72fac30a080f6d273d0939bb63751f7ba

See more details on using hashes here.

Provenance

The following attestation bundles were made for pydantic_marshmallow-1.0.1.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.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for pydantic_marshmallow-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 91756cef26d8a1099e4a68f371e8fe9614683bc469e8e8d0b2050291f1f7c301
MD5 688796befc6e23d926db655660654699
BLAKE2b-256 a00f6ffd21f1a062befaf48cbd0b56d74ded0fec3a14f04af6a114cc65c09741

See more details on using hashes here.

Provenance

The following attestation bundles were made for pydantic_marshmallow-1.0.1-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