**********
Quickstart
**********

.. note:: The following guide assumes some familiarity with the marshmallow API. To learn more about marshmallow, see its official documentation at `https://marshmallow.readthedocs.io <https://marshmallow.readthedocs.io>`_.

Declaring schemas
=================

Let’s start with a basic post “model”.

.. code-block:: python

    class Post:
        def __init__(self, id, title):
            self.id = id
            self.title = title

Declare your schemas as you would with marshmallow.

A :class:`.Schema` **MUST** define:

- An ``id`` field
- The ``type_`` class Meta option

It is **RECOMMENDED** to set strict mode to `True`.

Automatic self-linking is supported through these Meta options:

- ``self_url`` specifies the URL to the resource itself
- ``self_url_kwargs`` specifies replacement fields for `self_url`
- ``self_url_many`` specifies the URL the resource when a collection (many) are
  serialized

.. code-block:: python

    from marshmallow_jsonapi import Schema, fields


    class PostSchema(Schema):
        id = fields.Str(dump_only=True)
        title = fields.Str()

        class Meta:
            type_ = "posts"
            self_url = "/posts/{id}"
            self_url_kwargs = {"id": "<id>"}
            self_url_many = "/posts/"

These URLs can be auto-generated by specifying ``self_view``, ``self_view_kwargs``
and ``self_view_many`` instead when using the :ref:`flask-integration`.

Serialization
=============

Objects will be serialized to `JSON API documents <http://jsonapi.org/format/#document-structure>`_ with primary data.

.. code-block:: python

    post = Post(id="1", title="Django is Omakase")
    PostSchema().dump(post)
    # {
    #     'data': {
    #         'id': '1',
    #         'type': 'posts',
    #         'attributes': {'title': 'Django is Omakase'},
    #         'links': {'self': '/posts/1'}
    #     },
    #     'links': {'self': '/posts/1'}
    # }

Relationships
=============

The `Relationship <marshmallow_json.fields.Relationship>` field is used to serialize `relationship objects <http://jsonapi.org/format/#document-resource-object-relationships>`_. For example, a Post may have an author and comments associated with it.

.. code-block:: python

    class User:
        def __init__(self, id, name):
            self.id = id
            self.name = name


    class Comment:
        def __init__(self, id, body, author):
            self.id = id
            self.body = body
            self.author = author


    class Post:
        def __init__(self, id, title, author, comments=None):
            self.id = id
            self.title = title
            self.author = author  # User object
            self.comments = [] if comments is None else comments  # Comment objects

To serialize links, pass a URL format string and a dictionary of keyword arguments. String arguments enclosed in `< >` will be interpreted as attributes to pull from the object being serialized. The relationship links can automatically be generated from Flask view names when using the :ref:`flask-integration`.

.. code-block:: python
    :emphasize-lines: 5-10

    class PostSchema(Schema):
        id = fields.Str(dump_only=True)
        title = fields.Str()

        author = fields.Relationship(
            self_url="/posts/{post_id}/relationships/author",
            self_url_kwargs={"post_id": "<id>"},
            related_url="/authors/{author_id}",
            related_url_kwargs={"author_id": "<author.id>"},
        )

        class Meta:
            type_ = "posts"


    user = User(id="94", name="Laura")
    post = Post(id="1", title="Django is Omakase", author=user)
    PostSchema().dump(post)
    # {
    #     'data': {
    #         'id': '1',
    #         'type': 'posts',
    #         'attributes': {'title': 'Django is Omakase'},
    #         'relationships': {
    #             'author': {
    #                 'links': {
    #                     'self': '/posts/1/relationships/author',
    #                     'related': '/authors/94'
    #                 }
    #             }
    #         }
    #     }
    # }

Resource linkages
-----------------

You can serialize `resource linkages <http://jsonapi.org/format/#document-resource-object-linkage>`_ by passing ``include_resource_linkage=True`` and the resource ``type_`` argument.

.. code-block:: python
    :emphasize-lines: 10-12

    class PostSchema(Schema):
        id = fields.Str(dump_only=True)
        title = fields.Str()

        author = fields.Relationship(
            self_url="/posts/{post_id}/relationships/author",
            self_url_kwargs={"post_id": "<id>"},
            related_url="/authors/{author_id}",
            related_url_kwargs={"author_id": "<author.id>"},
            # Include resource linkage
            include_resource_linkage=True,
            type_="users",
        )

        class Meta:
            type_ = "posts"


    PostSchema().dump(post)
    # {
    #     'data': {
    #         'id': '1',
    #         'type': 'posts',
    #         'attributes': {'title': 'Django is Omakase'},
    #         'relationships': {
    #             'author': {
    #                 'data': {'type': 'users', 'id': '94'},
    #                 'links': {
    #                     'self': '/posts/1/relationships/author',
    #                     'related': '/authors/94'
    #                 }
    #             }
    #         }
    #     }
    # }

