Repository: marshmallow-code/apispec
Pull request #381 (2019-02-04 10:58:24)

Rework components interface
This addresses my comment here: https://github.com/marshmallow-code/apispec/pull/375#issuecomment-459724696.

I find it awkward to pass dicts as kwargs, and I think it would be clearer and more future-proof to pass the component `dict` as positional parameter, keeping kwargs only for plugin-specific parameters.

This is a totally breaking change. Tell me what you think.

I can try to fix the black issues. It would be easier if the changes were printed in the Travis log, as I wouldn't have to run it locally.

Docs would need an update.


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 8e14950..c8e3f96 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -9,19 +9,44 @@ Features:
 - Expanded support for OpenAPI Specification version 3 (:issue:`165`).
 - Add ``summary`` and ``description`` parameters to ``APISpec.path``
   (:issue:`227`). Thanks :user:`timakro` for the suggestion.
-- Add ``apispec.core.Components.security_scheme`` for adding Security
+- Add `apispec.core.Components.security_scheme` for adding Security
   Scheme Objects (:issue:`245`).
 - [apispec.ext.marshmallow]: Add support for outputting field patterns
   from ``Regexp`` validators (:pr:`364`).
   Thanks :user:`DStape` for the PR.
-- A `DuplicateComponentNameError` is raised when registering two
-  components with the same name (:issue:`340`).
+
+Other changes:
+
+- *Backwards-incompatible*: Components properties are now passed as dictionaries rather than keyword arguments (:pr:`381`).
+
+.. code-block:: python
+
+    # <1.0.0
+    spec.components.schema("Pet", properties={"name": {"type": "string"}})
+    spec.components.parameter("PetId", "path", format="int64", type="integer")
+    spec.component.response("NotFound", description="Pet not found")
+
+    # >=1.0.0
+    spec.components.schema("Pet", {"properties": {"name": {"type": "string"}}})
+    spec.components.parameter("PetId", "path", {"format": "int64", "type": "integer"})
+    spec.component.response("NotFound", {"description": "Pet not found"})
 
 Deprecations/Removals:
 
 - *Backwards-incompatible*: The ``ref`` argument passed to fields is no
   longer used (:issue:`354`). References for nested ``Schema`` are
   stored automatically.
+- *Backwards-incompatible*: The ``extra_fields`` argument of
+  `apispec.core.Components.schema` is removed. All properties may be
+  passed in the ``component`` argument.
+
+.. code-block:: python
+
+    # <1.0.0
+    spec.components.schema("Pet", schema=PetSchema, extra_fields={"discriminator": "name"})
+
+    # >=1.0.0
+    spec.components.schema("Pet", component={"discriminator": "name"}, schema=PetSchema)
 
 1.0.0rc1 (2018-01-29)
 +++++++++++++++++++++
@@ -34,7 +59,7 @@ Features:
 - Ability to opt out of the above behavior by passing a ``schema_name_resolver``
   function that returns ``None`` to ``api.ext.MarshmallowPlugin`` on initialization.
 - References now respect Schema initialization modifiers such as exclude.
-- *Backwards-incompatible*: A `DuplicateComponentNameError` is raised
+- *Backwards-incompatible*: A `apispec.exceptions.DuplicateComponentNameError` is raised
   when registering two components with the same name (:issue:`340`).
 
 1.0.0b6 (2018-12-16)
diff --git a/apispec/core.py b/apispec/core.py
index ddada9e..e31f2ad 100644
--- a/apispec/core.py
+++ b/apispec/core.py
@@ -108,16 +108,11 @@ class Components(object):
             security_key: self._security_schemes,
         }
 
-    def schema(
-        self,
-        name,
-        properties=None,
-        enum=None,
-        description=None,
-        extra_fields=None,
-        **kwargs
-    ):
-        """Add a new definition to the spec.
+    def schema(self, name, component=None, **kwargs):
+        """Add a new schema to the spec.
+
+        :param str name: identifier by which schema may be referenced.
+        :param dict component: schema definition.
 
         .. note::
 
@@ -138,82 +133,81 @@ class Components(object):
             raise DuplicateComponentNameError(
                 'Another schema with name "{}" is already registered.'.format(name)
             )
-        ret = {}
+        component = component or {}
+        ret = component.copy()
         # Execute all helpers from plugins
         for plugin in self._plugins:
             try:
-                ret.update(plugin.schema_helper(name, definition=ret, **kwargs) or {})
+                ret.update(plugin.schema_helper(name, component, **kwargs) or {})
             except PluginMethodNotImplementedError:
                 continue
-        if properties:
-            ret["properties"] = properties
-        if enum:
-            ret["enum"] = enum
-        if description:
-            ret["description"] = description
-        if extra_fields:
-            ret.update(extra_fields)
         self._schemas[name] = ret
         return self
 
-    def parameter(self, param_id, location, **kwargs):
+    def parameter(self, component_id, location, component=None, **kwargs):
         """ Add a parameter which can be referenced.
 
         :param str param_id: identifier by which parameter may be referenced.
         :param str location: location of the parameter.
-        :param dict kwargs: parameter fields.
+        :param dict component: parameter fields.
+        :param dict kwargs: plugin-specific arguments
         """
-        if param_id in self._parameters:
+        if component_id in self._parameters:
             raise DuplicateComponentNameError(
                 'Another parameter with name "{}" is already registered.'.format(
-                    param_id
+                    component_id
                 )
             )
-        ret = kwargs.copy()
-        ret.setdefault("name", param_id)
+        component = component or {}
+        ret = component.copy()
+        ret.setdefault("name", component_id)
         ret["in"] = location
         # Execute all helpers from plugins
         for plugin in self._plugins:
             try:
