Recipes

Base Schema I

A common pattern with marshmallow is to define a base Schema class which has common configuration and behavior for your application’s Schemas.

You may want to define a common session object, e.g. a scoped_session to use for all Schemas.

# myproject/db.py
import sqlalchemy as sa
from sqlalchemy import orm

Session = orm.scoped_session(orm.sessionmaker())
Session.configure(bind=engine)
# myproject/schemas.py

from marshmallow_sqlalchemy import ModelSchema

from .db import Session


class BaseSchema(ModelSchema):
    class Meta:
        sqla_session = Session
# myproject/users/schemas.py

from ..schemas import BaseSchema
from .models import User


class UserSchema(BaseSchema):

    # Inherit BaseSchema's options
    class Meta(BaseSchema.Meta):
        model = User

Base Schema II

Here is an alternative way to define a BaseSchema class with a common Session object.

# myproject/schemas.py

from marshmallow_sqlalchemy import ModelSchemaOpts
from .db import Session


class BaseOpts(ModelSchemaOpts):
    def __init__(self, meta, ordered=False):
        if not hasattr(meta, "sqla_session"):
            meta.sqla_session = Session
        super(BaseOpts, self).__init__(meta, ordered=ordered)


class BaseSchema(ModelSchema):
    OPTIONS_CLASS = BaseOpts

This allows you to define class Meta options without having to subclass BaseSchema.Meta.

# myproject/users/schemas.py

from ..schemas import BaseSchema
from .models import User


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

Introspecting Generated Fields

It is often useful to introspect what fields are generated for a ModelSchema.

Generated fields are added to a Schema's _declared_fields attribute.

AuthorSchema._declared_fields["books"]
# <fields.QuerySelectList(default=<marshmallow.missing>, ...>

You can also use marshmallow_sqlalchemy's conversion functions directly.

from marshmallow_sqlalchemy import property2field

id_prop = Author.__mapper__.get_property("id")

property2field(id_prop)
# <fields.Integer(default=<marshmallow.missing>, ...>

Overriding Generated Fields

Any field generated by a ModelSchema can be overridden.

from marshmallow import fields
from marshmallow_sqlalchemy import ModelSchema


class AuthorSchema(ModelSchema):
    # Override books field to use a nested representation rather than pks
    books = fields.Nested(BookSchema, many=True, exclude=("author",))

    class Meta:
        model = Author
        sqla_session = Session

You can use the field_for function to generate a marshmallow Field based on single model property. This is useful for passing additional keyword arguments to the generated field.

from marshmallow_sqlalchemy import ModelSchema, field_for


class AuthorSchema(ModelSchema):
    # Generate a field, passing in an additional dump_only argument
    date_created = field_for(Author, "date_created", dump_only=True)

    class Meta:
        model = Author
        sqla_session = Session

You can customize the keyword arguments passed to a column property’s corresponding marshmallow field by passing the info argument to the Column.

class Book(Model):
    # ...

    abstract = Column(Text(), info=dict(marshmallow=dict(required=True)))

Automatically Generating Schemas For SQLAlchemy Models

It can be tedious to implement a large number of schemas if not overriding any of the generated fields as detailed above. SQLAlchemy has a hook that can be used to trigger the creation of the schemas, assigning them to the SQLAlchemy model property <Model.__marshmallow__>.

from marshmallow_sqlalchemy import ModelConversionError, ModelSchema


def setup_schema(Base, session):
    # Create a function which incorporates the Base and session information
    def setup_schema_fn():
        for class_ in Base._decl_class_registry.values():
            if hasattr(class_, "__tablename__"):
                if class_.__name__.endswith("Schema"):
                    raise ModelConversionError(
                        "For safety, setup_schema can not be used when a"
                        "Model class ends with 'Schema'"
                    )

                class Meta(object):
                    model = class_
                    sqla_session = session

                schema_class_name = "%sSchema" % class_.__name__

                schema_class = type(schema_class_name, (ModelSchema,), {"Meta": Meta})

                setattr(class_, "__marshmallow__", schema_class)

    return setup_schema_fn

An example of then using this:

import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy import event
from sqlalchemy.orm import mapper

# Either import or declare setup_schema here

engine = sa.create_engine("sqlite:///:memory:")
session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()


class Author(Base):
    __tablename__ = "authors"
    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String)

    def __repr__(self):
        return "<Author(name={self.name!r})>".format(self=self)


# Listen for the SQLAlchemy event and run setup_schema.
# Note: This has to be done after Base and session are setup
event.listen(mapper, "after_configured", setup_schema(Base, session))

Base.metadata.create_all(engine)

author = Author(name="Chuck Paluhniuk")
session.add(author)
session.commit()

# Model.__marshmallow__ returns the Class not an instance of the schema
# so remember to instantiate it
author_schema = Author.__marshmallow__()

print author_schema.dump(author).data

This is inspired by functionality from ColanderAlchemy.

Smart Nested Field

To serialize nested attributes to primary keys unless they are already loaded, you can use this custom field.

class SmartNested(fields.Nested):
    def serialize(self, attr, obj, accessor=None):
        if attr not in obj.__dict__:
            return {"id": int(getattr(obj, attr + "_id"))}
        return super(SmartNested, self).serialize(attr, obj, accessor)

An example of then using this:

from marshmallow_sqlalchemy import ModelSchema


class BookSchema(ModelSchema):
    author = SmartNested(AuthorSchema)

    class Meta:
        model = Book
        sqla_session = Session


book = Book(id=1)
book.author = Author(name="Chuck Paluhniuk")
session.add(author)
session.commit()

book = Book.query.get(1)
print(BookSchema().dump(book).data["author"])
# {'id': 1}

book = Book.query.options(joinedload("author")).get(1)
print(BookSchema().dump(book).data["author"])
# {'id': 1, 'name': 'Chuck Paluhniuk'}

Transient Object Creation

Sometimes it might be desirable to deserialize instances that are transient (not attached to a session). In these cases you can specify the transient option in the Meta class of a ModelSchema.

from marshmallow_sqlalchemy import ModelSchema


class AuthorSchema(ModelSchema):
    class Meta:
        model = Author
        transient = True


dump_data = {"id": 1, "name": "John Steinbeck"}
print(AuthorSchema().load(dump_data).data)
# <Author(name='John Steinbeck')>

You may also explicitly specify an override by passing the same argument to load.

from marshmallow_sqlalchemy import ModelSchema


class AuthorSchema(ModelSchema):
    class Meta:
        model = Author
        sqla_session = session


dump_data = {"id": 1, "name": "John Steinbeck"}
print(AuthorSchema().load(dump_data, transient=True).data)
# <Author(name='John Steinbeck')>

Note that transience propagates to relationships (i.e. auto-generated schemas for nested items will also be transient).

See also

See State Management to understand session state management.