Skip to main content

Marshmallow-Peewee

Peewee ORM integration for the Marshmallow (de)serialization library. One Meta option turns a peewee model into a marshmallow schema.

Tests Status PYPI Version Python Versions

Requirements

  • python >= 3.11
  • marshmallow >= 4, < 5 (marshmallow 3 is not supported)
  • peewee

Installation

$ pip install marshmallow-peewee

Quickstart

import datetime as dt

import marshmallow as ma
import peewee as pw

db = pw.SqliteDatabase(":memory:")


class Role(pw.Model):
    name = pw.CharField(255, default="user")

    class Meta:
        database = db


class User(pw.Model):
    created = pw.DateTimeField(default=dt.datetime.now)
    name = pw.CharField(255)
    title = pw.CharField(127, null=True)
    active = pw.BooleanField(default=True)
    rating = pw.IntegerField(default=0)
    role = pw.ForeignKeyField(Role)

    class Meta:
        database = db


db.create_tables([Role, User])

from marshmallow_peewee import ModelSchema


class UserSchema(ModelSchema):
    class Meta:
        model = User


role = Role.create()
user = User.create(name="Mike", role=role)

data = UserSchema().dump(user)
# {
#     "id": "1",
#     "created": "2026-01-01T12:00:00",
#     "name": "Mike",
#     "title": None,
#     "active": True,
#     "rating": 0,
#     "role": "1",
# }

# The primary key is dump-only, so drop it when loading a full dump.
user = UserSchema().load(data, unknown=ma.EXCLUDE)
assert user.name == "Mike"

ModelSchema generates a marshmallow field for every peewee field when the schema class is created. Fields declared on the schema class override generated ones. Without Meta.model the schema behaves like a regular marshmallow.Schema.

Meta options

Option Default Description
model None Peewee model to map.
model_converter DefaultConverter Converter class that builds the fields.
dump_only_pk True Make the integer primary key dump-only.
string_keys True Serialize integer keys, including foreign keys, as strings.
id_keys False Use the column name (role_id) instead of the field name (role) for foreign keys.

Options are resolved in this order: the schema Meta, the base schema Meta, then global defaults. Standard marshmallow options (fields, exclude, unknown, dateformat, and others) keep working on top of the generated fields.

class UserSchema(ModelSchema):
    class Meta:
        model = User
        string_keys = False
        id_keys = True
        dump_only_pk = False

Field mapping

Peewee Marshmallow
CharField String with Length
TextField, FixedCharField String
IntegerField, BigIntegerField, SmallIntegerField Integer
AutoField String or Integer (see below)
FloatField, DoubleField Float
DecimalField Decimal (dumped as a string)
DateTimeField, DateField, TimeField DateTime, Date, Time
TimestampField DateTime
IPField String
BooleanField Boolean
UUIDField UUID
ForeignKeyField, DeferredForeignKey ForeignKey
anything else Raw

A few details that are not obvious from the table:

  • AutoField becomes String when string_keys is on and Integer otherwise. It is dump-only unless dump_only_pk is off. Any integer primary key (pw.IntegerField(primary_key=True)) behaves the same way.
  • null=True sets allow_none and load_default=None. A non-null field with a default gets that default as load_default; a non-null field without a default is required.
  • choices are turned into a OneOf validator, help_text is stored in field.metadata["description"].
  • Decimal fields dump as strings so Schema.dumps() and json.dumps() work; loading accepts a string, an int or a float.
  • Foreign keys are serialized from obj.__data__, so dumping a foreign key does not query the related table.

Foreign keys

By default a foreign key is dumped as its raw value: a string when string_keys is on, an integer otherwise. With id_keys=True the field is named after the column (role_id). Both forms load back into the same model. On load a foreign key accepts the related model instance or a value the related primary key can hold; anything else is a ValidationError.

Related builds a nested schema from the related model and lets you dump and load the whole object:

from marshmallow_peewee import Related


class UserSchema(ModelSchema):
    role = Related(unknown=ma.EXCLUDE)

    class Meta:
        model = User


data = UserSchema().dump(user)
# {..., "role": {"id": "1", "name": "user"}}

user = UserSchema().load(data, unknown=ma.EXCLUDE)
assert isinstance(user.role, Role)