-                ret.update(plugin.parameter_helper(**kwargs) or {})
+                ret.update(plugin.parameter_helper(component, **kwargs) or {})
             except PluginMethodNotImplementedError:
                 continue
-        self._parameters[param_id] = ret
+        self._parameters[component_id] = ret
         return self
 
-    def response(self, ref_id, **kwargs):
+    def response(self, component_id, component=None, **kwargs):
         """Add a response which can be referenced.
 
-        :param str ref_id: ref_id to use as reference
-        :param dict kwargs: response fields
+        :param str component_id: ref_id to use as reference
+        :param dict component: response fields
+        :param dict kwargs: plugin-specific arguments
         """
-        if ref_id in self._responses:
+        if component_id in self._responses:
             raise DuplicateComponentNameError(
-                'Another response with name "{}" is already registered.'.format(ref_id)
+                'Another response with name "{}" is already registered.'.format(
+                    component_id
+                )
             )
-        ret = kwargs.copy()
+        component = component or {}
+        ret = component.copy()
         # Execute all helpers from plugins
         for plugin in self._plugins:
             try:
-                ret.update(plugin.response_helper(**kwargs) or {})
+                ret.update(plugin.response_helper(component, **kwargs) or {})
             except PluginMethodNotImplementedError:
                 continue
-        self._responses[ref_id] = ret
+        self._responses[component_id] = ret
         return self
 
-    def security_scheme(self, sec_id, **kwargs):
+    def security_scheme(self, component_id, component):
         """Add a security scheme which can be referenced.
 
-        :param str sec_id: sec_id to use as reference
+        :param str component_id: component_id to use as reference
         :param dict kwargs: security scheme fields
         """
-        if sec_id in self._security_schemes:
+        if component_id in self._security_schemes:
             raise DuplicateComponentNameError(
                 'Another security scheme with name "{}" is already registered.'.format(
-                    sec_id
+                    component_id
                 )
             )
-        self._security_schemes[sec_id] = kwargs
+        self._security_schemes[component_id] = component
         return self
 
 
diff --git a/apispec/ext/marshmallow/__init__.py b/apispec/ext/marshmallow/__init__.py
index 28ba7ed..c1b67bf 100644
--- a/apispec/ext/marshmallow/__init__.py
+++ b/apispec/ext/marshmallow/__init__.py
@@ -139,7 +139,7 @@ class MarshmallowPlugin(BasePlugin):
         """
         return self.openapi.map_to_openapi_type(*args)
 
-    def schema_helper(self, name, schema=None, **kwargs):
+    def schema_helper(self, name, _, schema=None, **kwargs):
         """Definition helper that allows using a marshmallow
         :class:`Schema <marshmallow.Schema>` to provide OpenAPI
         metadata.
@@ -159,24 +159,26 @@ class MarshmallowPlugin(BasePlugin):
 
         return json_schema
 
-    def parameter_helper(self, **kwargs):
+    def parameter_helper(self, parameter, **kwargs):
         """Parameter component helper that allows using a marshmallow
         :class:`Schema <marshmallow.Schema>` in parameter definition.
 
-        :param type|Schema schema: A marshmallow Schema class or instance.
+        :param dict parameter: parameter fields. May contain a marshmallow
+            Schema class or instance.
         """
         # In OpenAPIv3, this only works when using the complex form using "content"
-        self.resolve_schema(kwargs)
-        return kwargs
+        self.resolve_schema(parameter)
+        return parameter
 
-    def response_helper(self, **kwargs):
+    def response_helper(self, response, **kwargs):
         """Response component helper that allows using a marshmallow
         :class:`Schema <marshmallow.Schema>` in response definition.
 
-        :param type|Schema schema: A marshmallow Schema class or instance.
+        :param dict parameter: response fields. May contain a marshmallow
+            Schema class or instance.
         """
-        self.resolve_schema(kwargs)
-        return kwargs
+        self.resolve_schema(response)
+        return response
 
     def operation_helper(self, operations, **kwargs):
         for operation in operations.values():
diff --git a/apispec/plugin.py b/apispec/plugin.py
index 4803fec..fce1a71 100644
--- a/apispec/plugin.py
+++ b/apispec/plugin.py
@@ -18,11 +18,11 @@ class BasePlugin(object):
         """May return definition as a dict."""
         raise PluginMethodNotImplementedError
 
-    def parameter_helper(self, **kwargs):
+    def parameter_helper(self, parameter, **kwargs):
         """May return parameter component description as a dict."""
         raise PluginMethodNotImplementedError
 
