class BadRequestError(BaseError):
    title = 'Bad Request'
    status_code = 400
    code = 'bad_request'

    def __init__(self, detail):
        self.detail = detail class InvalidTypeForEndpointError(BaseError):
    status_code = 409
    code = 'invalid_type_for_endpoint'
    title = 'Invalid Type For Endpoint'

    def __init__(self, expected, got):
        self.detail = 'Expected {}, got {}'.format(expected, got) class MissingTypeError(BaseError):
    status_code = 409
    code = 'missing_type'
    title = 'Missing Type'
    detail = 'Missing /data/type key in request body' class NotSortableError(BaseError):
    title = 'Not Sortable'
    status_code = 409
    code = 'not_sortable'

    def __init__(self, model, attr_name):
        tmpl = 'The requested field {} on type {} is not a sortable field.'
        self.detail = tmpl.format(model.__jsonapi_type__, attr_name) class PermissionDeniedError(BaseError):
    status_code = 403
    code = 'permission_denied'
    title = 'Permission Denied'

    def __init__(self, permission, model, instance=None, field=None):
        tmpl = '{} denied on {}'
        self.detail = tmpl.format(permission.name, model.__jsonapi_type__)
        if instance is not None:
            self.detail += '.' + str(instance.id)
        if field is not None:
            self.detail += '.' + field class RelationshipNotFoundError(BaseError):
    status_code = 404
    code = 'relationship_not_found'
    title = 'Relationsip Not Found'

    def __init__(self, model, instance, key):
        self.detail = '{}.{}.{} not found'.format(model, instance, key) class ResourceNotFoundError(BaseError):
    status_code = 404
    code = 'resource_not_found'
    title = 'Resource Not Found'

    def __init__(self, model, instance):
        self.detail = '{}.{} not found'.format(model, instance) class ResourceTypeNotFoundError(BaseError):
    title = 'Resource Type Not Found'
    status_code = 404
    code = 'resource_type_not_found'

    def __init__(self, api_type):
        tmpl = 'This backend has not been configured to handle resources of '\
            'type {}.'
        self.detail = tmpl.format(api_type) class ToManyExpectedError(BaseError):
    status_code = 409
    code = 'to_many_expected'
    title = 'To-Many Expected'

    def __init__(self, model, instance, relationship):
        self.detail = '{}.{}.{} is not a to-many relationship'.format(
            model.__jsonapi_type__, instance.id, relationship.key) class ValidationError(BaseError):
    status_code = 409
    code = 'validation_error'
    title = 'Validation Failed'

    def __init__(self, detail):
        self.detail = detail
    from enum import Enum
    from enum34 import Enum
from inflection import dasherize, tableize, underscore
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm.interfaces import MANYTOONE
from sqlalchemy.util.langhelpers import iterate_attributes
from .errors import (BadRequestError, InvalidTypeForEndpointError,
                     MissingTypeError, NotSortableError, PermissionDeniedError,
                     RelationshipNotFoundError, ResourceNotFoundError,
                     ResourceTypeNotFoundError, ToManyExpectedError,
                     ValidationError)
from ._version import __version__
                    built = self._render_full_resource(item, new_include,
                                                       fields)
                    included = built.pop('included')
                    to_ret['included'].update(included)
                    to_ret['included'][(item.__jsonapi_type__, item.id)] = built  # NOQA

        for key in set(orm_desc_keys) - attrs_to_ignore:
            try:
                desc = get_attr_desc(instance, key, AttributeActions.GET)
                if key in local_fields:
                    to_ret['attributes'][instance.__jsonapi_map_to_api__[key]] = desc(instance)  # NOQA
            except PermissionDeniedError:
                continue

        return to_ret

    def _check_instance_relationships_for_delete(self, instance):
        """
        Ensure we are authorized to delete this and all cascaded resources.

        :param instance: The instance to check the relationships of.
        """
        check_permission(instance, None, Permissions.DELETE)
        for rel_key, rel in instance.__mapper__.relationships.items():
            check_permission(instance, rel_key, Permissions.EDIT)

            if rel.cascade.delete:
                if rel.direction == MANYTOONE:
                    related = getattr(instance, rel_key)
                    self._check_instance_relationships_for_delete(related)
                else:
                    instances = getattr(instance, rel_key)
                    for to_check in instances:
                        self._check_instance_relationships_for_delete(to_check)

    def _parse_fields(self, query):
        """
        Parse the querystring args for fields.

        :param query: Dict of query args
        """
        field_args = {
            k: v
            for k, v in query.items() if k.startswith('fields[')
        }

        fields = {}

        for k, v in field_args.items():
            fields[k[7:-1]] = v.split(',')

        return fields

    def _parse_include(self, include):
        """
        Parse the querystring args or parent includes for includes.

        :param include: Dict of query args or includes
        """
        ret = {}
        for item in include:
            if '.' in item:
                local, remote = item.split('.', 1)
            else:
                local = item
                remote = None

            ret.setdefault(local, [])
            if remote:
                ret[local].append(remote)

        return ret

    def _parse_page(self, query):
        """
        Parse the querystring args for pagination.

        :param query: Dict of query args
        """
        args = {k[5:-1]: v for k, v in query.items() if k.startswith('page[')}

        if {'number', 'size'} == set(args.keys()):
            if not args['number'].isdecimal() or not args['size'].isdecimal():
                raise BadRequestError('Page query parameters must be integers')

            number = int(args['number'])
            size = int(args['size'])
            start = number * size

            return start, start + size - 1

        if {'limit', 'offset'} == set(args.keys()):
            if not args['limit'].isdecimal() or not args['offset'].isdecimal():
                raise BadRequestError('Page query parameters must be integers')

            limit = int(args['limit'])
            offset = int(args['offset'])

            return offset, offset + limit - 1

        return 0, None

    def delete_relationship(self, session, data, api_type, obj_id, rel_key):
        """
        Delete a resource or multiple resources from a to-many relationship.

        :param session: SQLAlchemy session
        :param data: JSON data provided with the request
        :param api_type: Type of the resource
        :param obj_id: ID of the resource
        :param rel_key: Key of the relationship to fetch
        """
        model = self._fetch_model(api_type)
        resource = self._fetch_resource(session, api_type, obj_id,
                                        Permissions.EDIT)
        relationship = self._get_relationship(resource, rel_key,
                                              Permissions.DELETE)
        self._check_json_data(data)

        if not isinstance(data['data'], list):

            raise ValidationError('Provided data must be an array.')