Nested schemas dump the primary key too, so pass unknown=ma.EXCLUDE to the field if you load nested dumps back.

Related also accepts an explicit schema, and works for reverse relations (for example Role.user_set = Related()); call Related() without arguments only where Meta.model is available, otherwise the field cannot be initialized.

FKNested does the same but reads the related object from peewee's relation cache (obj.__rel__) instead of querying. The relation must already be loaded, for example with User.select(User, Role).join(Role) or by accessing user.role before dumping. Otherwise the dump raises KeyError.

from marshmallow_peewee import FKNested


class UserSchema(ModelSchema):
    role_data = FKNested(Role, only=("name",), attribute="role")

    class Meta:
        model = User
        exclude = ("role",)

ForeignKey is the field class used for foreign key columns. Subclass it if you need to change how a raw key is serialized.

Global defaults

from marshmallow_peewee import setup

setup(id_keys=True, string_keys=False)  # Applies to schemas declared later

setup mutates process-wide defaults, so call it once at startup, not per request or per schema. Unknown option names raise TypeError. Per-schema Meta options always win.

Custom conversion

Register a marshmallow field for a peewee field class:

import marshmallow as ma
import peewee as pw

from marshmallow_peewee import DefaultConverter

DefaultConverter.register(pw.BooleanField, ma.fields.String)

For more control, register a builder function. It receives the peewee field, the schema options, and the standard field parameters:

@DefaultConverter.register(pw.BooleanField)
def convert_boolean(field, opts, **params):
    params["load_default"] = "yes"
    return ma.fields.String(**params)

To change fields for one schema only, register on a converter subclass. The subclass gets its own mapping; DefaultConverter is not affected:

class CustomConverter(DefaultConverter):
    pass


CustomConverter.register(pw.BooleanField, ma.fields.String)


class UserSchema(ModelSchema):
    class Meta:
        model = User
        model_converter = CustomConverter

The lookup walks the peewee field MRO, so subclasses of known fields are converted as their closest known parent.

Limitations

  • Marshmallow 3 is not supported. Use marshmallow-peewee 5.x if you still need it.
  • Meta.fields and Meta.exclude only filter fields that exist on the model or are declared on the schema. Marshmallow 4 removed implicit field creation.
  • A full dump contains the dump-only primary key, so loading it back requires unknown=ma.EXCLUDE or removing the key.
  • FKNested does not trigger queries and requires a loaded relation.
  • Nested schemas are generated from models. Fetching the right rows is up to you.

Bug tracker

If you have any suggestions, bug reports or annoyances please report them to the issue tracker at https://github.com/klen/marshmallow-peewee/issues

Contributing

Development of the project happens at: https://github.com/klen/marshmallow-peewee

License

Licensed under a MIT License

Metadata

Release files for marshmallow-peewee 6.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for marshmallow-peewee 6.0.0
File Size Uploaded Provenance Actions
marshmallow_peewee-6.0.0.tar.gz 9.6 kB Without provenance Details for marshmallow_peewee-6.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for marshmallow-peewee 6.0.0
File Interpreter ABI Platform Actions
marshmallow_peewee-6.0.0-py3-none-any.whl Python 3 none any Details for marshmallow_peewee-6.0.0-py3-none-any.whl

Total release size: 20.8 kB

Release files / marshmallow_peewee-6.0.0.tar.gz

Download URL marshmallow_peewee-6.0.0.tar.gz
Size 9.6 kB
Tags Source
SHA-256 checksum
How to use checksums
977f0670dc14c524745f3656c9d2dfba215e31e3fec52ad464ceabd5e15c3271
BLAKE2b-256 checksum
How to use checksums
a225512f1255743a4f2627c0f98a178c04b395fa0c1a54c92742e2ea3cb4eeca
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / marshmallow_peewee-6.0.0-py3-none-any.whl

Download URL marshmallow_peewee-6.0.0-py3-none-any.whl
Size 11.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d849737cd703349950edbffd42a2a1f15300cee6c45a34eea5bf2602c6f018dd
BLAKE2b-256 checksum
How to use checksums
f1b6852076520e5374fbefc969625d88505017c769c1b62ba999dbd3c1f6d748
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for marshmallow-peewee 6.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

6.0.0 This release

2 release files No provenance

0.0.1

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page