Metadata-Version: 2.4
Name: marshmallow-peewee
Version: 6.0.0
Summary: Peewee ORM integration with the Marshmallow (de)serialization library
Keywords: marshmallow,peewee,orm,serialization,deserialization
Author: Kirill Klenov
Author-email: Kirill Klenov <horneds@gmail.com>
License-Expression: MIT
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Requires-Dist: peewee
Requires-Dist: marshmallow>=4,<5
Requires-Python: >=3.11, <4
Project-URL: Homepage, https://github.com/klen/marshmallow-peewee
Project-URL: Repository, https://github.com/klen/marshmallow-peewee
Description-Content-Type: text/markdown

# Marshmallow-Peewee

[Peewee ORM](https://github.com/coleifer/peewee) integration for the
[Marshmallow](https://github.com/marshmallow-code/marshmallow) (de)serialization
library. One `Meta` option turns a peewee model into a marshmallow schema.

[![Tests Status](https://github.com/klen/marshmallow-peewee/workflows/tests/badge.svg)](https://github.com/klen/marshmallow-peewee/actions)
[![PYPI Version](https://img.shields.io/pypi/v/marshmallow-peewee)](https://pypi.org/project/marshmallow-peewee/)
[![Python Versions](https://img.shields.io/pypi/pyversions/marshmallow-peewee)](https://pypi.org/project/marshmallow-peewee/)

## Requirements

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

## Installation

```shell
$ pip install marshmallow-peewee
```

## Quickstart

```python
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](#global-defaults). Standard marshmallow options (`fields`,
`exclude`, `unknown`, `dateformat`, and others) keep working on top of the
generated fields.

```python
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:

```python
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`.

```python
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

```python
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:

```python
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:

```python
@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:

```python
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](http://opensource.org/licenses/MIT)
