class FieldABC(abc.ABC):
    @property
    @abc.abstractmethod
    def key(self) -> str:
        raise NotImplementedError

    @property
    @abc.abstractmethod
    def sp(self) -> JSONPointer:
        raise NotImplementedError

    @property
    @abc.abstractmethod
    def name(self) -> Optional[str]:
        raise NotImplementedError

    @name.setter
    @abc.abstractmethod
    def name(self, value: Optional[str]):
        pass

    @property
    @abc.abstractmethod
    def mapped_key(self) -> Optional[str]:
        raise NotImplementedError

    @mapped_key.setter
    @abc.abstractmethod
    def mapped_key(self, value: Optional[str]):
        pass

    @abc.abstractmethod
    def serialize(self, schema, data, **kwargs):
        """
        Serialize the passed *data*.
        """
        raise NotImplementedError

    @abc.abstractmethod
    def deserialize(self, schema, data, sp, **kwargs):
        """
        Deserialize the raw *data* from the JSON API input document
        and returns it.
        """
        raise NotImplementedError

    @abc.abstractmethod
    def pre_validate(self, schema, data, sp):
        """
        Validates the raw JSON API input for this field. This method is
        called before :meth:`deserialize`.

        :arg ~aiohttp_json_api.schema.BaseSchema schema:
            The schema this field has been defined on.
        :arg data:
            The raw input data
        :arg ~aiohttp_json_api.jsonpointer.JSONPointer sp:
            A JSON pointer to the source of *data*.
        :arg ~aiohttp_json_api.context.JSONAPIContext context:
            A JSON API request context instance
        """
        raise NotImplementedError

    @abc.abstractmethod
    def post_validate(self, schema, data, sp):
        """
        Validates the decoded input *data* for this field. This method is
        called after :meth:`deserialize`.

        :arg ~aiohttp_json_api.schema.BaseSchema schema:
            The schema this field has been defined on.
        :arg data:
            The decoded input data
        :arg ~aiohttp_json_api.jsonpointer.JSONPointer sp:
            A JSON pointer to the source of *data*.
        :arg ~aiohttp_json_api.context.JSONAPIContext context:
            A JSON API request context instance
        """
        raise NotImplementedError class SchemaABC(abc.ABC, metaclass=SchemaMeta):
    OPTIONS_CLASS = SchemaOpts

    class Options:
        pass

    def __init__(self, context):
        """
        Initialize the schema.

        :param ~aiohttp_json_api.context.JSONAPIContext context:
            Resource context instance
        """
        self.ctx = context

    @staticmethod
    @abc.abstractmethod
    def default_getter(field, resource, **kwargs):
        raise NotImplementedError

    @staticmethod
    @abc.abstractmethod
    async def default_setter(field, resource, data, sp, **kwargs):
        raise NotImplementedError

    @classmethod
    @abc.abstractmethod
    def get_field(cls, key) -> FieldABC:
        raise NotImplementedError

    @classmethod
    @abc.abstractmethod
    def get_relationship_field(cls, relation_name, source_parameter=None):
        raise NotImplementedError

    @abc.abstractmethod
    def get_value(self, field, resource, **kwargs):
        raise NotImplementedError

    @abc.abstractmethod
    async def set_value(self, field, resource, data, sp, **kwargs):
        raise NotImplementedError

    @abc.abstractmethod
    def serialize_resource(self, resource, **kwargs):
        raise NotImplementedError

    @abc.abstractmethod
    def serialize_relationship(self, relation_name, resource, *,
                               pagination=None):
        """
        .. seealso::

            http://jsonapi.org/format/#document-resource-object-relationships

        Creates the JSON API relationship object of the relationship
        *relation_name*.

        :arg str relation_name:
            The name of the relationship
        :arg resource:
            A resource object
        :arg ~aiohttp_json_api.pagination.PaginationABC pagination:
            Describes the pagination in case of a *to-many* relationship.

        :rtype: dict
        :returns:
            The JSON API relationship object for the relationship
            *relation_name* of the *resource*
        """
        raise NotImplementedError

    # Validation (pre deserialize)
    # -----------------------

    @abc.abstractmethod
    async def pre_validate_field(self, field, data, sp):
        """
        Validates the input data for a field, **before** it is deserialized.
        If the field has nested fields, the nested fields are validated first.

        :arg BaseField field:
        :arg data:
            The input data for the field.
        :arg aiohttp_json_api.jsonpointer.JSONPointer sp:
            The pointer to *data* in the original document. If *None*, there
            was no input data for this field.
        """
        raise NotImplementedError

    @abc.abstractmethod
    async def pre_validate_resource(self, data, sp, *, expected_id=None):
        """
        Validates a JSON API resource object received from an API client::

            schema.pre_validate_resource(
                data=request.json["data"], sp="/data"
            )

        :arg data:
            The received JSON API resource object
        :arg ~aiohttp_json_api.jsonpointer.JSONPointer sp:
            The JSON pointer to the source of *data*.
        :arg JSONAPIContext context:
            Request context instance
        :arg str expected_id:
            If passed, then ID of resrouce will be compared with this value.
            This is required in update methods
        """
        raise NotImplementedError

    # Validation (post deserialize)
    # -----------------------------

    @abc.abstractmethod
    async def post_validate_resource(self, data):
        """
        Validates the decoded *data* of JSON API resource object.

        :arg ~collections.OrderedDict data:
            The *memo* object returned from :meth:`deserialize_resource`.
        :arg JSONAPIContext context:
            Request context instance
        """
        raise NotImplementedError

    @abc.abstractmethod
    async def deserialize_resource(self, data, sp, *, expected_id=None,
                                   validate=True, validation_steps=None):
        """
        Decodes the JSON API resource object *data* and returns a dictionary
        which maps the key of a field to its decoded input data.

        :arg data:
            The received JSON API resource object
        :arg ~aiohttp_json_api.jsonpointer.JSONPointer sp:
            The JSON pointer to the source of *data*.
        :arg JSONAPIContext context:
            Request context instance
        :arg str expected_id:
            If passed, then ID of resource will be compared with this value.
            This is required in update methods
        :arg bool validate:
            Is validation required?
        :arg tuple validation_steps:
            Required validation steps

        :rtype: ~collections.OrderedDict
        :returns:
            An ordered dictionary which maps a fields key to a two tuple
            ``(data, sp)`` which contains the input data and the source pointer
            to it.
        """
        raise NotImplementedError class Attribute(BaseField):
    """
    .. seealso::

        http://jsonapi.org/format/#document-resource-object-attributes

    An attribute is always part of the resource's JSON API attributes object,
    unless *meta* is set, in which case the attribute appears in the resource's
    meta object.

    Per default, an attribute is mapped to a property on the resource object.
    You can customize this behaviour by implementing your own *getter* and
    *setter*:

    .. code-block:: python3

        class Article(BaseSchema):

            title = Attribute()

    Does the same as:

    .. code-block:: python3

        class Article(BaseSchema):

            title = Attribute()

            @gets('title')
            def title(self, article):
                return article.title

            @sets('title')
            def title(self, article, new_title):
                article.title = new_title
                return None

    :arg bool meta:
        If true, the attribute is part of the resource's *meta* object.
    :arg bool load_only:
        If `True` skip this field during serialization, otherwise
        its value will be present in the serialized data.
    :arg \*\*kwargs:
        The init arguments for the :class:`BaseField`.
    """

    def __init__(self, *, meta: bool = False, load_only=False, **kwargs):
        super(Attribute, self).__init__(**kwargs)
        self.meta = bool(meta)
        self.load_only = load_only
        self._trafaret = None class Relationship(BaseField):
    """
    .. seealso::

        http://jsonapi.org/format/#document-resource-object-relationships

    Additionally to attributes and basic fields, we must know how to *include*
    the related resources in the case of relationships. This class defines
    the common interface of *to-one* and *to-many* relationships (links object,
    meta object, *self* link, *related* link, validation, ...).

    Relationships are always part of the resource's JSON API relationships
    object.

    .. seealso::

        * :class:`~aiohttp_json_api.schema.relationships.ToOne`
        * :class:`~aiohttp_json_api.schema.relationships.ToMany`

    :arg Event require_data:
        If true, the JSON API relationship object must contain the *data*
        member. Must be a :class:`~aiohttp_json_api.schema.common.Event`
        instance.
    :arg bool dereference:
        If true, the relationship linkage is dereferenced automatic when
        decoded. (Implicitly sets *require_data* to Event.ALWAYS)
    :arg Sequence[str] foreign_types:
        A set with all foreign types. If given, this list is used to validate
        the input data. Leave it empty to allow all types.
    """
    relation = None

    def __init__(self, *, dereference: bool = True,
                 require_data: Event = Event.ALWAYS,
                 foreign_types: Sequence[str] = None,
                 links: Sequence[Link] = None,
                 id_field: Attribute = None,
                 **kwargs):
        BaseField.__init__(self, **kwargs)
        self.links = {link.name: link for link in links} if links else {}

        # NOTE: The related resources are loaded by the schema class for
        #       performance reasons (one big query vs many small ones).
        self.dereference = dereference

        self.foreign_types = frozenset(foreign_types or [])

        assert isinstance(require_data, Event)
        self.require_data = require_data
        self.id_field = id_field

    def add_link(self, link: Link):
        """
        Adds a new link to the links object.
        """
        self.links[link.name] = link
        return self

    def validate_resource_identifier(self, schema, data, sp):
        """
        .. seealso::

            http://jsonapi.org/format/#document-resource-identifier-objects

        Asserts that *data* is a JSON API resource identifier with the correct
        *type* value.
        """
        if not isinstance(data, Mapping):
            detail = 'Must be an object.'
            raise InvalidType(detail=detail, source_pointer=sp)

        if not ('type' in data and 'id' in data):
            detail = 'Must contain a "type" and an "id" member.'
            raise InvalidValue(detail=detail, source_pointer=sp)

        if self.foreign_types and not data['type'] in self.foreign_types:
            detail = f'Unexpected type: "{data["type"]}".'
            raise InvalidValue(detail=detail, source_pointer=sp / 'type')

        if self.id_field is not None:
            self.id_field.pre_validate(self, data['id'], sp / 'id')

    def validate_relationship_object(self, schema, data, sp):
        """
        Asserts that *data* is a JSON API relationship object.

        *   *data* is a dictionary
        *   *data* must be not empty
        *   *data* may only have the members *data*, *links* or *meta*.
        *   *data* must contain a *data* member, if :attr:`dereference` or
            :attr:`require_data` is true.
        """
        if not isinstance(data, Mapping):
            detail = 'Must be an object.'
            raise InvalidType(detail=detail, source_pointer=sp)

        if not data:
            detail = 'Must contain at least a "data", "links" or "meta" member.'
            raise InvalidValue(detail=detail, source_pointer=sp)

        if not (data.keys() <= {'links', 'data', 'meta'}):
            unexpected = (data.keys() - {'links', 'data', 'meta'}).pop()
            detail = f"Unexpected member: '{unexpected}'."
            raise InvalidValue(detail=detail, source_pointer=sp)

        if (self.dereference or self.require_data) and 'data' not in data:
            detail = 'The "data" member is required.'
            raise InvalidValue(detail=detail, source_pointer=sp)

    def pre_validate(self, schema, data, sp):
        self.validate_relationship_object(schema, data, sp)
        super(Relationship, self).pre_validate(schema, data, sp) class Tag(Enum):
    GET = 'get'
    SET = 'set'
    VALIDATE = 'validate'
    ADD = 'add'
    REMOVE = 'remove'
    INCLUDE = 'include'
    QUERY = 'query' class Event(Flag):
    """Request event enumeration."""

    GET = auto()
    POST = auto()
    PATCH = auto()
    DELETE = auto()
    NEVER = auto()
    ALWAYS = GET | POST | PATCH | DELETE
    CREATE = POST
    UPDATE = PATCH class Relation(Enum):
    """Types of relations enumeration."""

    TO_ONE = auto()
    TO_MANY = auto() class Step(Enum):
    """Marshalling step enumeration."""

    BEFORE_DESERIALIZATION = auto()
    AFTER_DESERIALIZATION = auto()
    BEFORE_SERIALIZATION = auto()
    AFTER_SERIALIZATION = auto() JSONAPI = 'jsonapi' class HTTPBadRequest(Error):
    """
    HTTP 400 Bad Request.

    The request could not be fulfilled due to the incorrect syntax
    of the request.
    """

    status = HTTPStatus.BAD_REQUEST class HTTPConflict(Error):
    """
    HTTP 409 Conflict.

    The request cannot be completed due to a conflict in the request
    parameters.
    """

    status = HTTPStatus.CONFLICT class InvalidType(ValidationError):
    """
    JSON API invalid type error. HTTP 400 Bad request.

    Raised if an input value (part of a JSON API document) has the wrong type.

    This type of exception is often raised during decoding.

    :seealso: http://jsonapi.org/format/#document-structure
    """ class InvalidValue(ValidationError):
    """
    JSON API invalid value error. HTTP 400 Bad request.

    Raised if an input value (part of a JSON API document) has an invalid
    value.

    :seealso: http://jsonapi.org/format/#document-structure
    """ class ValidationError(HTTPBadRequest):
    """
    JSON API validation error. HTTP 400 Bad request.

    Raised, if the structure of a json document in a request body is invalid.

    Please note, that this does not include semantic errors, like an unknown
    typename.

    This type of exception is used often in the :mod:`jsonapi.validator`
    and :mod:`jsonapi.validation` modules.

    :seealso: http://jsonapi.org/format/#document-structure
    """ MISSING = make_sentinel() def first(iterable, default=None, key=None):
    """
    Return first element of *iterable*.

    Return first element of *iterable* that evaluates to ``True``, else
    return ``None`` or optional *default*.

    >>> first([0, False, None, [], (), 42])
    42
    >>> first([0, False, None, [], ()]) is None
    True
    >>> first([0, False, None, [], ()], default='ohai')
    'ohai'
    >>> import re
    >>> m = first(re.match(regex, 'abc') for regex in ['b.*', 'a(.*)'])
    >>> m.group(1)
    'bc'

    The optional *key* argument specifies a one-argument predicate function
    like that used for *filter()*.  The *key* argument, if supplied, should be
    in keyword form. For example, finding the first even number in an iterable:

    >>> first([1, 1, 3, 4, 5], key=lambda x: x % 2 == 0)
    4

    Contributed by Hynek Schlawack, author of `the original standalone module`_

    .. _the original standalone module: https://github.com/hynek/first
    """
    return next(filter(key, iterable), default) def get_router_resource(app: web.Application, resource: str):
    """Return route of JSON API application for resource."""
    return app.router[f"{app[JSONAPI]['routes_namespace']}.{resource}"] def get_processors(obj, tag: Tag, field: FieldABC,
                   default: Optional[Callee] = None):
    has_processors = getattr(obj, '_has_processors', False)
    if has_processors:
        processor_tag = tag, field.key
        processors = obj.__processors__.get(processor_tag)
        if processors:
            for processor_name in processors:
                processor = getattr(obj, processor_name)
                processor_kwargs = \
                    processor.__processing_kwargs__.get(processor_tag)
                yield processor, processor_kwargs
            return

    if not callable(default):
        return

    yield default, {}