-    def response_helper(self, **kwargs):
+    def response_helper(self, response, **kwargs):
         """May return response component description as a dict."""
         raise PluginMethodNotImplementedError
 
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index fd274a0..e6669bf 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -23,9 +23,11 @@ Add schemas to your spec using `spec.components.schema <apispec.core.Components.
 
     spec.components.schema(
         "Gist",
-        properties={
-            "id": {"type": "integer", "format": "int64"},
-            "name": {"type": "string"},
+        {
+            "properties": {
+                "id": {"type": "integer", "format": "int64"},
+                "name": {"type": "string"},
+            }
         },
     )
 
diff --git a/docs/special_topics.rst b/docs/special_topics.rst
index 3082b43..892d4a0 100644
--- a/docs/special_topics.rst
+++ b/docs/special_topics.rst
@@ -7,7 +7,8 @@ Solutions to specific problems are documented here.
 Adding Additional Fields To Schema Objects
 ------------------------------------------
 
-To add additional fields (e.g. ``"discriminator"``) to Schema objects generated from `spec.components.schema <apispec.core.Components.schema>` , pass the ``extra_fields`` argument.
+To add additional fields (e.g. ``"discriminator"``) to Schema objects generated from `spec.components.schema <apispec.core.Components.schema>` , pass them
+to the ``component`` parameter. If your'e using ``MarshmallowPlugin``, the ``component`` properties will get merged with the autogenerated properties.
 
 .. code-block:: python
 
@@ -16,13 +17,11 @@ To add additional fields (e.g. ``"discriminator"``) to Schema objects generated
         "name": {"type": "string", "example": "doggie"},
     }
 
-    spec.components.schema(
-        "Pet", properties=properties, extra_fields={"discriminator": "petType"}
-    )
+    spec.components.schema("Pet", component={"discriminator": "petType"}, schema=PetSchema)
 
 
 .. note::
-    Be careful about the input that you pass to ``extra_fields``. ``apispec`` will not guarantee that the passed fields are valid against the OpenAPI spec.
+    Be careful about the input that you pass to ``component``. ``apispec`` will not guarantee that the passed fields are valid against the OpenAPI spec.
 
 Rendering to YAML or JSON
 -------------------------
@@ -129,8 +128,8 @@ to document `Security Scheme Objects <https://github.com/OAI/OpenAPI-Specificati
     api_key_scheme = {"type": "apiKey", "in": "header", "name": "X-API-Key"}
     jwt_scheme = {"type": "http", "scheme": "bearer", "bearerFormat": "JWT"}
 
-    spec.components.security_scheme("api_key", **api_key_scheme)
-    spec.components.security_scheme("jwt", **jwt_scheme)
+    spec.components.security_scheme("api_key", api_key_scheme)
+    spec.components.security_scheme("jwt", jwt_scheme)
 
     pprint(spec.to_dict()["components"]["securitySchemes"], indent=2)
     # { 'api_key': {'in': 'header', 'name': 'X-API-Key', 'type': 'apiKey'},
diff --git a/docs/using_plugins.rst b/docs/using_plugins.rst
index f9bdf90..3904140 100644
--- a/docs/using_plugins.rst
+++ b/docs/using_plugins.rst
@@ -173,12 +173,12 @@ If your API uses `method-based dispatching <http://flask.pocoo.org/docs/0.12/vie
         def get(self):
             """Gist view
             ---
-            description: get a gist
+            description: Get a gist
             responses:
-            200:
-              content:
-                application/json:
-                  schema: GistSchema
+              200:
+                content:
+                  application/json:
+                    schema: GistSchema
             """
             pass
 
@@ -207,29 +207,25 @@ By default, Marshmallow `Nested` fields are represented by a `JSON Reference obj
 If the schema has been added to the spec via `spec.components.schema <apispec.core.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 `schema_name_resolver <apispec.ext.marshmallow.resolver>`
+nested schema. The default `resolver <apispec.ext.marshmallow.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 an alternative
+To change the behavior of the name resolution simply pass a
 function accepting a `Schema` class and returning a string to the plugin's
 constructor. 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: Circular-referencing schemas cannot be defined in-line due to infinite
-recursion so a `schema_name_resolver` function must return a string name when
-working with circular-referencing schemas.
+.. note::
+    Circular-referencing schemas cannot be defined in-line due to infinite
+    recursion so a `schema_name_resolver` function must return a string name when
+    working with circular-referencing schemas.
 
 Schema Modifiers
 ****************
 
-`Schema` instances can be initialized with modifier parameters to exclude
-fields or ignore the absence of required fields. apispec will respect
-schema modifiers in the generated schema definition. If a particular schema is
-initialized in an application with modifiers, it may be added to the spec with
-each set of modifiers and apispec will treat each unique set of modifiers --
-including no modifiers - as a unique schema definition.
+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 Fields
 ***************
diff --git a/docs/writing_plugins.rst b/docs/writing_plugins.rst
index e9e5fab..121349d 100644
--- a/docs/writing_plugins.rst
+++ b/docs/writing_plugins.rst
@@ -40,12 +40,9 @@ A plugin with a path helper function may look something like this:
 The ``init_spec`` Method
 ------------------------
 
-`BasePlugin` has an `init_spec` method that `APISpec` calls on each plugin at initialization with the spec object itself as parameter. It is no-op by default, but a plugin may override it to access and store useful information from the spec object.
-
-A typical use case is conditional code depending on the OpenAPI version, which is stored as ``openapi_version`` attribute of the spec object. See source code for `apispec.ext.marshmallow.MarshmallowPlugin </_modules/apispec/ext/marshmallow.html>`_ for an example.
-
-Note that the OpenAPI version is stored in the spec object as an `apispec.utils.OpenAPIVersion`. An ``OpenAPIVersion`` instance provides the version as string as well as shortcuts to version digits.
+`BasePlugin` has an `init_spec` method that `APISpec` calls on each plugin at initialization with the spec object itself as parameter. It is no-op by default, but a plugin may override it to access and store useful information on the spec object.
 
+A typical use case is conditional code depending on the OpenAPI version, which is stored as ``openapi_version`` on the `spec` object. See source code for `apispec.ext.marshmallow.MarshmallowPlugin </_modules/apispec/ext/marshmallow.html>`_ for an example.
 
 Example: Docstring-parsing Plugin
 ---------------------------------
@@ -98,7 +95,7 @@ To use the plugin:
             200:
               content:
                 application/json:
