Repository: marshmallow-code/apispec
Pull request #305 (2018-10-07 20:59:35)

Rework spec: add Components class
I've been trying to work on https://github.com/marshmallow-code/apispec/issues/245.

This PR adds a `Components` class that stores the components. Components is v3 terminology. In v2, those are just top-level fields.

For now, it only has `add_schema` (formerly `spec.definition`) and `add_parameter` (formerly `spec.add_parameter`). It is a first step before adding other methods like `add_response`, `add_security_scheme`, etc (the point of #245).

I'm using v3 terminology because it is the future (hopefully...), but this is v2 compatible. I could even alias `add_definition` to `add_scheme` but I don't think it is necessary.

There's nothing mandatory in adding this `Components` class, we could keep all `add_xxx` methods in `APISpec`. I just thought it would be nicer this way.

From user perspective, the only difference is that

    spec.add_parameter(...)
    spec.definition(...)

become

    spec.components.add_parameter(...)
    spec.components.add_schema(...)

~The first commit is just a modification to the test to make sure we test the result (the doc that is produced) and not the internals (spec._definitions,...). It can be merged independently.~ (Done.)

Waiting for feedback about this before addressing the other components (reponses,...).

TODO:

- [x] Update changelog
- [x] Update docs

lafrech: I went ahead with response definitions.

I also added parameter/response components helpers to `BasePlugin`. I had to call them xxx_component helpers because response_helper already exists (see https://github.com/marshmallow-code/apispec/issues/315).

We don't need all types of component register methods and plugin helpers implemented to merge this, but those are the most important and they illustrate the point.

@sloria do you like the direction this is taking? Anything I should rework before this can be merged?
sloria: @lafrech Yes, this is looking good. I was a bit hesitant about it at first, but following your logic in https://github.com/marshmallow-code/apispec/issues/245#issuecomment-427745748 , it makes sense. I do like how it follows the v3 terminology. 

Kinda reminds me of elasticsearch-dsl's [aggregation API](https://elasticsearch-dsl.readthedocs.io/en/latest/search_dsl.html).

```python
s = Search()
s.aggs.bucket('tags_per_day', 'terms', field='tags')
```

It might be because I'm used to elasticsearch-dsl, but I have a slight preference towards the noun forms, e.g. `spec.components.definition`, for their terseness. It allows for more natural chaining, like so:

```python
spec.components\
   .definition('Gist', schema=GistSchema)\
   .parameter('GistParams', schema=GistParamSchema)\
    # ...
```

Thoughts?


lafrech: Nouns are fine. Let's go for nouns.
lafrech: Questions

- Rename `definition` (current apispec and OpenAPI v2 terminology) into `schema` (OpenAPI v3 terminology)? The change is breaking already in both cases.

- OK for removing `add_` from `add_parameter` and other `add_component` methods. Should I do the same with `add_tag` and `add_path`, which are methods of spec, not of spec.components, due to OpenAPIv3 terminology?

Assuming the answers are _keep definition_ and  _remove `add_` everywhere_, we'd get:

```py
spec.path(...)
spec.tag(...)
spec.components.definition(...)
spec.components.parameter(...)
spec.components.response(...)
```

- Rename `definition_helper` into `schema_helper`? The former is more explicit, but it could be because we're used to it. We should be consistent with the component name above.

- Remove `component` from the component helpers? It makes the name a bit long, but I had to do that for the response component helper, because a `response_helper` already exists. In https://github.com/marshmallow-code/apispec/issues/315, I suggest the existing `response_helper` could be removed...

sloria: > Rename definition (current apispec and OpenAPI v2 terminology) into schema (OpenAPI v3 terminology)? The change is breaking already in both cases.

If we're moving towards OpenAPI v3 terminology (which I think is a good idea), then we should use "schema" rather than "definition."

> OK for removing add_

Yes, remove add_ for consistency in all the method names.

> Rename definition_helper into schema_helper? The former is more explicit, but it could be because we're used to it.

"schema_helper" makes sense for consistency. Why do you say "definition_helper" is more consistent?

> Remove component from the component helpers? It makes the name a bit long, but I had to do that for the response component helper, because a response_helper already exists. In #315, I suggest the existing response_helper could be removed...

Since I can't recall the use case for the current `response_helper`, I'm fine getting rid of it. Then we can go with the more concise `parameter_helper`, `response_helper`, etc.

lafrech: > "schema_helper" makes sense for consistency. Why do you say "definition_helper" is more explicit?

I meant by itself `definition_helper` was more explicit than `schema_helper`, but consistency first and it must be the same as the name chosen above, of course.

I found `definition` could be clearer than `schema` in both places but here again, consistency first, so if we choose OpenAPIv3 terminology, then let's do it completely.

> Since I can't recall the use case for the current response_helper, I'm fine getting rid of it.

Good. Maybe it used to be practical for some use case, but since the plugin classes, it has become awkward to use and it has not benefit over the operation helper.

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 4d6d80c..6c3dcf2 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -4,6 +4,37 @@ Changelog
 1.0.0b5 (unreleased)
 ++++++++++++++++++++
 
+Features:
+
+- ``apispec.core.Components`` is added. Each ``APISpec`` instance has a
+  ``Components`` object used to define components such as schemas, parameters
+  or reponses. "Components" is the OpenAPI v3 terminology for those reusable
+  top-level objects.
+- ``apispec.core.Components.parameter`` and ``apispec.core.Components.response``
+  are added.
+- *Backwards-incompatible*: ``apispec.APISpec.add_path`` and
+  ``apispec.APISpec.add_tag`` are renamed to ``apispec.APISpec.path`` and
+  ``apispec.APISpec.tag``.
+- *Backwards-incompatible*: ``apispec.APISpec.definition`` is moved to the
+  ``Components`` class and renamed to ``apispec.core.Components.schema``.
+
+::
+
+    # apispec<1.0.0b5
+    spec.add_tag({'name': 'Pet', 'description': 'Operations on pets'})
+    spec.add_path('/pets/', operations=...)
+    spec.definition('Pet')
+    def PetSchema(Schema)
+        ...
+
+    # apispec>=1.0.0b5
+    spec.tag({'name': 'Pet', 'description': 'Operations on pets'})
+    spec.path('/pets/', operations=...)
+    spec.components.schema('Pet')
+    def PetSchema(Schema)
+        ...
+
+
 Deprecations/Removals:
 
 - The ``response_helper`` feature is removed. The same can be achieved from
@@ -12,7 +43,7 @@ Deprecations/Removals:
 1.0.0b4 (2018-10-28)
 ++++++++++++++++++++
 
-* *Backwards-incompatible*: ``apispec.ext.flask``,
+- *Backwards-incompatible*: ``apispec.ext.flask``,
   ``apispec.ext.bottle``, and ``apispec.ext.tornado`` are moved to
   a separate package, `apispec-webframeworks <https://github.com/marshmallow-code/apispec-webframeworks>`_.
   (:issue:`302`).
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 36b0af4..04443de 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -23,9 +23,7 @@ Questions, Feature Requests, Bug Reports, and Feedback. . .
 Setting Up for Local Development
 ++++++++++++++++++++++++++++++++
 
-1. Fork apispec_ on Github.
-
-::
+1. Fork apispec_ on Github. ::
 
     $ git clone https://github.com/marshmallow-code/apispec.git
     $ cd apispec
@@ -65,7 +63,6 @@ Pull Requests
 ++++++++++++++
 
 1. Create a new local branch.
-
 ::
 
     # For a new feature
@@ -75,7 +72,6 @@ Pull Requests
     $ git checkout -b fix-something 1.2-line
 
 2. Commit your changes. Write `good commit messages <http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html>`_.
-
 ::
 
     $ git commit -m "Detailed commit message"
diff --git a/README.rst b/README.rst
index 8180de4..df82d8c 100644
--- a/README.rst
+++ b/README.rst
@@ -81,7 +81,7 @@ Example Application
     spec.definition('Category', schema=CategorySchema)
     spec.definition('Pet', schema=PetSchema)
     with app.test_request_context():
-        spec.add_path(view=random_pet)
+        spec.path(view=random_pet)
 
 
 Generated OpenAPI Spec
diff --git a/apispec/__init__.py b/apispec/__init__.py
index 7505c9f..b7c6154 100755
--- a/apispec/__init__.py
+++ b/apispec/__init__.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
-"""Contains the main `APISpec` class.
-"""
+"""Contains main apispec classes: `APISpec` and `BasePlugin`"""
+
 from .core import APISpec
 from .plugin import BasePlugin
 
diff --git a/apispec/core.py b/apispec/core.py
index 8e23eeb..2144823 100644
--- a/apispec/core.py
+++ b/apispec/core.py
@@ -3,9 +3,9 @@
 import re
 from collections import OrderedDict
 
-from apispec.compat import iterkeys
+from apispec.compat import iterkeys, iteritems
 from .exceptions import APISpecError, PluginMethodNotImplementedError
-from .utils import OpenAPIVersion
+from .utils import OpenAPIVersion, deepupdate
 
 VALID_METHODS = [
     'get',
@@ -37,30 +37,141 @@ def clean_operations(operations, openapi_major_version):
             'One or more HTTP methods are invalid: {0}'.format(', '.join(invalid)),
         )
 
-    def get_ref(param, openapi_major_version):
-        if isinstance(param, dict):
-            return param
+    def get_ref(obj_type, obj, openapi_major_version):
+        """Return object or rererence
+
+        If obj is a dict, it is assumed to be a complete description and it is returned as is.
+        Otherwise, it is assumed to be a reference name as string and the corresponding $ref
+        string is returned.
+
+        :param str obj_type: 'parameter' or 'response'
+        :param dict|str obj: parameter or response in dict form or as ref_id string
+        :param int openapi_major_version: The major version of the OpenAPI standard
+        """
+        if isinstance(obj, dict):
+            return obj
 
         ref_paths = {
-            2: 'parameters',
-            3: 'components/parameters',
+            'parameter': {
+                2: 'parameters',
+                3: 'components/parameters',
+            },
+            'response': {
+                2: 'responses',
+                3: 'components/responses',
+            },
         }
-        ref_path = ref_paths[openapi_major_version]
-        return {'$ref': '#/{0}/{1}'.format(ref_path, param)}
+        ref_path = ref_paths[obj_type][openapi_major_version]
+        return {'$ref': '#/{0}/{1}'.format(ref_path, obj)}
 
     for operation in (operations or {}).values():
         if 'parameters' in operation:
-            parameters = operation.get('parameters')
+            parameters = operation['parameters']
             for parameter in parameters:
                 if (
-                    isinstance(parameter, dict) and
-                    'in' in parameter and parameter['in'] == 'path'
+                        isinstance(parameter, dict) and
+                        'in' in parameter and parameter['in'] == 'path'
                 ):
                     parameter['required'] = True
             operation['parameters'] = [
-                get_ref(p, openapi_major_version)
-                for p in parameters
+                get_ref('parameter', p, openapi_major_version) for p in parameters
             ]
+        if 'responses' in operation:
+            for code, response in iteritems(operation['responses']):
+                operation['responses'][code] = get_ref('response', response, openapi_major_version)
+
+
+class Components(object):
+    """Stores OpenAPI components
+
+    Components are top-level fields in Openapi v2.
+    They became sub-fields of "components" top-level field in Openapi v3.
+    """
+    def __init__(self, plugins, openapi_version):
+        self._plugins = plugins
+        self.openapi_version = openapi_version
+        self._schemas = {}
+        self._parameters = {}
+        self._responses = {}
+
+    def to_dict(self):
+        schemas_key = 'definitions' if self.openapi_version.major < 3 else 'schemas'
+        return {
+            'parameters': self._parameters,
+            'responses': self._responses,
+            schemas_key: self._schemas,
+        }
+
+    def schema(
+            self, name, properties=None, enum=None, description=None, extra_fields=None,
+            **kwargs
+    ):
+        """Add a new definition to the spec.
+
+        .. note::
+
+            If you are using `apispec.ext.marshmallow`, you can pass fields' metadata as
+            additional keyword arguments.
+
+            For example, to add ``enum`` and ``description`` to your field: ::
+
+                status = fields.String(
+                    required=True,
+                    enum=['open', 'closed'],
+                    description='Status (open or closed)',
+                )
+
+        https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#definitionsObject
+        """
+        ret = {}
+        # Execute all helpers from plugins
+        for plugin in self._plugins:
+            try:
+                ret.update(plugin.schema_helper(name, definition=ret, **kwargs))
+            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
+
+    def parameter(self, param_id, location, **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.
+        """
+        ret = kwargs.copy()
+        ret.setdefault('name', param_id)
+        ret['in'] = location
+        # Execute all helpers from plugins
+        for plugin in self._plugins:
+            try:
+                ret.update(plugin.parameter_helper(**kwargs))
+            except PluginMethodNotImplementedError:
+                continue
+        self._parameters[param_id] = ret
+
+    def response(self, ref_id, **kwargs):
+        """Add a response which can be referenced.
+
+        :param str ref_id: ref_id to use as reference
+        :param dict kwargs: response fields
+        """
+        ret = kwargs.copy()
+        # Execute all helpers from plugins
+        for plugin in self._plugins:
+            try:
+                ret.update(plugin.response_helper(**kwargs))
+            except PluginMethodNotImplementedError:
+                continue
+        self._responses[ref_id] = ret
 
 
 class APISpec(object):
@@ -82,8 +193,6 @@ class APISpec(object):
         self.options = options
 
         # Metadata
-        self._definitions = {}
-        self._parameters = {}
         self._tags = []
         self._paths = OrderedDict()
 
@@ -92,60 +201,40 @@ class APISpec(object):
         for plugin in self.plugins:
             plugin.init_spec(self)
 
+        # Components
+        self.components = Components(self.plugins, self.openapi_version)
+
     def to_dict(self):
         ret = {
             'paths': self._paths,
             'tags': self._tags,
+            'info': {
+                'title': self.title,
+                'version': self.version,
+            },
         }
-
-        if self.openapi_version.major == 2:
+        if self.openapi_version.major < 3:
             ret['swagger'] = self.openapi_version.vstring
-            ret['definitions'] = self._definitions
-            ret['parameters'] = self._parameters
-            ret.update(self.options)
-
-        elif self.openapi_version.major == 3:
+            ret.update(self.components.to_dict())
+        else:
             ret['openapi'] = self.openapi_version.vstring
-            ret.update(self.options)
-
-            # deep update components object
-            components = ret.setdefault('components', {})
-            components.setdefault('schemas', {}).update(self._definitions)
-            components.setdefault('parameters', {}).update(self._parameters)
-
-        # Add title and version unless those were provided in options['info']
-        info = ret.setdefault('info', {})
-        info.setdefault('title', self.title)
-        info.setdefault('version', self.version)
-
+            ret.update({'components': self.components.to_dict()})
+        ret = deepupdate(ret, self.options)
         return ret
 
     def to_yaml(self):
-        """Render the spec to YAML. Requires PyYAML to be installed.
-        """
+        """Render the spec to YAML. Requires PyYAML to be installed."""
         from .yaml_utils import dict_to_yaml
         return dict_to_yaml(self.to_dict())
 
-    def add_parameter(self, param_id, location, **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.
-        """
-        if 'name' not in kwargs:
-            kwargs['name'] = param_id
-        kwargs['in'] = location
-        self._parameters[param_id] = kwargs
-
-    def add_tag(self, tag):
+    def tag(self, tag):
         """ Store information about a tag.
 
         :param dict tag: the dictionary storing information about the tag.
         """
         self._tags.append(tag)
 
-    def add_path(self, path=None, operations=None, **kwargs):
+    def path(self, path=None, operations=None, **kwargs):
         """Add a new path object to the spec.
 
         https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#pathsObject
@@ -184,41 +273,3 @@ class APISpec(object):
         clean_operations(operations, self.openapi_version.major)
 
         self._paths.setdefault(path, operations).update(operations)
-
-    def definition(
-        self, name, properties=None, enum=None, description=None, extra_fields=None,
-        **kwargs
-    ):
-        """Add a new definition to the spec.
-
-        .. note::
-
-            If you are using `apispec.ext.marshmallow`, you can pass fields' metadata as
-            additional keyword arguments.
-
-            For example, to add ``enum`` and ``description`` to your field: ::
-
-                status = fields.String(
-                    required=True,
-                    enum=['open', 'closed'],
-                    description='Status (open or closed)',
-                )
-
-        https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#definitionsObject
-        """
-        ret = {}
-        # Execute all helpers from plugins
-        for plugin in self.plugins:
-            try:
-                ret.update(plugin.definition_helper(name, definition=ret, **kwargs))
-            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._definitions[name] = ret
diff --git a/apispec/ext/marshmallow/__init__.py b/apispec/ext/marshmallow/__init__.py
index 1415cdc..d4b7f76 100644
--- a/apispec/ext/marshmallow/__init__.py
+++ b/apispec/ext/marshmallow/__init__.py
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 """Marshmallow plugin for apispec. Allows passing a marshmallow
 `Schema` to `APISpec.definition <apispec.APISpec.definition>`
-and `APISpec.add_path <apispec.APISpec.add_path>` (for responses).
+and `APISpec.path <apispec.APISpec.path>` (for responses).
 
 Requires marshmallow>=2.15.2.
 
@@ -21,7 +21,7 @@ Requires marshmallow>=2.15.2.
             default_doc="The current datetime"
         )
 
-    spec.definition('User', schema=UserSchema)
+    spec.components.schema('User', schema=UserSchema)
     pprint(spec.to_dict()['definitions'])
     # {'User': {'properties': {'id': {'format': 'int32', 'type': 'integer'},
     #                         'name': {'description': "The user's name",
@@ -85,7 +85,7 @@ class MarshmallowPlugin(BasePlugin):
                     nested_schema_class,
                 )
                 if definition_name:
-                    self.spec.definition(
+                    self.spec.components.schema(
                         definition_name,
                         schema=nested_schema_class,
                     )
@@ -155,7 +155,7 @@ class MarshmallowPlugin(BasePlugin):
         """
         return self.openapi.map_to_openapi_type(*args)
 
-    def definition_helper(self, name, schema, **kwargs):
+    def schema_helper(self, name, schema, **kwargs):
         """Definition helper that allows using a marshmallow
         :class:`Schema <marshmallow.Schema>` to provide OpenAPI
         metadata.
@@ -177,6 +177,25 @@ class MarshmallowPlugin(BasePlugin):
 
         return json_schema
 
+    def parameter_helper(self, **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.
+        """
+        # In OpenAPIv3, this only works when using the complex form using "content"
+        self.resolve_schema(kwargs, dump=False, load=True)
+        return kwargs
+
+    def response_helper(self, **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.
+        """
+        self.resolve_schema(kwargs, dump=True, load=False)
+        return kwargs
+
     def operation_helper(self, operations, **kwargs):
         for operation in operations.values():
             if not isinstance(operation, dict):
diff --git a/apispec/plugin.py b/apispec/plugin.py
index 7631c84..2c49a57 100644
--- a/apispec/plugin.py
+++ b/apispec/plugin.py
@@ -12,10 +12,18 @@ class BasePlugin(object):
         :param APISpec spec: APISpec object this plugin instance is attached to
         """
 
-    def definition_helper(self, name, definition, **kwargs):
+    def schema_helper(self, name, definition, **kwargs):
         """Must return definition as a dict."""
         raise PluginMethodNotImplementedError
 
+    def parameter_helper(self, **kwargs):
+        """Must return parameter component description as a dict."""
+        raise PluginMethodNotImplementedError
+
+    def response_helper(self, **kwargs):
+        """Must return response component description as a dict."""
+        raise PluginMethodNotImplementedError
+
     def path_helper(self, path=None, operations=None, **kwargs):
         """May return a path as string and mutate operations dict.
 
diff --git a/apispec/utils.py b/apispec/utils.py
index 3a898ff..413af7c 100644
--- a/apispec/utils.py
+++ b/apispec/utils.py
@@ -113,3 +113,17 @@ def dedent(content):
         content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content)
 
     return content.strip()
+
+
+# http://stackoverflow.com/a/8310229
+def deepupdate(original, update):
+    """Recursively update a dict.
+
+    Subdict's won't be overwritten but also updated.
+    """
+    for key, value in original.items():
+        if key not in update:
+            update[key] = value
+        elif isinstance(value, dict):
+            deepupdate(value, update[key])
+    return update
diff --git a/docs/api_core.rst b/docs/api_core.rst
index fafeb42..0798ecb 100644
--- a/docs/api_core.rst
+++ b/docs/api_core.rst
@@ -7,11 +7,12 @@ apispec
 .. automodule:: apispec
     :members:
 
-apispec.plugin
---------------
 
-.. automodule:: apispec.plugin
-    :members:
+apispec.core
+------------
+
+.. automodule:: apispec.core
+    :members: Components
 
 apispec.exceptions
 ------------------
diff --git a/docs/index.rst b/docs/index.rst
index 7cc7e8a..0797501 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -65,10 +65,10 @@ Example Application
         return jsonify(PetSchema().dump(pet).data)
 
     # Register entities and paths
-    spec.definition('Category', schema=CategorySchema)
-    spec.definition('Pet', schema=PetSchema)
+    spec.components.schema('Category', schema=CategorySchema)
+    spec.components.schema('Pet', schema=PetSchema)
     with app.test_request_context():
-        spec.add_path(view=random_pet)
+        spec.path(view=random_pet)
 
 
 Generated OpenAPI Spec
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index edf90c0..c3816a5 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -19,22 +19,22 @@ First, create an `APISpec <apispec.APISpec>` object, passing basic information a
         )
     )
 