Compound documents
------------------

`Compound documents <http://jsonapi.org/format/#document-compound-documents>`_ allow to include related resources into the request with the primary resource. In order to include objects, you have to define a :class:`.Schema` for the respective relationship, which will be used to render those objects.

.. code-block:: python
    :emphasize-lines: 10-11

    class PostSchema(Schema):
        id = fields.Str(dump_only=True)
        title = fields.Str()

        comments = fields.Relationship(
            related_url="/posts/{post_id}/comments",
            related_url_kwargs={"post_id": "<id>"},
            many=True,
            include_resource_linkage=True,
            type_="comments",
            # define a schema for rendering included data
            schema="CommentSchema",
        )

        author = fields.Relationship(
            self_url="/posts/{post_id}/relationships/author",
            self_url_kwargs={"post_id": "<id>"},
            related_url="/authors/{author_id}",
            related_url_kwargs={"author_id": "<author.id>"},
            include_resource_linkage=True,
            type_="users",
        )

        class Meta:
            type_ = "posts"


    class CommentSchema(Schema):
        id = fields.Str(dump_only=True)
        body = fields.Str()

        author = fields.Relationship(
            self_url="/comments/{comment_id}/relationships/author",
            self_url_kwargs={"comment_id": "<id>"},
            related_url="/comments/{author_id}",
            related_url_kwargs={"author_id": "<author.id>"},
            type_="users",
            # define a schema for rendering included data
            schema="UserSchema",
        )

        class Meta:
            type_ = "comments"


    class UserSchema(Schema):
        id = fields.Str(dump_only=True)
        name = fields.Str()

        class Meta:
            type_ = "users"

Just as with nested fields the ``schema`` can be a class or a string with a simple or fully qualified class name. Make sure to import the schema beforehand.

Now you can include some data in a dump by specifying the ``include_data`` argument (also supports nested relations via the dot syntax).

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

    armin = User(id="101", name="Armin")
    laura = User(id="94", name="Laura")
    steven = User(id="23", name="Steven")
    comments = [
        Comment(id="5", body="Marshmallow is sweet like sugar!", author=steven),
        Comment(id="12", body="Flask is Fun!", author=armin),
    ]
    post = Post(id="1", title="Django is Omakase", author=laura, comments=comments)

    PostSchema(include_data=("comments", "comments.author")).dump(post)
    # {
    #     'data': {
    #         'id': '1',
    #         'type': 'posts',
    #         'attributes': {'title': 'Django is Omakase'},
    #         'relationships': {
    #             'author': {
    #                 'data': {'type': 'users', 'id': '94'},
    #                 'links': {
    #                     'self': '/posts/1/relationships/author',
    #                     'related': '/authors/94'
    #                 }
    #             },
    #             'comments': {
    #                 'data': [
    #                     {'type': 'comments', 'id': '5'},
    #                     {'type': 'comments', 'id': '12'}
    #                 ],
    #                 'links': {
    #                     'related': '/posts/1/comments'
    #                 }
    #             }
    #         }
    #     },
    #     'included': [
    #         {
    #             'id': '5',
    #             'type': 'comments',
    #             'attributes': {'body': 'Marshmallow is sweet like sugar!'},
    #             'relationships': {
    #                 'author': {
    #                     'data': {'type': 'users', 'id': '23'},
    #                     'links': {
    #                         'self': '/comments/5/relationships/author',
    #                         'related': '/comments/23'
    #                     }
    #                 }
    #             }
    #         },
    #         {
    #             'id': '12',
    #             'type': 'comments',
    #             'attributes': {'body': 'Flask is Fun!'},
    #             'relationships': {
    #                 'author': {
    #                     'data': {'type': 'users', 'id': '101'},
    #                     'links': {
    #                         'self': '/comments/12/relationships/author',
    #                         'related': '/comments/101'
    #                     }
    #                 }
    #             },
    #
    #         },
    #         {
    #             'id': '23',
    #             'type': 'users',
    #             'attributes': {'name': 'Steven'}
    #         },
    #         {
    #             'id': '101',
    #             'type': 'users',
    #             'attributes': {'name': 'Armin'}
    #         }
    #     ]
    # }

Meta Information
================

The :class:`.DocumentMeta` field is used to serialize
the meta object within a `document’s "top level" <http://jsonapi.org/format/#document-meta>`_.

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

    from marshmallow_jsonapi import Schema, fields


    class UserSchema(Schema):
        id = fields.Str(dump_only=True)
        name = fields.Str()
        document_meta = fields.DocumentMeta()

        class Meta:
            type_ = "users"


    user = {"name": "Alice", "document_meta": {"page": {"offset": 10}}}
    UserSchema().dump(user)
    # {
    #     "meta": {
    #         "page": {
    #             "offset": 10
    #         }
    #     },
    #     "data": {
    #         "id": "1",
    #         "type": "users"
    #         "attributes": {"name": "Alice"},
    #     }
    # }

