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__
class JSONAPI(object):
    """ JSON API Serializer for SQLAlchemy ORM models. """

    def __init__(self, base, prefix=''):
        """
        Initialize the serializer.

        :param base: Declarative base instance
        :param namespace: The namespace of the API endpoint
        """
        self.base = base
        self.prefix = prefix
        self.models = {}
        for name, model in base._decl_class_registry.items():
            if name.startswith('_'):
                continue

            prepped_name = self._api_type_for_model(model)
            api_type = getattr(
                model, '__jsonapi_type_override__', prepped_name)

            model_keys = set(model.__mapper__.all_orm_descriptors.keys())
            model_keys |= set(model.__mapper__.relationships.keys())

            model.__jsonapi_attribute_descriptors__ = {}
            model.__jsonapi_rel_desc__ = {}
            model.__jsonapi_permissions__ = {}
            model.__jsonapi_type__ = api_type
            model.__jsonapi_map_to_py__ = {
                dasherize(underscore(x)): x for x in model_keys}
            model.__jsonapi_map_to_api__ = {
                x: dasherize(underscore(x)) for x in model_keys}

            for prop_name, prop_value in iterate_attributes(model):

                if hasattr(prop_value, '__jsonapi_desc_for_attrs__'):
                    defaults = {'get': None, 'set': None}
                    descriptors = model.__jsonapi_attribute_descriptors__
                    for attribute in prop_value.__jsonapi_desc_for_attrs__:
                        descriptors.setdefault(attribute, defaults)
                        attr_desc = descriptors[attribute]
                        for action in prop_value.__jsonapi_action__:
                            attr_desc[action] = prop_value

                if hasattr(prop_value, '__jsonapi_desc_for_rels__'):
                    defaults = {
                        'get': None,
                        'set': None,
                        'append': None,
                        'remove': None
                    }
                    rels_desc = model.__jsonapi_rel_desc__
                    for relationship in prop_value.__jsonapi_desc_for_rels__:
                        rels_desc.setdefault(relationship, defaults)
                        rel_desc = rels_desc[relationship]
                        for action in prop_value.__jsonapi_action__:
                            rel_desc[action] = prop_value

                if hasattr(prop_value, '__jsonapi_check_permission__'):
                    defaults = {
                        'view': [],
                        'create': [],
                        'edit': [],
                        'delete': [],
                        'remove': [],
                        'append': []
                    }
                    perm_obj = model.__jsonapi_permissions__
                    for check_for in prop_value.__jsonapi_chk_perm_for__:
                        perm_obj.setdefault(check_for, defaults)
                        perm_idv = perm_obj[check_for]
                        check_perms = prop_value.__jsonapi_check_permission__
                        for check_perm in check_perms:
                            perm_idv[check_perm] = prop_value
            self.models[model.__jsonapi_type__] = model

    def _api_type_for_model(self, model):
        return dasherize(tableize(model.__name__))

    def _fetch_model(self, api_type):
        if api_type not in self.models.keys():
            raise ResourceTypeNotFoundError(api_type)
        return self.models[api_type]

    def _lazy_relationship(self, api_type, obj_id, rel_key):
        return {
            'self': '{}/{}/{}/relationships/{}'.format(
                self.prefix, api_type, obj_id, rel_key),
            'related': '{}/{}/{}/{}'.format(
                self.prefix, api_type, obj_id, rel_key)
        }

    def _get_relationship(self, resource, rel_key, permission):
        if rel_key not in resource.__mapper__.relationships.keys():
            raise RelationshipNotFoundError(resource, resource, rel_key)
        relationship = resource.__mapper__.relationships[rel_key]
        check_permission(resource, relationship.key, permission)
        return relationship

    def _check_json_data(self, json_data):
        """
        Ensure that the request body is both a hash and has a data key.

        :param json_data: The json data provided with the request
        """
        if not isinstance(json_data, dict):
            raise BadRequestError('Request body should be a JSON hash')
        if 'data' not in json_data.keys():
            raise BadRequestError('Request should contain data key')

    def _fetch_resource(self, session, api_type, obj_id, permission):
        """
        Fetch a resource by type and id, also doing a permission check.

        :param session: SQLAlchemy session
        :param api_type: The type
        :param obj_id: ID for the resource
        :param permission: Permission to check
        """
        if api_type not in self.models.keys():

            raise ResourceTypeNotFoundError(api_type)