-Add definitions to your spec using `definition <apispec.APISpec.definition>`.
+Add schemas to your spec using `schema <apispec.core.Components.schema>`.
 
 .. code-block:: python
 
-    spec.definition('Gist', properties={
+    spec.components.schema('Gist', properties={
         'id': {'type': 'integer', 'format': 'int64'},
         'name': {'type': 'string'}
     })
 
 
-Add paths to your spec using `add_path <apispec.APISpec.add_path>`.
+Add paths to your spec using `path <apispec.APISpec.path>`.
 
 .. code-block:: python
 
 
-    spec.add_path(
+    spec.path(
         path='/gist/{gist_id}',
         operations=dict(
             get=dict(
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 2f66e45..58fba71 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,4 +1,4 @@
-marshmallow==2.16.3
+marshmallow==2.16.1
 pyyaml==3.13
 sphinx==1.8.1
 sphinx-issues==1.1.0
diff --git a/docs/special_topics.rst b/docs/special_topics.rst
index 69baa72..fef7d36 100644
--- a/docs/special_topics.rst
+++ b/docs/special_topics.rst
@@ -7,7 +7,7 @@ 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 `APISpec.definition <apispec.APISpec.definition>` , 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 the ``extra_fields`` argument.
 
 .. code-block:: python
 
@@ -16,7 +16,7 @@ To add additional fields (e.g. ``"discriminator"``) to Schema objects generated
         'name': {'type': 'string', 'example': 'doggie'},
     }
 
-    spec.definition('Pet', properties=properties, extra_fields={'discriminator': 'petType'})
+    spec.components.schema('Pet', properties=properties, extra_fields={'discriminator': 'petType'})
 
 
 .. note::
@@ -52,8 +52,16 @@ JSON
 Documenting Top-level Components
 --------------------------------
 
-To add objects within the top-level ``components`` object, pass the
-components to the ``APISpec`` as a keyword argument.
+The ``APISpec`` object contains helpers to add top-level components.
+
+To add a schema (a.k.a. "definition" in OpenAPI v2 termionology), use
+`spec.components.schema <apispec.core.Components.schema>`.
+
+Likewise, parameters and responses can be added using
+`spec.components.parameter <apispec.core.Components.parameter>` and
+`spec.components.response <apispec.core.Components.response>`.
+
+To add other top-level objects, pass them to the ``APISpec`` as keyword arguments.
 
 Here is an example that includes a `Security Scheme Object <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#securitySchemeObject>`_.
 
@@ -104,3 +112,9 @@ Here is an example that includes a `Security Scheme Object <https://github.com/O
     )
 
     validate_swagger(spec)
+
+
+When adding components, the main advantage of using dedicated methods over
+passing them as kwargs is the ability to use plugin helpers. For instance,
+`MarshmallowPlugin <apispec.ext.marshmallow.MarshmallowPlugin>` has helpers to
+resolve schemas in parameters and responses.
diff --git a/docs/using_plugins.rst b/docs/using_plugins.rst
index 5439ed2..c86d887 100644
--- a/docs/using_plugins.rst
+++ b/docs/using_plugins.rst
@@ -81,14 +81,14 @@ Our application will have a marshmallow `Schema <marshmallow.Schema>` for gists.
 
 
 The marshmallow plugin allows us to pass this `Schema` to
-`spec.definition <apispec.APISpec.definition>`.
+`spec.components.schema <apispec.core.Components.schema>`.
 
 
 .. code-block:: python
 
-    spec.definition('Gist', schema=GistSchema)
+    spec.components.schema('Gist', schema=GistSchema)
 
-The definition is now added to the spec.
+The schema is now added to the spec.
 
 .. code-block:: python
     :emphasize-lines: 4,5,6,7
@@ -133,15 +133,15 @@ We'll add some YAML in the docstring to add response information.
         """
         return 'details about gist {}'.format(gist_id)
 
-The Flask plugin allows us to pass this view to `spec.add_path <apispec.APISpec.add_path>`.
+The Flask plugin allows us to pass this view to `spec.path <apispec.APISpec.path>`.
 
 
 .. code-block:: python
 
-    # Since add_path inspects the view and its route,
+    # Since path inspects the view and its route,
     # we need to be in a Flask request context
     with app.test_request_context():
-        spec.add_path(view=gist_detail)
+        spec.path(view=gist_detail)
 
 
 Our OpenAPI spec now looks like this:
@@ -190,7 +190,7 @@ If your API uses `method-based dispatching <http://flask.pocoo.org/docs/0.12/vie
     method_view = GistApi.as_view('gist')
     app.add_url_rule("/gist", view_func=method_view)
     with app.test_request_context():
-        spec.add_path(view=method_view)
+        spec.path(view=method_view)
     print(spec.to_dict()['paths'])
     # {'/gist': {'get': {'description': 'get a gist',
     #                    'responses': {200: {'schema': {'$ref': '#/definitions/Gist'}}}},
diff --git a/docs/writing_plugins.rst b/docs/writing_plugins.rst
index 7613c06..2a0b5ae 100644
--- a/docs/writing_plugins.rst
+++ b/docs/writing_plugins.rst
@@ -9,14 +9,15 @@ Helper Methods
 
 Plugins provide "helper" methods that augment the behavior of `apispec.APISpec` methods.
 
-There are four types of helper methods:
+There are five types of helper methods:
 
-* Definition helpers
+* Schema helpers
+* Parameter helpers
+* Response helpers
 * Path helpers
 * Operation helpers
-* Response helpers
 
-Each helper function type modifies a different `apispec.APISpec` method. For example, path helpers modify `apispec.APISpec.add_path`.
+Helper functions modify `apispec.APISpec` methods. For example, path helpers modify `apispec.APISpec.path`.
 
 
 A plugin with a path helper function may look something like this:
@@ -30,7 +31,7 @@ A plugin with a path helper function may look something like this:
 
         def path_helper(self, path, func, **kwargs):
             """Path helper that parses docstrings for operations. Adds a
-            ``func`` parameter to `apispec.APISpec.add_path`.
+            ``func`` parameter to `apispec.APISpec.path`.
             """
             operations = load_operations_from_docstring(func.__doc__)
             return Path(path=path, operations=operations)
@@ -66,7 +67,7 @@ Here's a plugin example involving conditional processing depending on the OpenAP
 
         def operation_helper(self, operations, func, **kwargs):
             """Operation helper that parses docstrings for operations. Adds a
-            ``func`` parameter to `apispec.APISpec.add_path`.
+            ``func`` parameter to `apispec.APISpec.path`.
             """
             doc_operations = load_operations_from_docstring(func.__doc__))
             # Apply conditional processing
@@ -101,7 +102,7 @@ To use the plugin:
         """
         pass
 
-    spec.add_path(path='/gists/{gist_id}', func=gist_detail)
+    spec.path(path='/gists/{gist_id}', func=gist_detail)
     print(spec.to_dict()['paths'])
     # {'/gists/{gist_id}': {'get': {'responses': {200: {'schema': '#/definitions/Gist'}}}}}
 
@@ -111,6 +112,6 @@ Next Steps
 
 To learn more about how to write plugins
 
-* Consult the :doc:`Core API docs <api_core>` for `BasePlugin <apispec.plugin.BasePlugin>`
+* 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>`_.
 * Check out some projects using apispec: https://github.com/marshmallow-code/apispec/wiki/Ecosystem
diff --git a/setup.py b/setup.py
old mode 100644
new mode 100755
index cd92106..24bd1b5
--- a/setup.py
+++ b/setup.py
@@ -21,7 +21,7 @@ EXTRAS_REQUIRE['tests'] = (
     EXTRAS_REQUIRE['yaml'] +
     EXTRAS_REQUIRE['validation'] +
     [
-        'marshmallow==2.16.3',
+        'marshmallow==2.16.1',
         'pytest',
         'mock',
     ]


Test patch:
diff --git a/tests/schemas.py b/tests/schemas.py
index 633e583..0b9f253 100644
--- a/tests/schemas.py
+++ b/tests/schemas.py
@@ -2,9 +2,10 @@ from marshmallow import Schema, fields
 
 
 class PetSchema(Schema):
-    description = dict(id='Pet id', name='Pet name')
+    description = dict(id='Pet id', name='Pet name', password='Password')
     id = fields.Int(dump_only=True, description=description['id'])
-    name = fields.Str(description=description['name'], required=True, deprecated=False, allowEmptyValue=False)
+    name = fields.Str(required=True, deprecated=False, allowEmptyValue=False, description=description['name'])
+    password = fields.Str(load_only=True, description=description['password'])
 
 
 class SampleSchema(Schema):
diff --git a/tests/test_core.py b/tests/test_core.py
index c2f89ff..6650166 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -7,7 +7,7 @@ import yaml
 from apispec import APISpec, BasePlugin
 from apispec.exceptions import APISpecError
 
-from .utils import get_definitions, get_paths
+from .utils import get_definitions, get_paths, get_parameters, get_responses
 
 
 description = 'This is a sample Petstore server.  You can find out more '
@@ -92,7 +92,7 @@ class TestMetadata:
                 'type': 'boolean', 'description': 'property description', 'example': True,
             },
         }
-        spec.definition('definition', properties=properties, description='definiton description')
+        spec.components.schema('definition', properties=properties, description='definiton description')
         metadata = spec.to_dict()
         assert metadata['components']['schemas'].get('ErrorResponse', False)
         assert metadata['components']['schemas'].get('definition', False)
@@ -106,7 +106,7 @@ class TestTags:
     }
 
     def test_tag(self, spec):
-        spec.add_tag(self.tag)
+        spec.tag(self.tag)
         tags_json = spec.to_dict()['tags']
         assert self.tag in tags_json
 
@@ -119,7 +119,7 @@ class TestDefinitions:
     }
 
     def test_definition(self, spec):
-        spec.definition('Pet', properties=self.properties)
+        spec.components.schema('Pet', properties=self.properties)
         if spec.openapi_version.major < 3:
             defs = spec.to_dict()['definitions']
         else:
@@ -129,7 +129,7 @@ class TestDefinitions:
 
     def test_definition_description(self, spec):
         model_description = 'An animal which lives with humans.'
-        spec.definition('Pet', properties=self.properties, description=model_description)
+        spec.components.schema('Pet', properties=self.properties, description=model_description)
         if spec.openapi_version.major < 3:
             defs = spec.to_dict()['definitions']
         else:
@@ -138,7 +138,7 @@ class TestDefinitions:
 
     def test_definition_stores_enum(self, spec):
         enum = ['name', 'photoUrls']
-        spec.definition(
+        spec.components.schema(
             'Pet',
             properties=self.properties,
             enum=enum,
@@ -151,7 +151,7 @@ class TestDefinitions:
 
     def test_definition_extra_fields(self, spec):
         extra_fields = {'discriminator': 'name'}
-        spec.definition('Pet', properties=self.properties, extra_fields=extra_fields)
+        spec.components.schema('Pet', properties=self.properties, extra_fields=extra_fields)
         if spec.openapi_version.major < 3:
             defs = spec.to_dict()['definitions']
         else:
@@ -160,7 +160,7 @@ class TestDefinitions:
 
     def test_to_yaml(self, spec):
         enum = ['name', 'photoUrls']
-        spec.definition(
+        spec.components.schema(
             'Pet',
             properties=self.properties,
             enum=enum,
@@ -208,9 +208,9 @@ class TestPath:
         },
     }
 
-    def test_add_path(self, spec):
+    def test_path(self, spec):
         route_spec = self.paths['/pet/{petId}']['get']
-        spec.add_path(
+        spec.path(
             path='/pet/{petId}',
             operations=dict(
                 get=dict(
@@ -234,30 +234,30 @@ class TestPath:
         assert p['tags'] == route_spec['tags']
 
     def test_paths_maintain_order(self, spec):
-        spec.add_path(path='/path1')
-        spec.add_path(path='/path2')
-        spec.add_path(path='/path3')
-        spec.add_path(path='/path4')
+        spec.path(path='/path1')
+        spec.path(path='/path2')
+        spec.path(path='/path3')
+        spec.path(path='/path4')
         assert list(spec.to_dict()['paths'].keys()) == ['/path1', '/path2', '/path3', '/path4']
 
     def test_methods_maintain_order(self, spec):
         methods = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options']
         for method in methods:
-            spec.add_path(path='/path', operations=OrderedDict({method: {}}))
+            spec.path(path='/path', operations=OrderedDict({method: {}}))
         assert list(spec.to_dict()['paths']['/path']) == methods
 
-    def test_add_path_merges_paths(self, spec):
+    def test_path_merges_paths(self, spec):
         """Test that adding a second HTTP method to an existing path performs
         a merge operation instead of an overwrite"""
         path = '/pet/{petId}'
         route_spec = self.paths[path]['get']
-        spec.add_path(
+        spec.path(
             path=path,
             operations=dict(
                 get=route_spec,
             ),
         )
-        spec.add_path(
+        spec.path(
             path=path,
             operations=dict(
                 put=dict(
@@ -276,9 +276,9 @@ class TestPath:
         assert 'get' in p
         assert 'put' in p
 
-    def test_add_path_ensures_path_parameters_required(self, spec):
+    def test_path_ensures_path_parameters_required(self, spec):
         path = '/pet/{petId}'
-        spec.add_path(
+        spec.path(
             path=path,
             operations=dict(
                 put=dict(
@@ -291,30 +291,26 @@ class TestPath:
         )
         assert get_paths(spec)[path]['put']['parameters'][0]['required'] is True
 
-    def test_add_path_with_no_path_raises_error(self, spec):
+    def test_path_with_no_path_raises_error(self, spec):
         with pytest.raises(APISpecError) as excinfo:
-            spec.add_path()
+            spec.path()
         assert 'Path template is not specified' in str(excinfo)
 
-    def test_add_path_strips_base_path(self, spec):
+    def test_path_strips_base_path(self, spec):
         spec.options['basePath'] = '/v1'
-        spec.add_path('/v1/pets')
+        spec.path('/v1/pets')
         paths = get_paths(spec)
         assert '/pets' in paths
         assert '/v1/pets' not in paths
 
-    def test_add_parameters(self, spec):
+    def test_parameter(self, spec):
         route_spec = self.paths['/pet/{petId}']['get']
 
-        spec.add_parameter('test_parameter', 'path', **route_spec['parameters'][0])
+        spec.components.parameter('test_parameter', 'path', **route_spec['parameters'][0])
 
-        spec.add_path(
+        spec.path(
             path='/pet/{petId}',
-            operations=dict(
-                get=dict(
-                    parameters=['test_parameter'],
-                ),
-            ),
+            operations={'get': {'parameters': ['test_parameter']}},
         )
 
         metadata = spec.to_dict()
@@ -327,20 +323,46 @@ class TestPath:
             assert p['parameters'][0] == {'$ref': '#/components/parameters/test_parameter'}
             assert route_spec['parameters'][0] == metadata['components']['parameters']['test_parameter']
 
-    def test_add_path_check_invalid_http_method(self, spec):
-        spec.add_path('/pet/{petId}', operations={'get': {}})
-        spec.add_path('/pet/{petId}', operations={'x-dummy': {}})
+    def test_response(self, spec):
+        route_spec = self.paths['/pet/{petId}']['get']
+
+        spec.components.response('test_response', **route_spec['responses']['200'])
+
+        spec.path(
+            path='/pet/{petId}',
+            operations={'get': {'responses': {'200': 'test_response'}}},
+        )
+
+        metadata = spec.to_dict()
+        p = get_paths(spec)['/pet/{petId}']['get']
+
+        if spec.openapi_version.major < 3:
+            assert p['responses']['200'] == {'$ref': '#/responses/test_response'}
+            assert route_spec['responses']['200'] == metadata['responses']['test_response']
+        else:
+            assert p['responses']['200'] == {'$ref': '#/components/responses/test_response'}
+            assert route_spec['responses']['200'] == metadata['components']['responses']['test_response']
+
+    def test_path_check_invalid_http_method(self, spec):
+        spec.path('/pet/{petId}', operations={'get': {}})
+        spec.path('/pet/{petId}', operations={'x-dummy': {}})
         with pytest.raises(APISpecError) as excinfo:
-            spec.add_path('/pet/{petId}', operations={'dummy': {}})
+            spec.path('/pet/{petId}', operations={'dummy': {}})
         assert 'One or more HTTP methods are invalid' in str(excinfo)
 
 
 class TestPlugins:
 
     class TestPlugin(BasePlugin):
-        def definition_helper(self, name, definition, **kwargs):
+        def schema_helper(self, name, definition, **kwargs):
             return {'properties': {'name': {'type': 'string'}}}
 
+        def parameter_helper(self, **kwargs):
+            return {'name': 'parameter'}
+
+        def response_helper(self, **kwargs):
+            return {'description': '42'}
+
         def path_helper(self, path, operations, **kwargs):
             if path == '/path_1':
                 operations.update({'get': {'responses': {'200': {}}}})
@@ -351,17 +373,41 @@ class TestPlugins:
                 operations['post'] = {'responses': {'201': {}}}
 
     @pytest.mark.parametrize('openapi_version', ('2.0', '3.0.0', ))
-    def test_plugin_definition_helper_is_used(self, openapi_version):
+    def test_plugin_schema_helper_is_used(self, openapi_version):
         spec = APISpec(
             title='Swagger Petstore',
             version='1.0.0',
             openapi_version=openapi_version,
             plugins=(self.TestPlugin(), ),
         )
-        spec.definition('Pet', {})
+        spec.components.schema('Pet', {})
         definitions = get_definitions(spec)
         assert definitions['Pet'] == {'properties': {'name': {'type': 'string'}}}
 
+    @pytest.mark.parametrize('openapi_version', ('2.0', '3.0.0', ))
+    def test_plugin_parameter_helper_is_used(self, openapi_version):
+        spec = APISpec(
+            title='Swagger Petstore',
+            version='1.0.0',
+            openapi_version=openapi_version,
+            plugins=(self.TestPlugin(), ),
+        )
+        spec.components.parameter('Pet', 'body', **{})
+        parameters = get_parameters(spec)
+        assert parameters['Pet'] == {'in': 'body', 'name': 'parameter'}
+
+    @pytest.mark.parametrize('openapi_version', ('2.0', '3.0.0', ))
+    def test_plugin_response_helper_is_used(self, openapi_version):
+        spec = APISpec(
+            title='Swagger Petstore',
+            version='1.0.0',
+            openapi_version=openapi_version,
+            plugins=(self.TestPlugin(), ),
+        )
+        spec.components.response('Pet', **{})
+        responses = get_responses(spec)
+        assert responses['Pet'] == {'description': '42'}
+
     @pytest.mark.parametrize('openapi_version', ('2.0', '3.0.0', ))
     def test_plugin_path_helper_is_used(self, openapi_version):
         spec = APISpec(
@@ -370,7 +416,7 @@ class TestPlugins:
             openapi_version=openapi_version,
             plugins=(self.TestPlugin(), ),
         )
-        spec.add_path('/path_1')
+        spec.path('/path_1')
         paths = get_paths(spec)
         assert len(paths) == 1
         assert paths['/path_1_modified'] == {'get': {'responses': {'200': {}}}}
@@ -383,7 +429,7 @@ class TestPlugins:
             openapi_version=openapi_version,
             plugins=(self.TestPlugin(), ),
         )
-        spec.add_path('/path_2', operations={'post': {'responses': {'200': {}}}})
+        spec.path('/path_2', operations={'post': {'responses': {'200': {}}}})
         paths = get_paths(spec)
         assert len(paths) == 1
         assert paths['/path_2'] == {'post': {'responses': {'201': {}}}}
@@ -405,7 +451,7 @@ class TestPluginsOrder:
             self.output.append('plugin_{}_operations'.format(self.index))
 
     def test_plugins_order(self):
-        """Test plugins execution order in APISpec.add_path
+        """Test plugins execution order in APISpec.path
 
         - All path helpers are called, then all operation helpers, then all response helpers.
         - At each step, helpers are executed in the order the plugins are passed to APISpec.
@@ -417,7 +463,7 @@ class TestPluginsOrder:
             openapi_version='3.0.0',
             plugins=(self.OrderedPlugin(1, output), self.OrderedPlugin(2, output)),
         )
-        spec.add_path('/path', operations={'get': {'responses': {200: {}}}})
+        spec.path('/path', operations={'get': {'responses': {200: {}}}})
         assert output == [
             'plugin_1_path', 'plugin_2_path',
             'plugin_1_operations', 'plugin_2_operations',
diff --git a/tests/test_ext_marshmallow.py b/tests/test_ext_marshmallow.py
index d1a98ed..48a8a4e 100644
--- a/tests/test_ext_marshmallow.py
+++ b/tests/test_ext_marshmallow.py
@@ -16,7 +16,7 @@ from .schemas import (
     DefaultValuesSchema, AnalysisWithListSchema,
 )
 
-from .utils import get_definitions, get_paths
+from .utils import get_definitions, get_parameters, get_responses, get_paths
 
 
 def ref_path(spec):
@@ -29,7 +29,7 @@ class TestDefinitionHelper:
 
     @pytest.mark.parametrize('schema', [PetSchema, PetSchema()])
     def test_can_use_schema_as_definition(self, spec, schema):
-        spec.definition('Pet', schema=schema)
+        spec.components.schema('Pet', schema=schema)
         definitions = get_definitions(spec)
         assert 'Pet' in definitions
         props = definitions['Pet']['properties']
@@ -51,8 +51,8 @@ class TestDefinitionHelper:
         )
         assert {} == get_definitions(spec)
 
-        spec.definition('analysis', schema=schema)
-        spec.add_path(
+        spec.components.schema('analysis', schema=schema)
+        spec.path(
             '/test', operations={
                 'get': {
                     'responses': {
@@ -86,8 +86,8 @@ class TestDefinitionHelper:
         )
         assert {} == get_definitions(spec)
 
-        spec.definition('analysis', schema=schema)
-        spec.add_path(
+        spec.components.schema('analysis', schema=schema)
+        spec.path(
             '/test', operations={
                 'get': {
                     'responses': {
@@ -122,8 +122,8 @@ class TestDefinitionHelper:
         )
         assert {} == get_definitions(spec)
 
-        spec.definition('analysis', schema=schema)
-        spec.add_path(
+        spec.components.schema('analysis', schema=schema)
+        spec.path(
             '/test', operations={
                 'get': {
                     'responses': {
@@ -148,6 +148,49 @@ class TestDefinitionHelper:
         assert 1 == len(definitions)
 
 
+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}
+        else:
+            kwargs = {'content': {'application/json': {'schema': schema}}}
+        spec.components.parameter('Pet', 'body', **kwargs)
+        parameter = get_parameters(spec)['Pet']
+        assert parameter['in'] == 'body'
+        if spec.openapi_version.major < 3:
+            schema = parameter['schema']['properties']
+        else:
+            schema = parameter['content']['application/json']['schema']['properties']
+
+        assert schema['name']['type'] == 'string'
+        assert schema['password']['type'] == 'string'
+        # dump_only field is ignored
+        assert 'id' not in schema
+
+
+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}
+        else:
+            kwargs = {'content': {'application/json': {'schema': schema}}}
+        spec.components.response('GetPetOk', **kwargs)
+        response = get_responses(spec)['GetPetOk']
+        if spec.openapi_version.major < 3:
+            schema = response['schema']['properties']
+        else:
+            schema = response['content']['application/json']['schema']['properties']
+
+        assert schema['id']['type'] == 'integer'
+        assert schema['name']['type'] == 'string'
+        # load_only field is ignored
+        assert 'password' not in schema
+
+
 class TestCustomField:
 
     def test_can_use_custom_field_decorator(self, spec_fixture):
@@ -171,9 +214,9 @@ class TestCustomField:
         class CustomPetBSchema(PetSchema):
             name = CustomNameB()
 
-        spec_fixture.spec.definition('Pet', schema=PetSchema)
-        spec_fixture.spec.definition('CustomPetA', schema=CustomPetASchema)
-        spec_fixture.spec.definition('CustomPetB', schema=CustomPetBSchema)
+        spec_fixture.spec.components.schema('Pet', schema=PetSchema)
+        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']
@@ -200,7 +243,7 @@ class TestOperationHelper:
     @pytest.mark.parametrize('pet_schema', (PetSchema, PetSchema(), 'tests.schemas.PetSchema'))
     @pytest.mark.parametrize('spec_fixture', ('2.0', ), indirect=True)
     def test_schema_v2(self, spec_fixture, pet_schema):
-        spec_fixture.spec.add_path(
+        spec_fixture.spec.path(
             path='/pet',
             operations={
                 'get': {
@@ -222,7 +265,7 @@ class TestOperationHelper:
     @pytest.mark.parametrize('pet_schema', (PetSchema, PetSchema(), 'tests.schemas.PetSchema'))
     @pytest.mark.parametrize('spec_fixture', ('3.0.0', ), indirect=True)
     def test_schema_v3(self, spec_fixture, pet_schema):
-        spec_fixture.spec.add_path(
+        spec_fixture.spec.path(
             path='/pet',
             operations={
                 'get': {
@@ -248,7 +291,7 @@ class TestOperationHelper:
 
     @pytest.mark.parametrize('spec_fixture', ('2.0', ), indirect=True)
     def test_schema_expand_parameters_v2(self, spec_fixture):
-        spec_fixture.spec.add_path(
+        spec_fixture.spec.path(
             path='/pet',
             operations={
                 'get': {
@@ -281,7 +324,7 @@ class TestOperationHelper:
 
     @pytest.mark.parametrize('spec_fixture', ('3.0.0', ), indirect=True)
     def test_schema_expand_parameters_v3(self, spec_fixture):
-        spec_fixture.spec.add_path(
+        spec_fixture.spec.path(
             path='/pet',
             operations={
                 'get': {
@@ -321,8 +364,8 @@ class TestOperationHelper:
 
     @pytest.mark.parametrize('spec_fixture', ('2.0', ), indirect=True)
     def test_schema_uses_ref_if_available_v2(self, spec_fixture):
-        spec_fixture.spec.definition('Pet', schema=PetSchema)
-        spec_fixture.spec.add_path(
+        spec_fixture.spec.components.schema('Pet', schema=PetSchema)
+        spec_fixture.spec.path(
             path='/pet',
             operations={
                 'get': {
@@ -339,8 +382,8 @@ class TestOperationHelper:
 
     @pytest.mark.parametrize('spec_fixture', ('3.0.0', ), indirect=True)
     def test_schema_uses_ref_if_available_v3(self, spec_fixture):
-        spec_fixture.spec.definition('Pet', schema=PetSchema)
-        spec_fixture.spec.add_path(
+        spec_fixture.spec.components.schema('Pet', schema=PetSchema)
+        spec_fixture.spec.path(
             path='/pet',
             operations={
                 'get': {
@@ -363,8 +406,8 @@ class TestOperationHelper:
 
     @pytest.mark.parametrize('spec_fixture', ('2.0', ), indirect=True)
     def test_schema_uses_ref_in_parameters_and_request_body_if_available_v2(self, spec_fixture):
-        spec_fixture.spec.definition('Pet', schema=PetSchema)
-        spec_fixture.spec.add_path(
+        spec_fixture.spec.components.schema('Pet', schema=PetSchema)
+        spec_fixture.spec.path(
             path='/pet',
             operations={
                 'get': {
@@ -389,8 +432,8 @@ class TestOperationHelper:
 
     @pytest.mark.parametrize('spec_fixture', ('3.0.0', ), indirect=True)
     def test_schema_uses_ref_in_parameters_and_request_body_if_available_v3(self, spec_fixture):
-        spec_fixture.spec.definition('Pet', schema=PetSchema)
-        spec_fixture.spec.add_path(
+        spec_fixture.spec.components.schema('Pet', schema=PetSchema)
+        spec_fixture.spec.path(
             path='/pet',
             operations={
                 'get': {
@@ -418,8 +461,8 @@ class TestOperationHelper:
 
     @pytest.mark.parametrize('spec_fixture', ('2.0', ), indirect=True)
     def test_schema_array_uses_ref_if_available_v2(self, spec_fixture):
-        spec_fixture.spec.definition('Pet', schema=PetSchema)
-        spec_fixture.spec.add_path(
+        spec_fixture.spec.components.schema('Pet', schema=PetSchema)
+        spec_fixture.spec.path(
             path='/pet',
             operations={
                 'get': {
@@ -452,8 +495,8 @@ class TestOperationHelper:
 
     @pytest.mark.parametrize('spec_fixture', ('3.0.0', ), indirect=True)
     def test_schema_array_uses_ref_if_available_v3(self, spec_fixture):
-        spec_fixture.spec.definition('Pet', schema=PetSchema)
-        spec_fixture.spec.add_path(
+        spec_fixture.spec.components.schema('Pet', schema=PetSchema)
+        spec_fixture.spec.path(
             path='/pet',
             operations={
                 'get': {
@@ -497,8 +540,8 @@ class TestOperationHelper:
 
     @pytest.mark.parametrize('spec_fixture', ('2.0', ), indirect=True)
     def test_schema_partially_v2(self, spec_fixture):
-        spec_fixture.spec.definition('Pet', schema=PetSchema)
-        spec_fixture.spec.add_path(
+        spec_fixture.spec.components.schema('Pet', schema=PetSchema)
+        spec_fixture.spec.path(
             path='/parents',
             operations={
                 'get': {
@@ -527,8 +570,8 @@ class TestOperationHelper:
 
     @pytest.mark.parametrize('spec_fixture', ('3.0.0', ), indirect=True)
     def test_schema_partially_v3(self, spec_fixture):
-        spec_fixture.spec.definition('Pet', schema=PetSchema)
-        spec_fixture.spec.add_path(
+        spec_fixture.spec.components.schema('Pet', schema=PetSchema)
+        spec_fixture.spec.path(
             path='/parents',
             operations={
                 'get': {
@@ -574,9 +617,9 @@ class TestOperationHelper:
 class TestCircularReference:
 
     def test_circular_referencing_schemas(self, spec):
-        spec.definition('Analysis', schema=AnalysisSchema)
-        spec.definition('Sample', schema=SampleSchema)
-        spec.definition('Run', schema=RunSchema)
+        spec.components.schema('Analysis', schema=AnalysisSchema)
+        spec.components.schema('Sample', schema=SampleSchema)
+        spec.components.schema('Run', schema=RunSchema)
         definitions = get_definitions(spec)
         ref = definitions['Analysis']['properties']['sample']['$ref']
         assert ref == ref_path(spec) + 'Sample'
@@ -585,13 +628,13 @@ class TestCircularReference:
 class TestSelfReference:
 
     def test_self_referencing_field_single(self, spec):
-        spec.definition('SelfReference', schema=SelfReferencingSchema)
+        spec.components.schema('SelfReference', schema=SelfReferencingSchema)
         definitions = get_definitions(spec)
         ref = definitions['SelfReference']['properties']['single']['$ref']
         assert ref == ref_path(spec) + 'SelfReference'
 
     def test_self_referencing_field_many(self, spec):
-        spec.definition('SelfReference', schema=SelfReferencingSchema)
+        spec.components.schema('SelfReference', schema=SelfReferencingSchema)
         definitions = get_definitions(spec)
         result = definitions['SelfReference']['properties']['many']
         assert result == {
@@ -601,7 +644,7 @@ class TestSelfReference:
 
     def test_self_referencing_with_ref(self, spec):
         version = 'v2' if spec.openapi_version.version[0] < 3 else 'v3'
-        spec.definition('SelfReference', schema=SelfReferencingSchema)
+        spec.components.schema('SelfReference', schema=SelfReferencingSchema)
         definitions = get_definitions(spec)
         result = definitions['SelfReference']['properties'][
             'single_with_ref_{}'.format(version)
@@ -616,20 +659,20 @@ class TestSelfReference:
 class TestOrderedSchema:
 
     def test_ordered_schema(self, spec):
-        spec.definition('Ordered', schema=OrderedSchema)
+        spec.components.schema('Ordered', schema=OrderedSchema)
         result = get_definitions(spec)['Ordered']['properties']
         assert list(result.keys()) == ['field1', 'field2', 'field3', 'field4', 'field5']
 
 
 class TestFieldWithCustomProps:
     def test_field_with_custom_props(self, spec):
-        spec.definition('PatternedObject', schema=PatternedObjectSchema)
+        spec.components.schema('PatternedObject', schema=PatternedObjectSchema)
         result = get_definitions(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.definition('PatternedObject', schema=PatternedObjectSchema)
+        spec.components.schema('PatternedObject', schema=PatternedObjectSchema)
         result = get_definitions(spec)['PatternedObject']['properties']['count2']
         assert 'x-count2' in result
         assert result['x-count2'] == 2
@@ -637,7 +680,7 @@ class TestFieldWithCustomProps:
 
 class TestSchemaWithDefaultValues:
     def test_schema_with_default_values(self, spec):
-        spec.definition('DefaultValuesSchema', schema=DefaultValuesSchema)
+        spec.components.schema('DefaultValuesSchema', schema=DefaultValuesSchema)
         definitions = get_definitions(spec)
         props = definitions['DefaultValuesSchema']['properties']
         assert props['number_auto_default']['default'] == 12
@@ -657,7 +700,7 @@ class TestDictValues:
         class SchemaWithDict(Schema):
             dict_field = Dict(values=String())
 
-        spec.definition('SchemaWithDict', schema=SchemaWithDict)
+        spec.components.schema('SchemaWithDict', schema=SchemaWithDict)
         result = get_definitions(spec)['SchemaWithDict']['properties']['dict_field']
         assert result == {'type': 'object', 'additionalProperties': {'type': 'string'}}
 
@@ -666,6 +709,6 @@ class TestDictValues:
         class SchemaWithDict(Schema):
             dict_field = Dict()
 
-        spec.definition('SchemaWithDict', schema=SchemaWithDict)
+        spec.components.schema('SchemaWithDict', schema=SchemaWithDict)
         result = get_definitions(spec)['SchemaWithDict']['properties']['dict_field']
         assert result == {'type': 'object'}
diff --git a/tests/test_openapi.py b/tests/test_openapi.py
index 4b40972..cb05d68 100644
--- a/tests/test_openapi.py
+++ b/tests/test_openapi.py
@@ -614,7 +614,7 @@ class TestNesting:
         return '#/components/schemas/'
 
     def test_field2property_nested_spec_metadatas(self, spec_fixture):
-        spec_fixture.spec.definition('Category', schema=CategorySchema)
+        spec_fixture.spec.components.schema('Category', schema=CategorySchema)
         category = fields.Nested(CategorySchema, description='A category')
         result = spec_fixture.openapi.field2property(category)
         assert result == {
@@ -623,14 +623,14 @@ class TestNesting:
         }
 
     def test_field2property_nested_spec(self, spec_fixture):
-        spec_fixture.spec.definition('Category', schema=CategorySchema)
+        spec_fixture.spec.components.schema('Category', schema=CategorySchema)
         category = fields.Nested(CategorySchema)
         assert spec_fixture.openapi.field2property(category) == {
             '$ref': self.ref_path(spec_fixture.spec) + 'Category',
         }
 
     def test_field2property_nested_many_spec(self, spec_fixture):
-        spec_fixture.spec.definition('Category', schema=CategorySchema)
+        spec_fixture.spec.components.schema('Category', schema=CategorySchema)
         category = fields.Nested(CategorySchema, many=True)
         ret = spec_fixture.openapi.field2property(category)
         assert ret['type'] == 'array'
@@ -747,7 +747,7 @@ class TestNesting:
         category_6 = fields.Nested(CategorySchema, many=True, ref=ref_path + 'Category')
         category_7 = fields.Nested(CategorySchema, many=True, dump_only=True)
         category_8 = fields.Nested(CategorySchema, many=True, dump_only=True, ref=ref_path + 'Category')
-        spec_fixture.spec.definition('Category', schema=CategorySchema)
+        spec_fixture.spec.components.schema('Category', schema=CategorySchema)
 
         assert spec_fixture.openapi.field2property(category_1) == {
             '$ref': ref_path + 'Category',
@@ -784,10 +784,10 @@ def test_openapi_tools_validate_v2():
     )
     openapi = ma_plugin.openapi
 
-    spec.definition('Category', schema=CategorySchema)
-    spec.definition('Pet', schema=PetSchema, extra_fields={'discriminator': 'name'})
+    spec.components.schema('Category', schema=CategorySchema)
+    spec.components.schema('Pet', schema=PetSchema, extra_fields={'discriminator': 'name'})
 
-    spec.add_path(
+    spec.path(
         view=None,
         path='/category/{category_id}',
         operations={
@@ -841,10 +841,10 @@ def test_openapi_tools_validate_v3():
     )
     #openapi = ma_plugin.openapi
 
-    spec.definition('Category', schema=CategorySchema)
-    spec.definition('Pet', schema=PetSchemaV3)
+    spec.components.schema('Category', schema=CategorySchema)
+    spec.components.schema('Pet', schema=PetSchemaV3)
 
-    spec.add_path(
+    spec.path(
         view=None,
         path='/category/{category_id}',
         operations={
@@ -934,7 +934,7 @@ class ValidationSchema(Schema):
 class TestFieldValidation:
 
     def test_range(self, spec):
-        spec.definition('Validation', schema=ValidationSchema)
+        spec.components.schema('Validation', schema=ValidationSchema)
         result = get_definitions(spec)['Validation']['properties']['range']
 
         assert 'minimum' in result
@@ -943,7 +943,7 @@ class TestFieldValidation:
         assert result['maximum'] == 10
 
     def test_multiple_ranges(self, spec):
-        spec.definition('Validation', schema=ValidationSchema)
+        spec.components.schema('Validation', schema=ValidationSchema)
         result = get_definitions(spec)['Validation']['properties']['multiple_ranges']
 
         assert 'minimum' in result
@@ -952,7 +952,7 @@ class TestFieldValidation:
         assert result['maximum'] == 7
 
     def test_list_length(self, spec):
-        spec.definition('Validation', schema=ValidationSchema)
+        spec.components.schema('Validation', schema=ValidationSchema)
         result = get_definitions(spec)['Validation']['properties']['list_length']
 
         assert 'minItems' in result
@@ -961,7 +961,7 @@ class TestFieldValidation:
         assert result['maxItems'] == 10
 
     def test_string_length(self, spec):
-        spec.definition('Validation', schema=ValidationSchema)
+        spec.components.schema('Validation', schema=ValidationSchema)
         result = get_definitions(spec)['Validation']['properties']['string_length']
 
         assert 'minLength' in result
@@ -970,7 +970,7 @@ class TestFieldValidation:
         assert result['maxLength'] == 10
 
     def test_multiple_lengths(self, spec):
-        spec.definition('Validation', schema=ValidationSchema)
+        spec.components.schema('Validation', schema=ValidationSchema)
         result = get_definitions(spec)['Validation']['properties']['multiple_lengths']
 
         assert 'minLength' in result
@@ -979,7 +979,7 @@ class TestFieldValidation:
         assert result['maxLength'] == 7
 
     def test_equal_length(self, spec):
-        spec.definition('Validation', schema=ValidationSchema)
+        spec.components.schema('Validation', schema=ValidationSchema)
         result = get_definitions(spec)['Validation']['properties']['equal_length']
 
         assert 'minLength' in result
diff --git a/tests/utils.py b/tests/utils.py
index b969a41..70350ff 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -6,5 +6,15 @@ def get_definitions(spec):
         return spec.to_dict()['definitions']
     return spec.to_dict()['components']['schemas']
 
+def get_parameters(spec):
+    if spec.openapi_version.major < 3:
+        return spec.to_dict()['parameters']
+    return spec.to_dict()['components']['parameters']
+
+def get_responses(spec):
+    if spec.openapi_version.major < 3:
+        return spec.to_dict()['responses']
+    return spec.to_dict()['components']['responses']
+
 def get_paths(spec):
     return spec.to_dict()['paths']