Source code for aiohttp_json_api

"""JSON API implementation for aiohttp."""

import inspect
from collections import MutableMapping, Sequence

__author__ = """Vladimir Bolshakov"""
__email__ = 'vovanbo@gmail.com'
__version__ = '0.37.0'


[docs]def setup_app_registry(app, registry_class, config): """Set up JSON API application registry.""" from .common import ALLOWED_MEMBER_NAME_REGEX, logger, JSONAPI from .registry import Registry from .abc.schema import SchemaABC from .abc.contoller import ControllerABC if registry_class is not None: if not issubclass(registry_class, Registry): raise TypeError(f'Subclass of Registry is required. ' f'Got: {registry_class}') else: registry_class = Registry app_registry = registry_class() for schema_cls, controller_cls in config.items(): resource_type = schema_cls.opts.resource_type resource_cls = schema_cls.opts.resource_cls if not inspect.isclass(controller_cls): raise TypeError('Class (not instance) of controller is required.') if not issubclass(controller_cls, ControllerABC): raise TypeError(f'Subclass of ControllerABC is required. ' f'Got: {controller_cls}') if not inspect.isclass(schema_cls): raise TypeError('Class (not instance) of schema is required.') if not issubclass(schema_cls, SchemaABC): raise TypeError(f'Subclass of SchemaABC is required. ' f'Got: {schema_cls}') if not inspect.isclass(schema_cls.opts.resource_cls): raise TypeError('Class (not instance) of resource is required.') if not ALLOWED_MEMBER_NAME_REGEX.fullmatch(resource_type): raise ValueError(f"Resource type '{resource_type}' is not allowed.") app_registry[resource_type] = schema_cls, controller_cls app_registry[resource_cls] = schema_cls, controller_cls logger.debug( 'Registered %r ' '(schema: %r, resource class: %r, type %r)', controller_cls.__name__, schema_cls.__name__, resource_cls.__name__, resource_type )
return app_registry
[docs]def setup_custom_handlers(custom_handlers): """Set up default and custom handlers for JSON API application.""" from . import handlers as default_handlers from .common import logger handlers = { name: handler for name, handler in inspect.getmembers(default_handlers, inspect.iscoroutinefunction) if name in default_handlers.__all__ } if custom_handlers is not None: if isinstance(custom_handlers, MutableMapping): custom_handlers_iter = custom_handlers.items() elif isinstance(custom_handlers, Sequence): custom_handlers_iter = ((c.__name__, c) for c in custom_handlers) else: raise TypeError('Wrong type of "custom_handlers" parameter. ' 'Mapping or Sequence is expected.') for name, custom_handler in custom_handlers_iter: handler_name = custom_handler.__name__ if name not in handlers: logger.warning('Custom handler %s is ignored.', name) continue if not inspect.iscoroutinefunction(custom_handler): logger.error('"%s" is not a co-routine function (ignored).', handler_name) continue handlers[name] = custom_handler logger.debug('Default handler "%s" is replaced ' 'with co-routine "%s" (%s)', name, handler_name, inspect.getmodule(custom_handler))
return handlers
[docs]def setup_resources(app, base_path, handlers, routes_namespace): """Set up JSON API application resources.""" from .common import ALLOWED_MEMBER_NAME_RULE type_part = '{type:' + ALLOWED_MEMBER_NAME_RULE + '}' relation_part = '{relation:' + ALLOWED_MEMBER_NAME_RULE + '}' collection_resource = app.router.add_resource( f'{base_path}/{type_part}', name=f'{routes_namespace}.collection' ) resource_resource = app.router.add_resource( f'{base_path}/{type_part}/{{id}}', name=f'{routes_namespace}.resource' ) relationships_resource = app.router.add_resource( f'{base_path}/{type_part}/{{id}}/relationships/{relation_part}', name=f'{routes_namespace}.relationships' ) related_resource = app.router.add_resource( f'{base_path}/{type_part}/{{id}}/{relation_part}', name=f'{routes_namespace}.related' ) collection_resource.add_route('GET', handlers['get_collection']) collection_resource.add_route('POST', handlers['post_resource']) resource_resource.add_route('GET', handlers['get_resource']) resource_resource.add_route('PATCH', handlers['patch_resource']) resource_resource.add_route('DELETE', handlers['delete_resource']) relationships_resource.add_route('GET', handlers['get_relationship']) relationships_resource.add_route('POST', handlers['post_relationship']) relationships_resource.add_route('PATCH', handlers['patch_relationship']) relationships_resource.add_route('DELETE', handlers['delete_relationship'])
related_resource.add_route('GET', handlers['get_related'])
[docs]def setup_jsonapi(app, config, *, base_path='/api', version='1.0', meta=None, context_cls=None, registry_class=None, custom_handlers=None, log_errors=True, routes_namespace=None): """ Set up JSON API in aiohttp application. This function will setup resources, handlers and middleware. :param ~aiohttp.web.Application app: Application instance :param ~typing.Sequence[DefaultController] controllers: List of controllers to register in JSON API :param str base_path: Prefix of JSON API routes paths :param str version: JSON API version (used in ``jsonapi`` key of document) :param dict meta: Meta information will added to response (``meta`` key of document) :param context_cls: Override of JSONAPIContext class (must be subclass of :class:`~aiohttp_json_api.context.JSONAPIContext`) :param registry_class: Override of Registry class (must be subclass of :class:`~aiohttp_json_api.registry.Registry`) :param custom_handlers: Sequence or mapping with overrides of default JSON API handlers. If your custom handlers named in conform with convention of this application, then pass it as sequence:: custom_handlers=(get_collection, patch_resource) If you have custom name of these handlers, then pass it as mapping:: custom_handlers={ 'get_collection': some_handler_for_get_collection, 'patch_resource': another_handler_to_patch_resource } :param bool log_errors: Log errors handled by :func:`~aiohttp_json_api.middleware.jsonapi_middleware` :param str routes_namespace: Namespace of JSON API application routes :return: aiohttp Application instance with configured JSON API :rtype: ~aiohttp.web.Application """ from .common import JSONAPI, logger from .context import JSONAPIContext from .middleware import jsonapi_middleware if JSONAPI in app: logger.warning('JSON API application is initialized already. ' 'Please check your aiohttp.web.Application instance ' 'does not have a "%s" dictionary key.', JSONAPI) logger.error('Initialization of JSON API application is FAILED.') return app routes_namespace = routes_namespace \ if routes_namespace and isinstance(routes_namespace, str) \ else JSONAPI if context_cls is not None: if not issubclass(context_cls, JSONAPIContext): raise TypeError(f'Subclass of JSONAPIContext is required. ' f'Got: {context_cls}') else: context_cls = JSONAPIContext app[JSONAPI] = { 'registry': setup_app_registry(app, registry_class, config), 'context_cls': context_cls, 'meta': meta, 'jsonapi': { 'version': version, }, 'log_errors': log_errors, 'routes_namespace': routes_namespace } handlers = setup_custom_handlers(custom_handlers) setup_resources(app, base_path, handlers, routes_namespace) logger.debug('Registered JSON API resources list:') for resource in filter(lambda r: r.name.startswith(routes_namespace), app.router.resources()): logger.debug('%s -> %s', [r.method for r in resource], resource.get_info()) app.middlewares.append(jsonapi_middleware)
return app