-                schema: '#/definitions/Gist'
+                  schema: '#/definitions/Gist'
         """
         pass
 
@@ -111,7 +108,7 @@ To use the plugin:
 Next Steps
 ----------
 
-To learn more about how to write plugins
+To learn more about how to write plugins:
 
 * Consult the :doc:`Core API docs <api_core>` for `BasePlugin <apispec.BasePlugin>`
 * View the source for an existing apispec plugin, e.g. `FlaskPlugin <https://github.com/marshmallow-code/apispec-webframeworks/blob/master/apispec_webframeworks/flask.py>`_.


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index cd13657..4449450 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -8,7 +8,7 @@ from apispec import APISpec, BasePlugin
 from apispec.exceptions import APISpecError, DuplicateComponentNameError
 
 from .utils import (
-    get_definitions,
+    get_schemas,
     get_paths,
     get_parameters,
     get_responses,
@@ -100,7 +100,7 @@ class TestMetadata:
             }
         }
         spec.components.schema(
-            "definition", properties=properties, description="definiton description"
+            "definition", {"properties": properties, "description": "description"}
         )
         metadata = spec.to_dict()
         assert metadata["components"]["schemas"].get("ErrorResponse", False)
@@ -124,55 +124,122 @@ class TestTags:
         assert spec.to_dict()["tags"] == [{"name": "tag1"}, {"name": "tag2"}]
 
 
-class TestDefinitions:
+class TestComponents:
 
     properties = {
         "id": {"type": "integer", "format": "int64"},
         "name": {"type": "string", "example": "doggie"},
     }
 
-    def test_definition(self, spec):
-        spec.components.schema("Pet", properties=self.properties)
-        defs = get_definitions(spec)
+    def test_schema(self, spec):
+        spec.components.schema("Pet", {"properties": self.properties})
+        defs = get_schemas(spec)
         assert "Pet" in defs
         assert defs["Pet"]["properties"] == self.properties
 
-    def test_definition_is_chainable(self, spec):
-        spec.components.schema("Pet", properties={}).schema("Plant", properties={})
-        defs = get_definitions(spec)
+    def test_schema_is_chainable(self, spec):
+        spec.components.schema("Pet", {"properties": {}}).schema(
+            "Plant", {"properties": {}}
+        )
+        defs = get_schemas(spec)
         assert "Pet" in defs
         assert "Plant" in defs
 
-    def test_definition_description(self, spec):
+    def test_schema_description(self, spec):
         model_description = "An animal which lives with humans."
         spec.components.schema(
-            "Pet", properties=self.properties, description=model_description
+            "Pet", {"properties": self.properties, "description": model_description}
         )
-        defs = get_definitions(spec)
+        defs = get_schemas(spec)
         assert defs["Pet"]["description"] == model_description
 
-    def test_definition_stores_enum(self, spec):
+    def test_schema_stores_enum(self, spec):
         enum = ["name", "photoUrls"]
-        spec.components.schema("Pet", properties=self.properties, enum=enum)
-        defs = get_definitions(spec)
+        spec.components.schema("Pet", {"properties": self.properties, "enum": enum})
+        defs = get_schemas(spec)
         assert defs["Pet"]["enum"] == enum
 
-    def test_definition_extra_fields(self, spec):
-        extra_fields = {"discriminator": "name"}
+    def test_schema_discriminator(self, spec):
         spec.components.schema(
-            "Pet", properties=self.properties, extra_fields=extra_fields
+            "Pet", {"properties": self.properties, "discriminator": "name"}
         )
-        defs = get_definitions(spec)
+        defs = get_schemas(spec)
         assert defs["Pet"]["discriminator"] == "name"
 
-    def test_definition_duplicate_name(self, spec):
-        spec.components.schema("Pet", properties=self.properties)
+    def test_schema_duplicate_name(self, spec):
+        spec.components.schema("Pet", {"properties": self.properties})
         with pytest.raises(
             DuplicateComponentNameError,
             match='Another schema with name "Pet" is already registered.',
         ):
             spec.components.schema("Pet", properties=self.properties)
 
+    def test_parameter(self, spec):
+        parameter = {"format": "int64", "type": "integer"}
+        spec.components.parameter("PetId", "path", parameter)
+        params = get_parameters(spec)
+        assert params["PetId"] == {
+            "format": "int64",
+            "type": "integer",
+            "in": "path",
+            "name": "PetId",
+        }
+
+    def test_parameter_is_chainable(self, spec):
+        spec.components.parameter("param1", "path").parameter("param2", "path")
+        params = get_parameters(spec)
+        assert "param1" in params
+        assert "param2" in params
+
+    def test_parameter_duplicate_name(self, spec):
+        spec.components.parameter("test_parameter", "path")
+        with pytest.raises(
+            DuplicateComponentNameError,
+            match='Another parameter with name "test_parameter" is already registered.',
+        ):
+            spec.components.parameter("test_parameter", "path")
+
+    def test_response(self, spec):
+        response = {"description": "Pet not found"}
+        spec.components.response("NotFound", response)
+        responses = get_responses(spec)
+        assert responses["NotFound"] == response
+
+    def test_response_is_chainable(self, spec):
+        spec.components.response("resp1").response("resp2")
+        responses = get_responses(spec)
+        assert "resp1" in responses
+        assert "resp2" in responses
+
+    def test_response_duplicate_name(self, spec):
+        spec.components.response("test_response")
+        with pytest.raises(
+            DuplicateComponentNameError,
+            match='Another response with name "test_response" is already registered.',
+        ):
+            spec.components.response("test_response")
+
+    def test_security_scheme(self, spec):
+        sec_scheme = {"type": "apiKey", "in": "header", "name": "X-API-Key"}
+        spec.components.security_scheme("ApiKeyAuth", sec_scheme)
+        assert get_security_schemes(spec)["ApiKeyAuth"] == sec_scheme
+
+    def test_security_scheme_is_chainable(self, spec):
+        spec.components.security_scheme("sec_1", {}).security_scheme("sec_2", {})
+        security_schemes = get_security_schemes(spec)
+        assert "sec_1" in security_schemes
+        assert "sec_2" in security_schemes
+
+    def test_security_scheme_duplicate_name(self, spec):
+        sec_scheme_1 = {"type": "apiKey", "in": "header", "name": "X-API-Key"}
+        sec_scheme_2 = {"type": "apiKey", "in": "header", "name": "X-API-Key-2"}
+        spec.components.security_scheme("ApiKeyAuth", sec_scheme_1)
+        with pytest.raises(
+            DuplicateComponentNameError,
+            match='Another security scheme with name "ApiKeyAuth" is already registered.',
+        ):
+            spec.components.security_scheme("ApiKeyAuth", sec_scheme_2)
+
     def test_to_yaml(self, spec):
         enum = ["name", "photoUrls"]
         spec.components.schema("Pet", properties=self.properties, enum=enum)
@@ -310,9 +377,7 @@ class TestPath:
     def test_parameter(self, spec):
         route_spec = self.paths["/pet/{petId}"]["get"]
 
-        spec.components.parameter(
-            "test_parameter", "path", **route_spec["parameters"][0]
-        )
+        spec.components.parameter("test_parameter", "path", route_spec["parameters"][0])
 
         spec.path(
             path="/pet/{petId}", operations={"get": {"parameters": ["test_parameter"]}}
@@ -335,29 +400,10 @@ class TestPath:
                 == metadata["components"]["parameters"]["test_parameter"]
             )
 
-    def test_parameter_is_chainable(self, spec):
-        spec.components.parameter("param1", "path").parameter("param2", "path")
-        params = get_parameters(spec)
-        assert "param1" in params
-        assert "param2" in params
-
-    def test_parameter_duplicate_name(self, spec):
-        route_spec = self.paths["/pet/{petId}"]["get"]
-        spec.components.parameter(
-            "test_parameter", "path", **route_spec["parameters"][0]
-        )
-        with pytest.raises(
-            DuplicateComponentNameError,
-            match='Another parameter with name "test_parameter" is already registered.',
-        ):
-            spec.components.parameter(
-                "test_parameter", "path", **route_spec["parameters"][0]
-            )
-
     def test_response(self, spec):
         route_spec = self.paths["/pet/{petId}"]["get"]
 
-        spec.components.response("test_response", **route_spec["responses"]["200"])
+        spec.components.response("test_response", route_spec["responses"]["200"])
 
         spec.path(
             path="/pet/{petId}",
@@ -381,42 +427,6 @@ class TestPath:
                 == metadata["components"]["responses"]["test_response"]
             )
 
-    def test_response_is_chainable(self, spec):
-        spec.components.response("resp1").response("resp2")
-        responses = get_responses(spec)
-        assert "resp1" in responses
-        assert "resp2" in responses
-
-    def test_response_duplicate_name(self, spec):
-        route_spec = self.paths["/pet/{petId}"]["get"]
-        spec.components.response("test_response", **route_spec["responses"]["200"])
-        with pytest.raises(
-            DuplicateComponentNameError,
-            match='Another response with name "test_response" is already registered.',
-        ):
-            spec.components.response("test_response", **route_spec["responses"]["200"])
-
-    def test_security_scheme(self, spec):
-        sec_scheme = {"type": "apiKey", "in": "header", "name": "X-API-Key"}
-        spec.components.security_scheme("ApiKeyAuth", **sec_scheme)
-        assert get_security_schemes(spec)["ApiKeyAuth"] == sec_scheme
-
-    def test_security_scheme_is_chainable(self, spec):
-        spec.components.security_scheme("sec_1").security_scheme("sec_2")
-        security_schemes = get_security_schemes(spec)
-        assert "sec_1" in security_schemes
-        assert "sec_2" in security_schemes
-
-    def test_security_scheme_duplicate_name(self, spec):
-        sec_scheme_1 = {"type": "apiKey", "in": "header", "name": "X-API-Key"}
-        sec_scheme_2 = {"type": "apiKey", "in": "header", "name": "X-API-Key-2"}
-        spec.components.security_scheme("ApiKeyAuth", **sec_scheme_1)
-        with pytest.raises(
-            DuplicateComponentNameError,
-            match='Another security scheme with name "ApiKeyAuth" is already registered.',
-        ):
-            spec.components.security_scheme("ApiKeyAuth", **sec_scheme_2)
-
     def test_path_check_invalid_http_method(self, spec):
         spec.path("/pet/{petId}", operations={"get": {}})
         spec.path("/pet/{petId}", operations={"x-dummy": {}})
@@ -433,11 +443,11 @@ class TestPlugins:
                 if not return_none:
                     return {"properties": {"name": {"type": "string"}}}
 
-            def parameter_helper(self, **kwargs):
+            def parameter_helper(self, parameter, **kwargs):
                 if not return_none:
                     return {"description": "some parameter"}
 
-            def response_helper(self, **kwargs):
+            def response_helper(self, response, **kwargs):
                 if not return_none:
                     return {"description": "42"}
 
@@ -462,8 +472,8 @@ class TestPlugins:
             openapi_version=openapi_version,
             plugins=(self.test_plugin_factory(return_none),),
         )
-        spec.components.schema("Pet", {})
-        definitions = get_definitions(spec)
+        spec.components.schema("Pet")
+        definitions = get_schemas(spec)
         if return_none:
             assert definitions["Pet"] == {}
         else:
@@ -478,7 +488,7 @@ class TestPlugins:
             openapi_version=openapi_version,
             plugins=(self.test_plugin_factory(return_none),),
         )
-        spec.components.parameter("Pet", "body", **{})
+        spec.components.parameter("Pet", "body", {})
         parameters = get_parameters(spec)
         if return_none:
             assert parameters["Pet"] == {"in": "body", "name": "Pet"}
@@ -498,7 +508,7 @@ class TestPlugins:
             openapi_version=openapi_version,
             plugins=(self.test_plugin_factory(return_none),),
         )
-        spec.components.response("Pet", **{})
+        spec.components.response("Pet", {})
         responses = get_responses(spec)
         if return_none:
             assert responses["Pet"] == {}
diff --git a/tests/test_ext_marshmallow.py b/tests/test_ext_marshmallow.py
index 17079e5..f474b5f 100644
--- a/tests/test_ext_marshmallow.py
+++ b/tests/test_ext_marshmallow.py
@@ -22,22 +22,22 @@ from .schemas import (
     AnalysisWithListSchema,
 )
 
-from .utils import get_definitions, get_parameters, get_responses, get_paths, ref_path
+from .utils import get_schemas, get_parameters, get_responses, get_paths, ref_path
 
 
 class TestDefinitionHelper:
     @pytest.mark.parametrize("schema", [PetSchema, PetSchema()])
     def test_can_use_schema_as_definition(self, spec, schema):
         spec.components.schema("Pet", schema=schema)
-        definitions = get_definitions(spec)
+        definitions = get_schemas(spec)
         props = definitions["Pet"]["properties"]
 
         assert props["id"]["type"] == "integer"
         assert props["name"]["type"] == "string"
 
     def test_schema_helper_without_schema(self, spec):
-        spec.components.schema("Pet", properties={"key": {"type": "integer"}})
-        definitions = get_definitions(spec)
+        spec.components.schema("Pet", {"properties": {"key": {"type": "integer"}}})
+        definitions = get_schemas(spec)
         assert definitions["Pet"]["properties"] == {"key": {"type": "integer"}}
 
     @pytest.mark.parametrize("schema", [AnalysisSchema, AnalysisSchema()])
@@ -51,7 +51,7 @@ class TestDefinitionHelper:
             openapi_version="2.0",
             plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
         )
-        assert {} == get_definitions(spec)
+        assert {} == get_schemas(spec)
 
         spec.components.schema("analysis", schema=schema)
         spec.path(
@@ -62,7 +62,7 @@ class TestDefinitionHelper:
                 }
             },
         )
-        definitions = get_definitions(spec)
+        definitions = get_schemas(spec)
         assert 3 == len(definitions)
 
         assert "analysis" in definitions
@@ -82,7 +82,7 @@ class TestDefinitionHelper:
             openapi_version="2.0",
             plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
         )
-        assert {} == get_definitions(spec)
+        assert {} == get_schemas(spec)
 
         spec.components.schema("analysis", schema=schema)
         spec.path(
@@ -93,7 +93,7 @@ class TestDefinitionHelper:
                 }
             },
         )
-        definitions = get_definitions(spec)
+        definitions = get_schemas(spec)
         assert 3 == len(definitions)
 
         assert "analysis" in definitions
@@ -112,7 +112,7 @@ class TestDefinitionHelper:
             openapi_version="2.0",
             plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
         )
-        assert {} == get_definitions(spec)
+        assert {} == get_schemas(spec)
 
         with pytest.raises(
             APISpecError, match="Name resolver returned None for schema"
@@ -135,7 +135,7 @@ class TestDefinitionHelper:
 
         spec.components.schema("MultiModifierSchema", schema=MultiModifierSchema)
 
-        definitions = get_definitions(spec)
+        definitions = get_schemas(spec)
         pet_unmodified_ref = definitions["MultiModifierSchema"]["properties"][
             "pet_unmodified"
         ]
@@ -157,7 +157,7 @@ class TestDefinitionHelper:
         ):
             spec.components.schema("NameClashSchema", schema=NameClashSchema)
 
-        definitions = get_definitions(spec)
+        definitions = get_schemas(spec)
 
         assert "Pet" in definitions
         assert "Pet1" in definitions
@@ -167,10 +167,10 @@ class TestComponentParameterHelper:
     @pytest.mark.parametrize("schema", [PetSchema, PetSchema()])
     def test_can_use_schema_in_parameter(self, spec, schema):
         if spec.openapi_version.major < 3:
-            kwargs = {"schema": schema}
+            param = {"schema": schema}
         else:
-            kwargs = {"content": {"application/json": {"schema": schema}}}
-        spec.components.parameter("Pet", "body", **kwargs)
+            param = {"content": {"application/json": {"schema": schema}}}
+        spec.components.parameter("Pet", "body", param)
         parameter = get_parameters(spec)["Pet"]
         assert parameter["in"] == "body"
         if spec.openapi_version.major < 3:
@@ -190,10 +190,10 @@ class TestComponentResponseHelper:
     @pytest.mark.parametrize("schema", [PetSchema, PetSchema()])
     def test_can_use_schema_in_response(self, spec, schema):
         if spec.openapi_version.major < 3:
-            kwargs = {"schema": schema}
+            resp = {"schema": schema}
         else:
-            kwargs = {"content": {"application/json": {"schema": schema}}}
-        spec.components.response("GetPetOk", **kwargs)
+            resp = {"content": {"application/json": {"schema": schema}}}
+        spec.components.response("GetPetOk", resp)
         response = get_responses(spec)["GetPetOk"]
         if spec.openapi_version.major < 3:
             reference = response["schema"]
@@ -234,9 +234,9 @@ class TestCustomField:
         spec_fixture.spec.components.schema("CustomPetA", schema=CustomPetASchema)
         spec_fixture.spec.components.schema("CustomPetB", schema=CustomPetBSchema)
 
-        props_0 = get_definitions(spec_fixture.spec)["Pet"]["properties"]
-        props_a = get_definitions(spec_fixture.spec)["CustomPetA"]["properties"]
-        props_b = get_definitions(spec_fixture.spec)["CustomPetB"]["properties"]
+        props_0 = get_schemas(spec_fixture.spec)["Pet"]["properties"]
+        props_a = get_schemas(spec_fixture.spec)["CustomPetA"]["properties"]
+        props_b = get_schemas(spec_fixture.spec)["CustomPetB"]["properties"]
 
         assert props_0["name"]["type"] == "string"
         assert "format" not in props_0["name"]
@@ -632,7 +632,7 @@ class TestOperationHelper:
 class TestCircularReference:
     def test_circular_referencing_schemas(self, spec):
         spec.components.schema("Analysis", schema=AnalysisSchema)
-        definitions = get_definitions(spec)
+        definitions = get_schemas(spec)
         ref = definitions["Analysis"]["properties"]["sample"]["$ref"]
         assert ref == ref_path(spec) + "Sample"
 
@@ -641,13 +641,13 @@ class TestCircularReference:
 class TestSelfReference:
     def test_self_referencing_field_single(self, spec):
         spec.components.schema("SelfReference", schema=SelfReferencingSchema)
-        definitions = get_definitions(spec)
+        definitions = get_schemas(spec)
         ref = definitions["SelfReference"]["properties"]["single"]["$ref"]
         assert ref == ref_path(spec) + "SelfReference"
 
     def test_self_referencing_field_many(self, spec):
         spec.components.schema("SelfReference", schema=SelfReferencingSchema)
-        definitions = get_definitions(spec)
+        definitions = get_schemas(spec)
         result = definitions["SelfReference"]["properties"]["many"]
         assert result == {
             "type": "array",
@@ -658,20 +658,20 @@ class TestSelfReference:
 class TestOrderedSchema:
     def test_ordered_schema(self, spec):
         spec.components.schema("Ordered", schema=OrderedSchema)
-        result = get_definitions(spec)["Ordered"]["properties"]
+        result = get_schemas(spec)["Ordered"]["properties"]
         assert list(result.keys()) == ["field1", "field2", "field3", "field4", "field5"]
 
 
 class TestFieldWithCustomProps:
     def test_field_with_custom_props(self, spec):
         spec.components.schema("PatternedObject", schema=PatternedObjectSchema)
-        result = get_definitions(spec)["PatternedObject"]["properties"]["count"]
+        result = get_schemas(spec)["PatternedObject"]["properties"]["count"]
         assert "x-count" in result
         assert result["x-count"] == 1
 
     def test_field_with_custom_props_passed_as_snake_case(self, spec):
         spec.components.schema("PatternedObject", schema=PatternedObjectSchema)
-        result = get_definitions(spec)["PatternedObject"]["properties"]["count2"]
+        result = get_schemas(spec)["PatternedObject"]["properties"]["count2"]
         assert "x-count2" in result
         assert result["x-count2"] == 2
 
@@ -679,7 +679,7 @@ class TestFieldWithCustomProps:
 class TestSchemaWithDefaultValues:
     def test_schema_with_default_values(self, spec):
         spec.components.schema("DefaultValuesSchema", schema=DefaultValuesSchema)
-        definitions = get_definitions(spec)
+        definitions = get_schemas(spec)
         props = definitions["DefaultValuesSchema"]["properties"]
         assert props["number_auto_default"]["default"] == 12
         assert props["number_manual_default"]["default"] == 42
@@ -697,7 +697,7 @@ class TestDictValues:
             dict_field = Dict(values=String())
 
         spec.components.schema("SchemaWithDict", schema=SchemaWithDict)
-        result = get_definitions(spec)["SchemaWithDict"]["properties"]["dict_field"]
+        result = get_schemas(spec)["SchemaWithDict"]["properties"]["dict_field"]
         assert result == {"type": "object", "additionalProperties": {"type": "string"}}
 
     def test_dict_with_empty_values_field(self, spec):
@@ -705,7 +705,7 @@ class TestDictValues:
             dict_field = Dict()
 
         spec.components.schema("SchemaWithDict", schema=SchemaWithDict)
-        result = get_definitions(spec)["SchemaWithDict"]["properties"]["dict_field"]
+        result = get_schemas(spec)["SchemaWithDict"]["properties"]["dict_field"]
         assert result == {"type": "object"}
 
     def test_dict_with_nested(self, spec):
@@ -714,9 +714,9 @@ class TestDictValues:
 
         spec.components.schema("SchemaWithDict", schema=SchemaWithDict)
 
-        assert len(get_definitions(spec)) == 2
+        assert len(get_schemas(spec)) == 2
 
-        result = get_definitions(spec)["SchemaWithDict"]["properties"]["dict_field"]
+        result = get_schemas(spec)["SchemaWithDict"]["properties"]["dict_field"]
         assert result == {
             "additionalProperties": {"$ref": ref_path(spec) + "Pet"},
             "type": "object",
@@ -730,7 +730,7 @@ class TestList:
 
         spec.components.schema("SchemaWithList", schema=SchemaWithList)
 
-        assert len(get_definitions(spec)) == 2
+        assert len(get_schemas(spec)) == 2
 
-        result = get_definitions(spec)["SchemaWithList"]["properties"]["list_field"]
+        result = get_schemas(spec)["SchemaWithList"]["properties"]["list_field"]
         assert result == {"items": {"$ref": ref_path(spec) + "Pet"}, "type": "array"}
diff --git a/tests/test_openapi.py b/tests/test_openapi.py
index 8e1d5ea..41b2298 100644
--- a/tests/test_openapi.py
+++ b/tests/test_openapi.py
@@ -12,7 +12,7 @@ from apispec.ext.marshmallow import MarshmallowPlugin
 from apispec.ext.marshmallow.openapi import MARSHMALLOW_VERSION_INFO
 from apispec import exceptions, utils, APISpec
 
-from .utils import get_definitions, ref_path
+from .utils import get_schemas, ref_path
 
 
 class TestMarshmallowFieldToOpenAPI:
@@ -647,7 +647,7 @@ class TestNesting:
             child = fields.Nested(Child, **{modifier: ("i",)})
 
         spec_fixture.openapi.schema2jsonschema(Parent)
-        props = get_definitions(spec_fixture.spec)["Child"]["properties"]
+        props = get_schemas(spec_fixture.spec)["Child"]["properties"]
         assert ("i" in props) == (modifier == "only")
         assert ("j" not in props) == (modifier == "only")
 
@@ -662,7 +662,7 @@ class TestNesting:
             name = fields.Str()
 
         spec_fixture.spec.components.schema("Pet", schema=PetSchema)
-        props = get_definitions(spec_fixture.spec)
+        props = get_schemas(spec_fixture.spec)
 
         assert props["Category"] == spec_fixture.openapi.schema2jsonschema(
             category_schema
@@ -682,7 +682,7 @@ class TestNesting:
 
         spec.components.schema("Pet", schema=PetSchema)
 
-        category_props = get_definitions(spec)["Category"]["properties"]
+        category_props = get_schemas(spec)["Category"]["properties"]
         assert "breed" not in category_props
 
     def test_nested_field_with_property(self, spec_fixture):
@@ -720,9 +720,7 @@ def test_openapi_tools_validate_v2():
     openapi = ma_plugin.openapi
 
     spec.components.schema("Category", schema=CategorySchema)
-    spec.components.schema(
-        "Pet", schema=PetSchema, extra_fields={"discriminator": "name"}
-    )
+    spec.components.schema("Pet", {"discriminator": "name"}, schema=PetSchema)
 
     spec.path(
         view=None,
@@ -869,7 +867,7 @@ class ValidationSchema(Schema):
 class TestFieldValidation:
     def test_range(self, spec):
         spec.components.schema("Validation", schema=ValidationSchema)
-        result = get_definitions(spec)["Validation"]["properties"]["range"]
+        result = get_schemas(spec)["Validation"]["properties"]["range"]
 
         assert "minimum" in result
         assert result["minimum"] == 1
@@ -878,7 +876,7 @@ class TestFieldValidation:
 
     def test_multiple_ranges(self, spec):
         spec.components.schema("Validation", schema=ValidationSchema)
-        result = get_definitions(spec)["Validation"]["properties"]["multiple_ranges"]
+        result = get_schemas(spec)["Validation"]["properties"]["multiple_ranges"]
 
         assert "minimum" in result
         assert result["minimum"] == 3
@@ -887,7 +885,7 @@ class TestFieldValidation:
 
     def test_list_length(self, spec):
         spec.components.schema("Validation", schema=ValidationSchema)
-        result = get_definitions(spec)["Validation"]["properties"]["list_length"]
+        result = get_schemas(spec)["Validation"]["properties"]["list_length"]
 
         assert "minItems" in result
         assert result["minItems"] == 1
@@ -896,7 +894,7 @@ class TestFieldValidation:
 
     def test_string_length(self, spec):
         spec.components.schema("Validation", schema=ValidationSchema)
-        result = get_definitions(spec)["Validation"]["properties"]["string_length"]
+        result = get_schemas(spec)["Validation"]["properties"]["string_length"]
 
         assert "minLength" in result
         assert result["minLength"] == 1
@@ -905,7 +903,7 @@ class TestFieldValidation:
 
     def test_multiple_lengths(self, spec):
         spec.components.schema("Validation", schema=ValidationSchema)
-        result = get_definitions(spec)["Validation"]["properties"]["multiple_lengths"]
+        result = get_schemas(spec)["Validation"]["properties"]["multiple_lengths"]
 
         assert "minLength" in result
         assert result["minLength"] == 3
@@ -914,7 +912,7 @@ class TestFieldValidation:
 
     def test_equal_length(self, spec):
         spec.components.schema("Validation", schema=ValidationSchema)
-        result = get_definitions(spec)["Validation"]["properties"]["equal_length"]
+        result = get_schemas(spec)["Validation"]["properties"]["equal_length"]
 
         assert "minLength" in result
         assert result["minLength"] == 5
diff --git a/tests/utils.py b/tests/utils.py
index e098c23..ce7ccd2 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -2,7 +2,7 @@
 """Utilities to get elements of generated spec"""
 
 
-def get_definitions(spec):
+def get_schemas(spec):
     if spec.openapi_version.major < 3:
         return spec.to_dict()["definitions"]
     return spec.to_dict()["components"]["schemas"]