Repository: marshmallow-code/apispec
Pull request #493 (2019-09-02 15:42:09)

Make OpenAPIConverter class a class member of MarshmallowPlugin
Discussed in https://github.com/marshmallow-code/apispec/pull/488.

@sloria, I cherry-picked your docs revisions.

openapi and field_converter are private API, but I left their section in the docs when rebasing your doc revisions commit. In other words, this leaves the docs in the same state as on `dev` branch.


Patch:
diff --git a/docs/using_plugins.rst b/docs/using_plugins.rst
index da3f831..367fa6e 100644
--- a/docs/using_plugins.rst
+++ b/docs/using_plugins.rst
@@ -288,7 +288,7 @@ method. Continuing from the example above:
         return ret
 
 
-    ma_plugin.openapi.add_attribute_function(my_custom_field2properties)
+    ma_plugin.converter.add_attribute_function(my_custom_field2properties)
 
 Next Steps
 ----------
diff --git a/src/apispec/ext/marshmallow/__init__.py b/src/apispec/ext/marshmallow/__init__.py
index 7658aed..f68915d 100644
--- a/src/apispec/ext/marshmallow/__init__.py
+++ b/src/apispec/ext/marshmallow/__init__.py
@@ -54,8 +54,7 @@ from .openapi import OpenAPIConverter
 
 
 def resolver(schema):
-    """Default implementation of a schema name resolver function
-    """
+    """Default schema name resolver function that strips 'Schema' from the end of the class name."""
     schema_cls = resolve_schema_cls(schema)
     name = schema_cls.__name__
     if name.endswith("Schema"):
@@ -64,7 +63,7 @@ def resolver(schema):
 
 
 class MarshmallowPlugin(BasePlugin):
-    """APISpec plugin handling marshmallow schemas
+    """APISpec plugin for translating marshmallow schemas to OpenAPI/JSONSchema format.
 
     :param callable schema_name_resolver: Callable to generate the schema definition name.
         Receives the `Schema` class and returns the name to be used in refs within
@@ -80,18 +79,20 @@ class MarshmallowPlugin(BasePlugin):
                 return schema_cls.__name__
     """
 
+    Converter = OpenAPIConverter
+
     def __init__(self, schema_name_resolver=None):
         super().__init__()
         self.schema_name_resolver = schema_name_resolver or resolver
         self.spec = None
         self.openapi_version = None
-        self.openapi = None
+        self.converter = None
 
     def init_spec(self, spec):
         super().init_spec(spec)
         self.spec = spec
         self.openapi_version = spec.openapi_version
-        self.openapi = OpenAPIConverter(
+        self.converter = self.Converter(
             openapi_version=spec.openapi_version,
             schema_name_resolver=self.schema_name_resolver,
             spec=spec,
@@ -106,7 +107,7 @@ class MarshmallowPlugin(BasePlugin):
                 and "in" in parameter
             ):
                 schema_instance = resolve_schema_instance(parameter.pop("schema"))
-                resolved += self.openapi.schema2parameters(
+                resolved += self.converter.schema2parameters(
                     schema_instance, default_in=parameter.pop("in"), **parameter
                 )
             else:
@@ -121,7 +122,7 @@ class MarshmallowPlugin(BasePlugin):
         content = request_body["content"]
         for content_type in content:
             schema = content[content_type]["schema"]
-            content[content_type]["schema"] = self.openapi.resolve_schema_dict(schema)
+            content[content_type]["schema"] = self.converter.resolve_schema_dict(schema)
 
     def resolve_schema(self, data):
         """Function to resolve a schema in a parameter or response - modifies the
@@ -136,13 +137,13 @@ class MarshmallowPlugin(BasePlugin):
 
         # OAS 2 component or OAS 3 header
         if "schema" in data:
-            data["schema"] = self.openapi.resolve_schema_dict(data["schema"])
+            data["schema"] = self.converter.resolve_schema_dict(data["schema"])
         # OAS 3 component except header
         if self.openapi_version.major >= 3:
             if "content" in data:
                 for content in data["content"].values():
                     if "schema" in content:
-                        content["schema"] = self.openapi.resolve_schema_dict(
+                        content["schema"] = self.converter.resolve_schema_dict(
                             content["schema"]
                         )
 
@@ -164,7 +165,7 @@ class MarshmallowPlugin(BasePlugin):
             class MyCustomFieldThatsKindaLikeAnInteger(Integer):
                 # ...
         """
-        return self.openapi.map_to_openapi_type(*args)
+        return self.converter.map_to_openapi_type(*args)
 
     def schema_helper(self, name, _, schema=None, **kwargs):
         """Definition helper that allows using a marshmallow
@@ -180,9 +181,9 @@ class MarshmallowPlugin(BasePlugin):
 
         schema_key = make_schema_key(schema_instance)
         self.warn_if_schema_already_in_spec(schema_key)
-        self.openapi.refs[schema_key] = name
+        self.converter.refs[schema_key] = name
 
-        json_schema = self.openapi.schema2jsonschema(schema_instance)
+        json_schema = self.converter.schema2jsonschema(schema_instance)
 
         return json_schema
 
@@ -231,7 +232,7 @@ class MarshmallowPlugin(BasePlugin):
         """Method to warn the user if the schema has already been added to the
         spec.
         """
-        if schema_key in self.openapi.refs:
+        if schema_key in self.converter.refs:
             warnings.warn(
                 "{} has already been added to the spec. Adding it twice may "
                 "cause references to not resolve properly.".format(schema_key[0]),
diff --git a/src/apispec/ext/marshmallow/common.py b/src/apispec/ext/marshmallow/common.py
index ee4125f..1421217 100644
--- a/src/apispec/ext/marshmallow/common.py
+++ b/src/apispec/ext/marshmallow/common.py
@@ -11,7 +11,7 @@ MODIFIERS = ["only", "exclude", "load_only", "dump_only", "partial"]
 
 
 def resolve_schema_instance(schema):
-    """Return schema instance for given schema (instance or class)
+    """Return schema instance for given schema (instance or class).
 
     :param type|Schema|str schema: instance, class or class name of marshmallow.Schema
     :return: schema instance of given schema (instance or class)
@@ -25,12 +25,12 @@ def resolve_schema_instance(schema):
     except marshmallow.exceptions.RegistryError:
         raise ValueError(
             "{!r} is not a marshmallow.Schema subclass or instance and has not"
-            " been registered in the Marshmallow class registry.".format(schema)
+            " been registered in the marshmallow class registry.".format(schema)
         )
 
 
 def resolve_schema_cls(schema):
-    """Return schema class for given schema (instance or class)
+    """Return schema class for given schema (instance or class).
 
     :param type|Schema|str: instance, class or class name of marshmallow.Schema
     :return: schema class of given schema (instance or class)
@@ -44,12 +44,12 @@ def resolve_schema_cls(schema):
     except marshmallow.exceptions.RegistryError:
         raise ValueError(
             "{!r} is not a marshmallow.Schema subclass or instance and has not"
-            " been registered in the Marshmallow class registry.".format(schema)
+            " been registered in the marshmallow class registry.".format(schema)
         )
 
 
 def get_fields(schema, exclude_dump_only=False):
-    """Return fields from schema
+    """Return fields from schema.
 
     :param Schema schema: A marshmallow Schema instance or a class object
     :param bool exclude_dump_only: whether to filter fields in Meta.dump_only
@@ -69,8 +69,7 @@ def get_fields(schema, exclude_dump_only=False):
 
 
 def warn_if_fields_defined_in_meta(fields, Meta):
-    """Warns user that fields defined in Meta.fields or Meta.additional will
-    be ignored
+    """Warns user that fields defined in Meta.fields or Meta.additional will be ignored.
 
     :param dict fields: A dictionary of fields name field object pairs
     :param Meta: the schema's Meta class
@@ -88,7 +87,7 @@ def warn_if_fields_defined_in_meta(fields, Meta):
 
 
 def filter_excluded_fields(fields, Meta, exclude_dump_only):
-    """Filter fields that should be ignored in the OpenAPI spec
+    """Filter fields that should be ignored in the OpenAPI spec.
 
     :param dict fields: A dictionary of fields name field object pairs
     :param Meta: the schema's Meta class
diff --git a/src/apispec/ext/marshmallow/field_converter.py b/src/apispec/ext/marshmallow/field_converter.py
index 6cdbe4f..3c1cbfb 100644
--- a/src/apispec/ext/marshmallow/field_converter.py
+++ b/src/apispec/ext/marshmallow/field_converter.py
@@ -83,7 +83,7 @@ _VALID_PREFIX = "x-"
 
 
 class FieldConverterMixin:
-    """Mixin class to convert fields to an OpenAPI property"""
+    """Adds methods for converting marshmallow fields to an OpenAPI properties."""
 
     field_mapping = DEFAULT_FIELD_MAPPING
 
@@ -128,7 +128,7 @@ class FieldConverterMixin:
     def add_attribute_function(self, func):
         """Method to add an attribute function to the list of attribute functions
         that will be called on a field to convert it from a field to an OpenAPI
-        property
+        property.
 
         :param func func: the attribute function to add - will be called for each
             field in a schema with a `field <marshmallow.fields.Field>` instance
@@ -163,8 +163,7 @@ class FieldConverterMixin:
         return ret
 
     def field2type_and_format(self, field, **kwargs):
-        """Return the dictionary of OpenAPI type and format based on the field
-        type
+        """Return the dictionary of OpenAPI type and format based on the field type.
 
         :param Field field: A marshmallow field.
         :rtype: dict
@@ -192,7 +191,7 @@ class FieldConverterMixin:
         return ret
 
     def field2default(self, field, **kwargs):
-        """Return the dictionary containing the field's default value
+        """Return the dictionary containing the field's default value.
 
         Will first look for a `doc_default` key in the field's metadata and then
         fall back on the field's `missing` parameter. A callable passed to the
@@ -212,7 +211,7 @@ class FieldConverterMixin:
         return ret
 
     def field2choices(self, field, **kwargs):
-        """Return the dictionary of OpenAPI field attributes for valid choices definition
+        """Return the dictionary of OpenAPI field attributes for valid choices definition.
 
         :param Field field: A marshmallow field.
         :rtype: dict
@@ -371,7 +370,7 @@ class FieldConverterMixin:
         return attributes
 
     def metadata2properties(self, field, **kwargs):
-        """Return a dictionary of properties extracted from field Metadata
+        """Return a dictionary of properties extracted from field metadata.
 
         Will include field metadata that are valid properties of `OpenAPI schema
         objects
@@ -402,8 +401,7 @@ class FieldConverterMixin:
         return ret
 
     def nested2properties(self, field, ret):
-        """Return a dictionary of properties from :class:`Nested <marshmallow.fields.Nested`
-        fields
+        """Return a dictionary of properties from :class:`Nested <marshmallow.fields.Nested` fields.
 
         Typically provides a reference object and will add the schema to the spec
         if it is not already present
@@ -423,8 +421,7 @@ class FieldConverterMixin:
         return ret
 
     def list2properties(self, field, **kwargs):
-        """Return a dictionary of properties from :class:`List <marshmallow.fields.List`
-        fields
+        """Return a dictionary of properties from :class:`List <marshmallow.fields.List>` fields.
 
         Will provide an `items` property based on the field's `inner` attribute
 
@@ -440,8 +437,7 @@ class FieldConverterMixin:
         return ret
 
     def dict2properties(self, field, **kwargs):
-        """Return a dictionary of properties from :class:`Dict <marshmallow.fields.Dict`
-        fields
+        """Return a dictionary of properties from :class:`Dict <marshmallow.fields.Dict>` fields.
 
         Only applicable for Marshmallow versions greater than 3. Will provide an
         `additionalProperties` property based on the field's `value_field` attribute
diff --git a/src/apispec/ext/marshmallow/openapi.py b/src/apispec/ext/marshmallow/openapi.py
index 1b72bf9..6d157fe 100644
--- a/src/apispec/ext/marshmallow/openapi.py
+++ b/src/apispec/ext/marshmallow/openapi.py
@@ -39,7 +39,7 @@ __location_map__ = {
 
 
 class OpenAPIConverter(FieldConverterMixin):
-    """Converter generating OpenAPI specification from Marshmallow schemas and fields
+    """Adds methods for generating OpenAPI specification from marshmallow schemas and fields.
 
     :param str|OpenAPIVersion openapi_version: The OpenAPI version to use.
         Should be in the form '2.x' or '3.x.x' to comply with the OpenAPI standard.
@@ -75,7 +75,7 @@ class OpenAPIConverter(FieldConverterMixin):
         return field.data_key or name
 
     def resolve_nested_schema(self, schema):
-        """Return the Open API representation of a marshmallow Schema.
+        """Return the OpenAPI representation of a marshmallow Schema.
 
         Adds the schema to the spec if it isn't already present.
 
@@ -257,28 +257,6 @@ class OpenAPIConverter(FieldConverterMixin):
 
         https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject
 
-        Example: ::
-
-            class UserSchema(Schema):
-                _id = fields.Int()
-                email = fields.Email(description='email address of the user')
-                name = fields.Str()
-
-                class Meta:
-                    title = 'User'
-                    description = 'A registered user'
-
-            oaic = OpenAPIConverter(openapi_version='3.0.2', schema_name_resolver=resolver, spec=spec)
-            pprint(oaic.schema2jsonschema(UserSchema))
-            # {'description': 'A registered user',
-            #  'properties': {'_id': {'format': 'int32', 'type': 'integer'},
-            #                 'email': {'description': 'email address of the user',
-            #                           'format': 'email',
-            #                           'type': 'string'},
-            #                 'name': {'type': 'string'}},
-            #  'title': 'User',
-            #  'type': 'object'}
-
         :param Schema schema: A marshmallow Schema instance
         :rtype: dict, a JSON Schema Object
         """


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 9e3f227..879e660 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -15,7 +15,7 @@ def make_spec(openapi_version):
         plugins=(ma_plugin,),
     )
     return namedtuple("Spec", ("spec", "marshmallow_plugin", "openapi"))(
-        spec, ma_plugin, ma_plugin.openapi
+        spec, ma_plugin, ma_plugin.converter
     )
 
 
diff --git a/tests/test_ext_marshmallow_field.py b/tests/test_ext_marshmallow_field.py
index 51afe08..43fb333 100644
--- a/tests/test_ext_marshmallow_field.py
+++ b/tests/test_ext_marshmallow_field.py
@@ -284,6 +284,6 @@ def test_custom_properties_for_custom_fields():
     APISpec(
         title="Validation", version="0.1", openapi_version="3.0.0", plugins=(ma_plugin,)
     )
-    ma_plugin.openapi.add_attribute_function(custom_string2properties)
-    properties = ma_plugin.openapi.field2property(CustomStringField())
+    ma_plugin.converter.add_attribute_function(custom_string2properties)
+    properties = ma_plugin.converter.field2property(CustomStringField())
     assert properties["x-customString"]
diff --git a/tests/test_ext_marshmallow_openapi.py b/tests/test_ext_marshmallow_openapi.py
index d919db5..665c34b 100644
--- a/tests/test_ext_marshmallow_openapi.py
+++ b/tests/test_ext_marshmallow_openapi.py
@@ -467,7 +467,7 @@ def test_openapi_tools_validate_v2():
     spec = APISpec(
         title="Pets", version="0.1", plugins=(ma_plugin,), openapi_version="2.0"
     )
-    openapi = ma_plugin.openapi
+    openapi = ma_plugin.converter
 
     spec.components.schema("Category", schema=CategorySchema)
     spec.components.schema("Pet", {"discriminator": "name"}, schema=PetSchema)
@@ -524,7 +524,7 @@ def test_openapi_tools_validate_v3():
     spec = APISpec(
         title="Pets", version="0.1", plugins=(ma_plugin,), openapi_version="3.0.0"
     )
-    openapi = ma_plugin.openapi
+    openapi = ma_plugin.converter
 
     spec.components.schema("Category", schema=CategorySchema)
     spec.components.schema("Pet", schema=PetSchema)