Source code for flask_unchained.bundles.api.extensions.api

from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec.ext.marshmallow.openapi import __location_map__
from apispec_webframeworks.flask import FlaskPlugin

from flask_unchained import FlaskUnchained, unchained
from flask_unchained.bundles.controller.constants import (
    CREATE, DELETE, GET, LIST, PATCH, PUT)
from flask_unchained.string_utils import title_case, pluralize

from ..model_resource import ModelResource


[docs]class Api: """ The `Api` extension:: from flask_unchained.bundles.api import api Allows interfacing with `apispec <https://apispec.readthedocs.io/en/latest/>`_. """ def __init__(self): self.app: FlaskUnchained = None self.flask_plugin: FlaskPlugin = None self.ma_plugin: MarshmallowPlugin = None self.spec: APISpec = None def init_app(self, app: FlaskUnchained): self.app = app app.extensions['api'] = self plugins = app.config.API_APISPEC_PLUGINS plugins = plugins and list(plugins) or [] self.flask_plugin = FlaskPlugin() self.ma_plugin = MarshmallowPlugin() plugins.extend([self.flask_plugin, self.ma_plugin]) self.spec = APISpec(title=app.config.API_TITLE or app.name, version=app.config.API_VERSION, openapi_version=app.config.API_OPENAPI_VERSION, plugins=plugins, info=dict(description=app.config.API_DESCRIPTION))
[docs] def register_serializer(self, serializer, name=None, **kwargs): """ Method to manually register a :class:`Serializer` with APISpec. :param serializer: :param name: :param kwargs: """ name = name or serializer.__name__ if name not in self.spec.components.schemas: self.spec.components.schema(name, schema=serializer, **kwargs)
# FIXME need to be able to create 'fake' schemas for the query parameter
[docs] def register_model_resource(self, resource: ModelResource): """ Method to manually register a :class:`ModelResource` with APISpec. :param resource: """ model_name = resource.Meta.model.__name__ self.spec.tag({ 'name': model_name, 'description': resource.Meta.model.__doc__, }) for method in resource.methods(): key = f'{resource.__name__}.{method}' if key not in unchained.controller_bundle.controller_endpoints: continue docs = {} http_method = method if method == CREATE: http_method = 'post' docs[http_method] = dict( parameters=[{ 'in': __location_map__['json'], 'required': True, 'schema': resource.Meta.serializer_create, }], responses={ '201': dict(description=getattr(resource, CREATE).__doc__, schema=resource.Meta.serializer_create), }, ) elif method == DELETE: docs[http_method] = dict( parameters=[], responses={ '204': dict(description=getattr(resource, DELETE).__doc__), }, ) elif method == GET: docs[http_method] = dict( parameters=[], responses={ '200': dict(description=getattr(resource, GET).__doc__, schema=resource.Meta.serializer), }, ) elif method == LIST: http_method = 'get' docs[http_method] = dict( parameters=[], responses={ '200': dict(description=getattr(resource, LIST).__doc__, schema=resource.Meta.serializer_many), }, ) elif method == PATCH: docs[http_method] = dict( parameters=[{ 'in': __location_map__['json'], 'required': False, 'schema': resource.Meta.serializer, }], responses={ '200': dict(description=getattr(resource, PATCH).__doc__, schema=resource.Meta.serializer), }, ) elif method == PUT: docs[http_method] = dict( parameters=[{ 'in': __location_map__['json'], 'required': True, 'schema': resource.Meta.serializer, }], responses={ '200': dict(description=getattr(resource, PUT).__doc__, schema=resource.Meta.serializer), }, ) docs[http_method]['tags'] = [model_name] display_name = title_case(model_name) if method == LIST: display_name = pluralize(display_name) docs[http_method]['summary'] = f'{http_method.upper()} {display_name}' routes = unchained.controller_bundle.controller_endpoints[key] for route in routes: for rule in self.app.url_map.iter_rules(route.endpoint): self.spec.path(app=self.app, rule=rule, operations=docs, view=route.view_func)
[docs] def register_field(self, field, *args): """ Register custom Marshmallow field. Registering the Field class allows the Schema parser to set the proper type and format when documenting parameters from Schema fields. :param Field field: Marshmallow Field class :param: args: - a pair of the form ``(type, format)`` to map to - a core marshmallow field type (then that type's mapping is used) Examples:: # Map to ('string', 'UUID') api.register_field(UUIDField, 'string', 'UUID') # Map to ('string') api.register_field(URLField, 'string', None) # Map to ('integer, 'int32') api.register_field(CustomIntegerField, ma.fields.Integer) """ self.ma_plugin.map_to_openapi_type(*args)(field)