Repository: marshmallow-code/apispec
Pull request #461 (2019-06-20 05:37:23)

Bump sphinx from 2.1.1 to 2.1.2
Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 2.1.1 to 2.1.2.
<details>
<summary>Changelog</summary>

*Sourced from [sphinx's changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES).*

> Release 2.1.2 (in development)
> ==============================
> 
> Dependencies
> ------------
> 
> Incompatible changes
> --------------------
> 
> Deprecated
> ----------
> 
> Features added
> --------------
> 
> Bugs fixed
> ----------
> 
> Testing
> --------
</details>
<details>
<summary>Commits</summary>

- [`39563d1`](https://github.com/sphinx-doc/sphinx/commit/39563d1b294ef5cce7102ad82177e99f646ba042) Bump to 2.1.2 final
- [`131c93c`](https://github.com/sphinx-doc/sphinx/commit/131c93caf59a4737f31b6e187e17a3366c28109d) Remove "universal" configuration from setup.cfg
- [`2709cd3`](https://github.com/sphinx-doc/sphinx/commit/2709cd3cd18394b0771de0539caaffabe8f7d502) Update CHANGES for PR [#6482](https://github-redirect.dependabot.com/sphinx-doc/sphinx/issues/6482)
- [`982f63e`](https://github.com/sphinx-doc/sphinx/commit/982f63e4a22aee01cca73609dcaf13ac32eb60d2) Merge pull request [#6482](https://github-redirect.dependabot.com/sphinx-doc/sphinx/issues/6482) from ViktorHaag/object-descr-setting-class-type-map-...
- [`e9ee39d`](https://github.com/sphinx-doc/sphinx/commit/e9ee39d655be2c1aff29aa3716f9f3dfbcd82006) repair white space
- [`6a05435`](https://github.com/sphinx-doc/sphinx/commit/6a054355d92628fbfa4670f097b5ce20db6fdc2e) Simplify to ensure we're lazily setting the instance field-type-map variable
- [`be08561`](https://github.com/sphinx-doc/sphinx/commit/be0856190fe076d6ea012f70cbb78fd040f7f986) Merge pull request [#6502](https://github-redirect.dependabot.com/sphinx-doc/sphinx/issues/6502) from tk0miya/6497_custom_lexers_fails_highlighting
- [`2119a65`](https://github.com/sphinx-doc/sphinx/commit/2119a65b860ea6619efe0881118cc3c693558ac5) Fix [#6497](https://github-redirect.dependabot.com/sphinx-doc/sphinx/issues/6497): custom lexers fails highlighting when syntax error
- [`d19dd1c`](https://github.com/sphinx-doc/sphinx/commit/d19dd1c8dec8cc80229e8314cdbc37650b366eaf) mypy type annotations for methods added
- [`085ae2e`](https://github.com/sphinx-doc/sphinx/commit/085ae2e08187acc5dfdff7e79364e5962afc121c) type declaration for new internal method
- Additional commits viewable in [compare view](https://github.com/sphinx-doc/sphinx/compare/v2.1.1...v2.1.2)
</details>
<br />

[![Dependabot compatibility score](https://api.dependabot.com/badges/compatibility_score?dependency-name=sphinx&package-manager=pip&previous-version=2.1.1&new-version=2.1.2)](https://dependabot.com/compatibility-score.html?dependency-name=sphinx&package-manager=pip&previous-version=2.1.1&new-version=2.1.2)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
If all status checks pass Dependabot will automatically merge this pull request.

[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot ignore this [patch|minor|major] version` will close this PR and stop Dependabot creating any more for this minor/major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language
- `@dependabot badge me` will comment on this PR with code to add a "Dependabot enabled" badge to your readme

Additionally, you can set the following in your Dependabot [dashboard](https://app.dependabot.com):
- Update frequency (including time of day and day of week)
- Automerge options (never/patch/minor, and dev/runtime dependencies)
- Pull request limits (per update run and/or open at any time)
- Out-of-range updates (receive only lockfile updates, if desired)
- Security updates (receive only security updates, if desired)

Finally, you can contact us by mentioning @dependabot.

</details>


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index a784859..36186c4 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -19,6 +19,8 @@ Other changes:
 
 - *Backwards-incompatible*: All plugin helpers must accept extra `**kwargs`
   (:issue:`453`).
+- *Backwards-incompatible*: Components must be referenced by ID, not full path
+  (:issue:`463`).
 
 1.3.3 (2019-05-05)
 ++++++++++++++++++
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index e6669bf..d5ef7db 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -41,13 +41,7 @@ Add paths to your spec using `path <apispec.APISpec.path>`.
         path="/gist/{gist_id}",
         operations=dict(
             get=dict(
-                responses={
-                    "200": {
-                        "content": {
-                            "application/json": {"schema": {"$ref": "#/definitions/Gist"}}
-                        }
-                    }
-                }
+                responses={"200": {"content": {"application/json": {"schema": "Gist"}}}}
             )
         ),
     )
diff --git a/docs/requirements.txt b/docs/requirements.txt
index b1fcee1..43070eb 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,5 +1,5 @@
 marshmallow==2.19.5
 pyyaml==5.1.1
-sphinx==2.1.1
+sphinx==2.1.2
 sphinx-issues==1.2.0
 sphinx-rtd-theme==0.4.3
diff --git a/docs/upgrading.rst b/docs/upgrading.rst
index b78abff..6253140 100644
--- a/docs/upgrading.rst
+++ b/docs/upgrading.rst
@@ -3,6 +3,69 @@ Upgrading to Newer Releases
 
 This section documents migration paths to new releases.
 
+Upgrading to 2.0.0
+------------------
+
+plugin helpers must accept extra `**kwargs`
+*******************************************
+
+Since custom plugins helpers may define extra kwargs and those kwargs are passed
+to all plugin helpers by :meth:`APISpec.path <APISpec.path>`, all plugins should
+accept unknown kwargs.
+
+The example plugin below defines an additional `func` argument and accepts extra
+`**kwargs`.
+
+.. code-block:: python
+    :emphasize-lines: 2
+
+    class MyPlugin(BasePlugin):
+        def path_helper(self, path, func, **kwargs):
+            """Path helper that parses docstrings for operations. Adds a
+            ``func`` parameter to `apispec.APISpec.path`.
+            """
+            operations = load_operations_from_docstring(func.__doc__)
+            return Path(path=path, operations=operations)
+
+Components must be referenced by ID, not full path
+**************************************************
+
+While apispec 1.x would let the user reference components by path or ID,
+apispec 2.x only accepts references by ID.
+
+.. code-block:: python
+
+    # apispec<2.0.0
+    spec.path(
+        path="/gist/{gist_id}",
+        operations=dict(
+            get=dict(
+                responses={
+                    "200": {
+                        "content": {
+                            "application/json": {"schema": {"$ref": "#/definitions/Gist"}}
+                        }
+                    }
+                }
+            )
+        ),
+    )
+
+    # apispec>=2.0.0
+    spec.path(
+        path="/gist/{gist_id}",
+        operations=dict(
+            get=dict(
+                responses={"200": {"content": {"application/json": {"schema": "Gist"}}}}
+            )
+        ),
+    )
+
+References by ID are accepted by both apispec 1.x ad 2.x and are a better
+choice because they delegate the creation of the full component path to apispec.
+This allows more flexibility as apispec creates the component path according to
+the OpenAPI version.
+
 Upgrading to 1.0.0
 ------------------
 
diff --git a/src/apispec/core.py b/src/apispec/core.py
index 0ba1f5e..d6c5d70 100644
--- a/src/apispec/core.py
+++ b/src/apispec/core.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 """Core apispec classes and functions."""
 from collections import OrderedDict
+from copy import deepcopy
 import warnings
 
 from apispec.compat import iterkeys, iteritems
@@ -334,8 +335,10 @@ class APISpec(object):
         :param list|None parameters: list of parameters relevant to all operations in this path
         :param dict kwargs: parameters used by any path helpers see :meth:`register_path_helper`
         """
-        operations = operations or OrderedDict()
-        parameters = parameters or []
+        # operations and parameters must be deepcopied because they are mutated
+        # in clean_operations and operation helpers and path may be called twice
+        operations = deepcopy(operations) or OrderedDict()
+        parameters = deepcopy(parameters) or []
 
         # Execute path helpers
         for plugin in self.plugins:


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 5ad5dbf..8fb7d19 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -269,10 +269,7 @@ class TestPath:
                     }
                 ],
                 "responses": {
-                    "200": {
-                        "schema": {"$ref": "#/definitions/Pet"},
-                        "description": "successful operation",
-                    },
+                    "200": {"schema": "Pet", "description": "successful operation"},
                     "400": {"description": "Invalid ID supplied"},
                     "404": {"description": "Pet not found"},
                 },
@@ -360,6 +357,51 @@ class TestPath:
         assert "get" in p
         assert "put" in p
 
+    @pytest.mark.parametrize("openapi_version", ("2.0", "3.0.0"))
+    def test_path_called_twice_with_same_operations_parameters(self, openapi_version):
+        """Test calling path twice with same operations or parameters
+
+        operations and parameters being mutated by clean_operations and plugin helpers
+        should not make path fail on second call
+        """
+
+        class TestPlugin(BasePlugin):
+            def path_helper(self, path, operations, parameters, **kwargs):
+                """Mutate operations and parameters"""
+                operations.update({"post": {"responses": {"201": "201ResponseRef"}}})
+                parameters.append("ParamRef_3")
+                return path
+
+        spec = APISpec(
+            title="Swagger Petstore",
+            version="1.0.0",
+            openapi_version=openapi_version,
+            plugins=[TestPlugin()],
+        )
+
+        path = "/pet/{petId}"
+        parameters = ["ParamRef_1"]
+        operation = {
+            "parameters": ["ParamRef_2"],
+            "responses": {"200": "200ResponseRef"},
+        }
+
+        spec.path(path=path, operations={"get": operation}, parameters=parameters)
+        spec.path(path=path, operations={"put": operation}, parameters=parameters)
+        operations = (get_paths(spec))[path]
+        assert (
+            operations["get"]
+            == operations["put"]
+            == {
+                "parameters": [build_ref(spec, "parameter", "ParamRef_2")],
+                "responses": {"200": build_ref(spec, "response", "200ResponseRef")},
+            }
+        )
+        assert operations["parameters"] == [
+            build_ref(spec, "parameter", "ParamRef_1"),
+            build_ref(spec, "parameter", "ParamRef_3"),
+        ]
+
     def test_path_ensures_path_parameters_required(self, spec):
         path = "/pet/{petId}"
         spec.path(