Using Plugins ============= What is an apispec "plugin"? ---------------------------- An apispec *plugin* is an object that provides helper methods for generating OpenAPI entities from objects in your application. A plugin may modify the behavior of `APISpec ` methods so that they can take your application's objects as input. Enabling Plugins ---------------- To enable a plugin, pass an instance to the constructor of `APISpec `. .. code-block:: python :emphasize-lines: 9 from apispec import APISpec from apispec.ext.marshmallow import MarshmallowPlugin spec = APISpec( title="Gisty", version="1.0.0", openapi_version="3.0.2", info=dict(description="A minimal gist API"), plugins=[MarshmallowPlugin()], ) Example: Flask and Marshmallow Plugins -------------------------------------- The bundled marshmallow plugin (`apispec.ext.marshmallow.MarshmallowPlugin`) provides helpers for generating OpenAPI schema and parameter objects from `marshmallow `_ schemas and fields. The `apispec-webframeworks `_ package includes a Flask plugin with helpers for generating path objects from view functions. Let's recreate the spec from the :doc:`Quickstart guide ` using these two plugins. First, ensure that ``apispec-webframeworks`` is installed: :: $ pip install apispec-webframeworks Also, ensure that a compatible ``marshmallow`` version is used: :: $ pip install -U apispec[marshmallow] We can now use the marshmallow and Flask plugins. .. code-block:: python from apispec import APISpec from apispec.ext.marshmallow import MarshmallowPlugin from apispec_webframeworks.flask import FlaskPlugin spec = APISpec( title="Gisty", version="1.0.0", openapi_version="3.0.2", info=dict(description="A minimal gist API"), plugins=[FlaskPlugin(), MarshmallowPlugin()], ) Our application will have a marshmallow `Schema ` for gists. .. code-block:: python from marshmallow import Schema, fields class GistParameter(Schema): gist_id = fields.Int() class GistSchema(Schema): id = fields.Int() content = fields.Str() The marshmallow plugin allows us to pass this `Schema` to `spec.components.schema `. .. code-block:: python spec.components.schema("Gist", schema=GistSchema) The schema is now added to the spec. .. code-block:: python from pprint import pprint pprint(spec.to_dict()) # {'components': {'parameters': {}, 'responses': {}, 'schemas': {}}, # 'info': {'description': 'A minimal gist API', # 'title': 'Gisty', # 'version': '1.0.0'}, # 'openapi': '3.0.2', # 'paths': {}, # 'tags': []} Our application will have a Flask route for the gist detail endpoint. We'll add some YAML in the docstring to add response information. .. code-block:: python from flask import Flask app = Flask(__name__) # NOTE: Plugins may inspect docstrings to gather more information for the spec @app.route("/gists/") def gist_detail(gist_id): """Gist detail view. --- get: parameters: - in: path schema: GistParameter responses: 200: content: application/json: schema: GistSchema """ return "details about gist {}".format(gist_id) The Flask plugin allows us to pass this view to `spec.path `. .. code-block:: python # Since path inspects the view and its route, # we need to be in a Flask request context with app.test_request_context(): spec.path(view=gist_detail) Our OpenAPI spec now looks like this: .. code-block:: python pprint(spec.to_dict()) # {'components': {'parameters': {}, # 'responses': {}, # 'schemas': {'Gist': {'properties': {'content': {'type': 'string'}, # 'id': {'format': 'int32', # 'type': 'integer'}}, # 'type': 'object'}}}, # 'info': {'description': 'A minimal gist API', # 'title': 'Gisty', # 'version': '1.0.0'}, # 'openapi': '3.0.2', # 'paths': {'/gists/{gist_id}': {'get': {'parameters': [{'in': 'path', # 'name': 'gist_id', # 'required': True, # 'schema': {'format': 'int32', # 'type': 'integer'}}], # 'responses': {200: {'content': {'application/json': {'schema': {'$ref': '#/components/schemas/Gist'}}}}}}}}, # 'tags': []} If your API uses `method-based dispatching `_, the process is similar. Note that the method no longer needs to be included in the docstring. .. code-block:: python from flask.views import MethodView class GistApi(MethodView): def get(self): """Gist view --- description: Get a gist responses: 200: content: application/json: schema: GistSchema """ pass def post(self): pass method_view = GistApi.as_view("gist") app.add_url_rule("/gist", view_func=method_view) with app.test_request_context(): spec.path(view=method_view) pprint(dict(spec.to_dict()["paths"]["/gist"])) # {'get': {'description': 'get a gist', # 'responses': {200: {'content': {'application/json': {'schema': {'$ref': '#/components/schemas/Gist'}}}}}}, # 'post': {}} Marshmallow Plugin ------------------ .. _marshmallow_nested_schemas: Nested Schemas ************** By default, Marshmallow `Nested` fields are represented by a `JSON Reference object `_. If the schema has been added to the spec via `spec.components.schema `, the user-supplied name will be used in the reference. Otherwise apispec will add the nested schema to the spec using an automatically resolved name for the nested schema. The default `resolver ` function will resolve a name based on the schema's class `__name__`, dropping a trailing "Schema" so that `class PetSchema(Schema)` resolves to "Pet". To change the behavior of the name resolution simply pass a function accepting a `Schema` class, `Schema` instance or a string that resolves to a `Schema` class and returning a string to the plugin's constructor. To easily work with these argument types the marshmallow plugin provides `resolve_schema_cls ` and `resolve_schema_instance ` functions. If the `schema_name_resolver` function returns a value that evaluates to `False` in a boolean context the nested schema will not be added to the spec and instead defined in-line. .. note:: A `schema_name_resolver` function must return a string name when working with circular-referencing schemas in order to avoid infinite recursion. Schema Modifiers **************** apispec will respect schema modifiers such as ``exclude`` and ``partial`` in the generated schema definition. If a schema is initialized with modifiers, apispec will treat each combination of modifiers as a unique schema definition. Custom DateTime formats *********************** apispec supports all four basic formats of `marshmallow.fields.DateTime`: ``"rfc"`` (for RFC822), ``"iso"`` (for ISO8601), ``"timestamp"``, ``"timestamp_ms"`` (for a POSIX timestamp). If you are using a custom DateTime format you should pass a regex string to the ``pattern`` parameter in your field ``metadata`` so that it is included as documentation. .. code-block:: python class SchemaWithCustomDate(Schema): french_date = ma.DateTime( format="%d-%m%Y %H:%M:%S", metadata={"pattern": r"^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}$"}, ) Custom Fields ************* apispec maps standard marshmallow fields to OpenAPI types and formats. If your custom field subclasses a standard marshmallow `Field` class then it will inherit the default mapping. If you want to override the OpenAPI type and format for custom fields, use the `map_to_openapi_type ` method. It can be invoked with either a pair of strings providing the OpenAPI type and format, or a marshmallow `Field` that has the desired target mapping. .. code-block:: python from apispec import APISpec from apispec.ext.marshmallow import MarshmallowPlugin from marshmallow.fields import Integer, Field ma_plugin = MarshmallowPlugin() spec = APISpec( title="Demo", version="0.1", openapi_version="3.0.0", plugins=(ma_plugin,) ) # Inherits Integer mapping of ('integer', None) class CustomInteger(Integer): pass # Override Integer mapping class Int32(Integer): pass ma_plugin.map_to_openapi_type(Int32, "string", "int32") # Map to ('integer', None) like Integer class IntegerLike(Field): pass ma_plugin.map_to_openapi_type(IntegerLike, Integer) In situations where greater control of the properties generated for a custom field is desired, users may add custom logic to the conversion of fields to OpenAPI properties through the use of the `add_attribute_function ` method. Continuing from the example above: .. code-block:: python def my_custom_field2properties(self, field, **kwargs): """Add an OpenAPI extension flag to MyCustomField instances""" ret = {} if isinstance(field, MyCustomField): if self.openapi_version.major > 2: ret["x-customString"] = True return ret ma_plugin.converter.add_attribute_function(my_custom_field2properties) The function passed to `add_attribute_function` will be bound to the converter. It must accept the converter instance as first positional argument. In some rare cases, typically with container fields such as fields derived from :class:`List `, documenting the parameters using this field require some more customization. This can be achieved using the `add_parameter_attribute_function ` method. For instance, when documenting webargs's :class:`DelimitedList ` field, one may register this function: .. code-block:: python def delimited_list2param(self, field, **kwargs): ret: dict = {} if isinstance(field, DelimitedList): if self.openapi_version.major < 3: ret["collectionFormat"] = "csv" else: ret["explode"] = False ret["style"] = "form" return ret ma_plugin.converter.add_parameter_attribute_function(delimited_list2param) Enum Fields *********** When using `marshmallow.fields.Enum` fields to (de)serialize `enum.Enum` values, we recommend passing a marshmallow field to ``by_value``. This ensures the correct ``type`` property is included in the generated OAI spec. .. code-block:: python :emphasize-lines: 23,42 from enum import Enum from apispec import APISpec from apispec.ext.marshmallow import MarshmallowPlugin from marshmallow import Schema, fields spec = APISpec( title="Gisty", version="1.0.0", openapi_version="3.0.2", info=dict(description="A minimal gist API"), plugins=[MarshmallowPlugin()], ) class GistVisibility(Enum): PRIVATE = "private" PUBLIC = "public" class GistSchema(Schema): id = fields.Int() visibility = fields.Enum(GistVisibility, by_value=fields.String()) spec.components.schema("Gist", schema=GistSchema) print(spec.to_yaml()) # info: # description: A minimal gist API # title: Gisty # version: 1.0.0 # paths: {} # openapi: 3.0.2 # components: # schemas: # Gist: # type: object # properties: # id: # type: integer # visibility: # type: string # enum: # - private # - public Next Steps ---------- You now know how to use plugins. The next section will show you how to write plugins: :doc:`Writing Plugins `.