.. _recipes:

*******
Recipes
*******


Base Schema I
=============

A common pattern with `marshmallow` is to define a base `Schema <marshmallow.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 <sqlalchemy.orm.scoping.scoped_session>` to use for all `Schemas <marshmallow.Schema>`.


.. code-block:: python

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

    Session = orm.scoped_session(orm.sessionmaker())
    Session.configure(bind=engine)

.. code-block:: python

    # myproject/schemas.py

    from marshmallow_sqlalchemy import ModelSchema

    from .db import Session

    class BaseSchema(ModelSchema):
        class Meta:
            sqla_session = Session


.. code-block:: python
    :emphasize-lines: 9

    # 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.

.. code-block:: python

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

.. code-block:: python
    :emphasize-lines: 8

    # 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 <marshmallow_sqlalchemy.ModelSchema>`.

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

.. code-block:: python

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


You can also use `marshmallow_sqlalchemy's` conversion functions directly.


.. code-block:: python

    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 <marshmallow_sqlalchemy.ModelSchema>` can be overridden.

.. code-block:: python

    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 <marshmallow_sqlalchemy.field_for>` function to generate a marshmallow `Field <marshmallow.fields.Field>` based on single model property. This is useful for passing additional keyword arguments to the generated field.

.. code-block:: python

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

.. code-block:: python

    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:

.. code-block:: python

    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.

.. code-block:: python

    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:

.. code-block:: python

    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'}
