Source code for flump.schemas
from collections import namedtuple
from marshmallow import Schema, fields, post_load, pre_dump
from werkzeug.exceptions import Conflict
from .exceptions import FlumpUnprocessableEntity
EntityData = namedtuple('EntityData', ('id', 'type', 'attributes', 'meta'))
ResponseData = namedtuple('ResponseData', ('data', 'links'))
EntityMetaData = namedtuple('EntityMetaData', ('etag'))
ManyResponseData = namedtuple('ManyResponseData', ('data', 'links', 'meta'))
class EntityMetaSchema(Schema):
etag = fields.Str(dump_only=True)
def make_data_schema(
resource_schema, only=None, partial=False, id_required=False
):
"""
Constructs a Schema describing the main jsonapi format for the
current `resource_schema`.
:param resource_schema: The schema describing the resource. Should be
an instance of :class:`marshmallow.Schema`
:param only: A list or tuple of fields to serialize on the
`resource_schema`, if None, all fields will be
serialized.
:param partial: If True, ignore missing fields on the
`resource_schema` when deserializing.
:param id_required: Whether or not the `id` field of the returned
`JsonApiSchema`
:returns: :class:`make_data_schema.JsonApiSchema`
"""
class JsonApiSchema(Schema):
id = fields.Str(required=id_required)
type = fields.Str(required=True)
attributes = fields.Nested(resource_schema,
required=True, only=only, partial=partial)
meta = fields.Nested(EntityMetaSchema, dump_only=True)
@post_load
def to_entity_data(self, data):
"""
Automagically load the current data to the `EntityData`
namedtuple format. When loading we do not have an ID so this
will be None.
"""
return EntityData(data.get('id'), data['type'],
data['attributes'], None)
return JsonApiSchema
def make_response_schema(resource_schema, only=None, many=False):
"""
Constructs Schema describing the format of a response according to jsonapi.
:param resource_schema: The schema describing the resource. Should be
an instance of :class:`marshmallow.Schema`
:param only: A list or tuple of fields to serialize on the
`resource_schema`, if None, all fields will be
serialized.
:param many: Should be set to True if we are returning multiple
entities.
:returns: :class:`make_response_schema.JsonApiResponseSchema`
"""
data_schema = make_data_schema(resource_schema, only=only)
class LinkSchema(Schema):
self = fields.Str()
first = fields.Str()
last = fields.Str()
next = fields.Str()
prev = fields.Str()
class MetaSchema(Schema):
total_count = fields.Integer()
# This may contain extra data depending on the context. For instance
# the PageSizePagination mixin makes use of this field to include the
# current page and size in the response.
extra = fields.Dict()
class JsonApiResponseSchema(Schema):
data = fields.Nested(data_schema, many=many)
links = fields.Nested(LinkSchema)
meta = fields.Nested(MetaSchema)
return JsonApiResponseSchema
def make_entity_schema(resource_schema, resource_name, data_schema):
"""
Constructs a schema describing the format of POST/PATCH requests for
jsonapi. Provides automatic error checking for the data format.
:param resource_schema: The schema describing the resource. Should be
an instance of :class:`marshmallow.Schema`
:param resource_name: The name of the resource type defined for the API.
:param data_schema: An instance or
:class:`make_data_schema.JsonApiSchema`.
:returns: :class:`make_entity_schema.JsonApiPostSchema`
"""
class JsonApiPostSchema(Schema):
data = fields.Nested(data_schema)
@post_load
def check_for_errors(self, loaded_data):
"""
Checks for errors with the ID or respource type, raising the
errors specified in jsonapi if found.
"""
resource = loaded_data.get('data')
if not resource:
raise FlumpUnprocessableEntity
if resource.type != resource_name:
err_msg = (
'Url specified the creation of "{}" but type '
'specified "{}".'
).format(resource_name, resource.type)
raise Conflict(err_msg)
return resource
return JsonApiPostSchema