Source code for restfulchemy.router

"""
    restfulchemy.router
    ~~~~~~~~~~~~~~~~~~~

    Tools for automatically routing API url paths to resources.

    Work in progress, should not be used as anything other than
    a proof of concept at this point.

    :copyright: (c) 2016 by Nicholas Repole and contributors.
                See AUTHORS for more details.
    :license: MIT - See LICENSE for more details.
"""
from mqlalchemy import convert_to_alchemy_type
from restfulchemy.parser import (
    parse_embeds, parse_fields, parse_filters, parse_offset_limit, parse_sorts)
from restfulchemy.fields import EmbeddedField, RelationshipUrl
from restfulchemy.resource import BadRequestException


[docs]def generic_api_router_get(path, api_resource, query_params, strict=True): """Generic API router for GET requests. :param str path: The resource path specified. :param api_resource: A resource instance. :type api_resource: :class:`~restfulchemy.resource.ModelResource` :param dict query_params: Dictionary of query parameters. :param bool strict: If `True`, raise non fatal errors rather than ignoring them. :return: If this is a single entity query, an individual resource or `None`. If this is a collection query, a list of resources. """ split_path = path.split("/") # remove empty string if path started with a slash if len(split_path) > 0 and split_path[0] == "": split_path.pop(0) id_keys = api_resource.schema_class().id_keys fields = parse_fields(query_params) embeds = parse_embeds(query_params) if len(split_path) == 1: # e.g. /albums/ filters = parse_filters( api_resource.model, query_params, convert_key_names_func=api_resource.convert_key_name, gettext=api_resource.gettext) offset, limit = parse_offset_limit( query_params, api_resource.page_max_size, gettext=api_resource.gettext) sorts = parse_sorts( query_params) return api_resource.get_collection( filters=filters, fields=fields, embeds=embeds, sorts=sorts, offset=offset, limit=limit, strict=strict) elif len(split_path) < (len(id_keys) + 1): # e.g. /resource/<key_one_of_two/ # resource that has a multi key identifier; only one provided raise BadRequestException() elif len(split_path) == (len(id_keys) + 1): # e.g. /albums/<album_id> # simple api get ident = () for key in split_path[1:(len(id_keys) + 1)]: ident = ident + (key, ) return api_resource.get( ident=ident, fields=fields, embeds=embeds, strict=strict) else: # subresource # e.g. /albums/1/tracks or /albums/1/tracks/5 sub_split_path = split_path[(len(id_keys) + 1):] # get the parent obj parent_query = api_resource.session.query(api_resource.model) for i, id_key in enumerate(id_keys): # TODO - error handling here model_attr = getattr(api_resource.model, id_key) target_type = type(model_attr.property.columns[0].type) value = convert_to_alchemy_type(split_path[i+1], target_type) parent_query = parent_query.filter(model_attr == value) parent = parent_query.first() if parent is None: # Not found exception raise BadRequestException() sub_resource_name = api_resource.convert_key_name(sub_split_path[0]) sub_resource_field = api_resource.schema_class().fields.get( sub_resource_name) if isinstance(sub_resource_field, EmbeddedField): if isinstance(sub_resource_field.default_field, RelationshipUrl): sub_resource_api_class = ( sub_resource_field.default_field.resource_class) # TODO - Error handling here sub_resource_api = sub_resource_api_class( session=api_resource.session, gettext=api_resource.gettext) sub_id_keys = sub_resource_api.schema_class().id_keys query_session = sub_resource_api.session query_session = query_session.query(sub_resource_api.model) query_session = query_session.with_parent(parent, sub_resource_name) if len(sub_split_path) == 1: # e.g. /albums/1/tracks filters = parse_filters( sub_resource_api.model, query_params, convert_key_names_func=sub_resource_api.convert_key_name, gettext=sub_resource_api.gettext) offset, limit = parse_offset_limit( query_params, sub_resource_api.page_max_size, gettext=sub_resource_api.gettext) sorts = parse_sorts( query_params) return sub_resource_api.get_collection( filters=filters, fields=fields, embeds=embeds, sorts=sorts, offset=offset, limit=limit, session=query_session, strict=strict) elif len(sub_split_path) < (len(sub_id_keys) + 1): # e.g. /albums/1/some_resource/<key_one_of_two> # sub resource has a multi key identifier; only one provided raise BadRequestException() elif len(sub_split_path) == (len(sub_id_keys) + 1): # e.g. /albums/1/tracks/3 # sub resource collection get ident = () for key in sub_split_path[1:(len(sub_id_keys) + 1)]: ident = ident + (key, ) return sub_resource_api.get( ident=ident, fields=fields, embeds=embeds, strict=strict, session=query_session) else: raise BadRequestException()
[docs]def generic_api_router(method, path, api_resource, query_params, data=None, content_type="json", strict=True): """Route requests based on path and resource. :param str path: The resource path specified. This should not include the root ``/api`` or any versioning info. :param api_resource: An already initialized resource instance. :type api_resource: :class:`~restfulchemy.resource.ResourceABC` :param dict query_params: Dictionary of query parameters, likely provided as part of a request. :param bool strict: If `True`, faulty pagination info, fields, or embeds will result in an error being raised rather than silently ignoring them. """ if method.toLower() == "GET": return generic_api_router_get(path, api_resource, query_params, strict) else: # TODO - Finish this! raise BadRequestException("Method not available.")