The :class:`.ResourceMeta` field is used to serialize the meta object within a `resource object <http://jsonapi.org/format/#document-resource-objects>`_.

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

    from marshmallow_jsonapi import Schema, fields


    class UserSchema(Schema):
        id = fields.Str(dump_only=True)
        name = fields.Str()
        resource_meta = fields.ResourceMeta()

        class Meta:
            type_ = "users"


    user = {"name": "Alice", "resource_meta": {"active": True}}
    UserSchema().dump(user)
    # {
    #     "data": {
    #         "type": "users",
    #         "attributes": {"name": "Alice"},
    #         "meta": {
    #             "active": true
    #         }
    #     }
    # }

Errors
======

:func:`.Schema.load` and :func:`.Schema.validate` will return JSON API-formatted `Error objects <http://jsonapi.org/format/#error-objects>`_.

.. code-block:: python

    from marshmallow_jsonapi import Schema, fields
    from marshmallow import validate, ValidationError


    class AuthorSchema(Schema):
        id = fields.Str(dump_only=True)
        first_name = fields.Str(required=True)
        last_name = fields.Str(required=True)
        password = fields.Str(load_only=True, validate=validate.Length(6))
        twitter = fields.Str()

        class Meta:
            type_ = "authors"


    author_data = {
        "data": {"type": "users", "attributes": {"first_name": "Dan", "password": "short"}}
    }
    AuthorSchema().validate(author_data)
    # {
    #     'errors': [
    #         {
    #             'detail': 'Missing data for required field.',
    #             'source': {
    #                 'pointer': '/data/attributes/last_name'
    #             }
    #         },
    #         {
    #             'detail': 'Shorter than minimum length 6.',
    #             'source': {
    #                 'pointer': '/data/attributes/password'
    #             }
    #         }
    #     ]
    # }

If an invalid "type" is passed in the input data, an :class:`.IncorrectTypeError` is raised.

.. code-block:: python

    from marshmallow_jsonapi.exceptions import IncorrectTypeError

    author_data = {
        "data": {
            "type": "invalid-type",
            "attributes": {
                "first_name": "Dan",
                "last_name": "Gebhardt",
                "password": "verysecure",
            },
        }
    }

    try:
        AuthorSchema().validate(author_data)
    except IncorrectTypeError as err:
        pprint(err.messages)
    # {
    #     'errors': [
    #         {
    #             'detail': 'Invalid type. Expected "users".',
    #             'source': {
    #                 'pointer': '/data/type'
    #             }
    #         }
    #     ]
    # }

Inflection
==========

You can optionally specify a function to transform attribute names. For example, you may decide to follow JSON API's `recommendation <http://jsonapi.org/recommendations/#naming>`_ to use "dasherized" names.

.. code-block:: python

    from marshmallow_jsonapi import Schema, fields


    def dasherize(text):
        return text.replace("_", "-")


    class UserSchema(Schema):
        id = fields.Str(dump_only=True)
        first_name = fields.Str(required=True)
        last_name = fields.Str(required=True)

        class Meta:
            type_ = "users"
            inflect = dasherize


    UserSchema().dump(user)
    # {
    #     'data': {
    #         'id': '9',
    #         'type': 'users',
    #         'attributes': {
    #             'first-name': 'Dan',
    #             'last-name': 'Gebhardt'
    #         }
    #     }
    # }

.. _flask-integration:

Flask integration
=================

marshmallow-jsonapi includes optional utilities to integrate with Flask.

A Flask-specific schema in `marshmallow_jsonapi.flask` can be used to
auto-generate self-links based on view names instead of hard-coding URLs.

Additionally, the ``Relationship`` field in the `marshmallow_jsonapi.flask`
module allows you to pass view names instead of path templates to generate
relationship links.

.. code-block:: python

    from marshmallow_jsonapi import fields
    from marshmallow_jsonapi.flask import Relationship, Schema


    class PostSchema(Schema):
        id = fields.Str(dump_only=True)
        title = fields.Str()

        author = fields.Relationship(
            self_view="post_author",
            self_url_kwargs={"post_id": "<id>"},
            related_view="author_detail",
            related_view_kwargs={"author_id": "<author.id>"},
        )

        comments = Relationship(
            related_view="post_comments",
            related_view_kwargs={"post_id": "<id>"},
            many=True,
            include_resource_linkage=True,
            type_="comments",
        )

        class Meta:
            type_ = "posts"
            self_view = "post_detail"
            self_view_kwargs = {"post_detail": "<id>"}
            self_view_many = "posts_list"

See `here <https://github.com/marshmallow-code/marshmallow-jsonapi/blob/dev/examples/flask_example.py>`_ for a full example.
