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):
if not hasattr(meta, 'sql_session'):
meta.sqla_session = Session
super(BaseOpts, self).__init__(meta)
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
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'}