import asyncio
import urllib.parse
from collections import MutableMapping, OrderedDict
from typing import Dict
from .abc.field import FieldABC
from .abc.schema import SchemaABC
from .fields.base import Attribute, Relationship
from .fields.decorators import Tag
from .common import Event, Relation, Step, JSONAPI
from .errors import (
    HTTPBadRequest, HTTPConflict, InvalidType, InvalidValue, ValidationError
)
from .helpers import MISSING, first, get_router_resource, get_processors
    def get_field(cls, key) -> FieldABC:
        return cls._declared_fields[key]

    @classmethod
    def get_relationship_field(cls, relation_name, source_parameter=None):
        try:
            return cls._relationships[cls.opts.deflect(relation_name)]
        except KeyError:
            raise HTTPBadRequest(
                detail=f"Wrong relationship name '{relation_name}'.",
                source_parameter=source_parameter
            )

    @staticmethod
    def default_getter(field, resource, **kwargs):
        if field.mapped_key:
            return getattr(resource, field.mapped_key)
        return None

    @staticmethod
    async def default_setter(field, resource, data, sp, **kwargs):
        if field.mapped_key:
            setattr(resource, field.mapped_key, data)

    def get_value(self, field, resource, **kwargs):
        getter, getter_kwargs = first(
            get_processors(self, Tag.GET, field, self.default_getter)
        )
        return getter(field, resource, **getter_kwargs, **kwargs)

    async def set_value(self, field, resource, data, sp, **kwargs):
        if field.writable is Event.NEVER:
            raise RuntimeError('Attempt to set value to read-only field.')

        setter, setter_kwargs = first(
            get_processors(self, Tag.SET, field, self.default_setter)
        )
        return await setter(field, resource, data, sp, **setter_kwargs,
                            **kwargs)

    def serialize_resource(self, resource, **kwargs) -> MutableMapping:
        """
        .. seealso::

            http://jsonapi.org/format/#document-resource-objects

        :arg resource:
            A resource object
        """
        fieldset = self.ctx.fields.get(self.opts.resource_type)

        fields_map = (
            ('attributes', self._attributes),
            ('relationships', self._relationships),
            ('meta', self._meta),
            ('links', self._links)
        )

        result = OrderedDict((
            ('type', self.opts.resource_type),
            ('id', self.get_object_id(resource)),
        ))

        for key, schema_fields in fields_map:
            for field in schema_fields.values():
                # Ignore 'load_only' field during serialization
                if getattr(field, 'load_only', False):
                    continue

                if fieldset is None or field.name in fieldset:
                    field_data = self.get_value(field, resource, **kwargs)
                    links = None
                    if isinstance(field, Relationship):
                        links = {
                            link.name: link.serialize(self, resource, **kwargs)
                            for link in field.links.values()
                        }
                    # TODO: Validation steps for pre/post serialization
                    result.setdefault(key, OrderedDict())
                    result[key][field.name] = \
                        field.serialize(self, field_data, links=links,
                                        **kwargs)

        result.setdefault('links', OrderedDict())
        if 'self' not in result['links']:
            rid = self.ctx.registry.ensure_identifier(resource)
            route = get_router_resource(self.ctx.request.app, 'resource')
            route_url = route._formatter.format_map({'type': rid.type,
                                                     'id': rid.id})
            route_url = urllib.parse.urlunsplit(
                (self.ctx.request.scheme, self.ctx.request.host, route_url,
                 None, None)
            )
            result['links']['self'] = route_url

        return result

    # Validation (pre deserialize)
    # ----------------------------

    def serialize_relationship(self, relation_name, resource,
                               *, pagination=None):
        field = self.get_relationship_field(relation_name)

        kwargs = dict()
        if field.relation is Relation.TO_MANY and pagination:
            kwargs['pagination'] = pagination
        field_data = self.get_value(field, resource, **kwargs)
        return field.serialize(self, field_data, **kwargs)

    async def pre_validate_field(self, field, data, sp):
        writable = field.writable in (Event.ALWAYS, self.ctx.event)
        if data is not MISSING and not writable:
            detail = f"The field '{field.name}' is readonly."
            raise ValidationError(detail=detail, source_pointer=sp)

        if data is MISSING and field.required in (Event.ALWAYS,
                                                  self.ctx.event):
            if isinstance(field, Attribute):
                detail = f"Attribute '{field.name}' is required."

            elif isinstance(field, Relationship):