Codebase list apispec / 833183f
Merge new upstream release 4.3.0+git20210322.1f66eef. Kali Janitor 3 years ago
39 changed file(s) with 1572 addition(s) and 676 deletion(s). Raw diff Collapse all Expand all
+0
-2
.github/FUNDING.yml less more
0 open_collective: "marshmallow"
1 tidelift: "pypi/apispec"
+0
-105
.gitignore less more
0 # Byte-compiled / optimized / DLL files
1 __pycache__/
2 *.py[cod]
3 *$py.class
4
5 # C extensions
6 *.so
7
8 # Distribution / packaging
9 .Python
10 build/
11 develop-eggs/
12 dist/
13 downloads/
14 eggs/
15 .eggs/
16 lib/
17 lib64/
18 parts/
19 sdist/
20 var/
21 wheels/
22 *.egg-info/
23 .installed.cfg
24 *.egg
25 MANIFEST
26
27 # PyInstaller
28 # Usually these files are written by a python script from a template
29 # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 *.manifest
31 *.spec
32
33 # Installer logs
34 pip-log.txt
35 pip-delete-this-directory.txt
36
37 # Unit test / coverage reports
38 htmlcov/
39 .tox/
40 .coverage
41 .coverage.*
42 .cache
43 nosetests.xml
44 coverage.xml
45 *.cover
46 .hypothesis/
47 .pytest_cache/
48
49 # Translations
50 *.mo
51 *.pot
52
53 # Django stuff:
54 *.log
55 local_settings.py
56 db.sqlite3
57
58 # Flask stuff:
59 instance/
60 .webassets-cache
61
62 # Scrapy stuff:
63 .scrapy
64
65 # Sphinx documentation
66 docs/_build/
67 README.html
68
69 # PyBuilder
70 target/
71
72 # Jupyter Notebook
73 .ipynb_checkpoints
74
75 # pyenv
76 .python-version
77
78 # celery beat schedule file
79 celerybeat-schedule
80
81 # SageMath parsed files
82 *.sage.py
83
84 # Environments
85 .env
86 .venv
87 env/
88 venv/
89 ENV/
90 env.bak/
91 venv.bak/
92
93 # Spyder project settings
94 .spyderproject
95 .spyproject
96
97 # Rope project settings
98 .ropeproject
99
100 # mkdocs documentation
101 /site
102
103 # mypy
104 .mypy_cache/
+0
-21
.pre-commit-config.yaml less more
0 repos:
1 - repo: https://github.com/asottile/pyupgrade
2 rev: v2.4.1
3 hooks:
4 - id: pyupgrade
5 args: [--py3-plus]
6 - repo: https://github.com/python/black
7 rev: 19.10b0
8 hooks:
9 - id: black
10 language_version: python3
11 - repo: https://gitlab.com/pycqa/flake8
12 rev: 3.8.1
13 hooks:
14 - id: flake8
15 additional_dependencies: [flake8-bugbear==20.1.4]
16 - repo: https://github.com/asottile/blacken-docs
17 rev: v1.7.0
18 hooks:
19 - id: blacken-docs
20 additional_dependencies: [black==19.10b0]
6262 - Ashutosh Chaudhary `@codeasashu <https://github.com/codeasashu>`_
6363 - Fedor Fominykh `@fedorfo <https://github.com/fedorfo>`_
6464 - Colin Bounouar `@Colin-b <https://github.com/Colin-b>`_
65 - Mikko Kortelainen `@kortsi <https://github.com/kortsi>
66 - David Bishop `@teancom <https://github.com/teancom>`_
67 - Andrea Ghensi `@sanzoghenzo <https://github.com/sanzoghenzo>`_
68 - `@timsilvers <https://github.com/timsilvers>`_
00 Changelog
11 ---------
2
3 4.4.0 (unreleased)
4 ******************
5
6 Features:
7
8 - Populate ``additionalProperties`` from ``Meta.unknown`` (:pr:`635`).
9 Thanks :user:`timsilvers` for the PR.
10
11 4.3.0 (2021-02-10)
12 ******************
13
14 Features:
15
16 - Add `apispec.core.Components.header` to register header components
17 (:pr:`637`).
18
19 4.2.0 (2021-02-06)
20 ******************
21
22 Features:
23
24 - Make components public attributes of ``Components`` class (:pr:`634`).
25
26 4.1.0 (2021-01-26)
27 ******************
28
29 Features:
30
31 - Resolve schemas in callbacks (:pr:`544`). Thanks :user:`kortsi` for the PR.
32
33 Bug fixes:
34
35 - Fix docstrings documenting kwargs type as dict (:issue:`534`).
36 - Use ``x-minimum`` and ``x-maximum`` extensions to document ranges that are
37 not of number type (e.g. datetime) (:issue:`614`).
38
39 Other changes:
40
41 - Test against Python 3.9.
42
43 4.0.0 (2020-09-30)
44 ******************
45
46 Features:
47
48 - *Backwards-incompatible*: Automatically generate references for schemas
49 passed as strings in responses and request bodies. When using
50 ``MarshmallowPlugin``, if a schema is passed as string, the marshmallow
51 registry is looked up for this schema name and if none is found, the name is
52 assumed to be a reference to a manually created schema and a reference is
53 generated. No exception is raised anymore if the schema name can't be found
54 in the registry. (:pr:`554`)
55
56 4.0.0b1 (2020-09-06)
57 ********************
58
59 Features:
60
61 - *Backwards-incompatible*: Ignore ``location`` field metadata. This attribute
62 was used in webargs but it has now been dropped. A ``Schema`` can now only
63 have a single location. This simplifies the logic in ``OpenAPIConverter``
64 methods, where ``default_in`` argument now becomes ``location``. (:pr:`526`)
65 - *Backwards-incompatible*: Don't document ``int`` format as ``"int32"`` and
66 ``float`` format as ``"float"``, as those are platform-dependent (:pr:`595`).
67
68 Refactoring:
69
70 - ``OpenAPIConverter.field2parameters`` and
71 ``OpenAPIConverter.property2parameter`` are removed.
72 ``OpenAPIConverter.field2parameter`` becomes private. (:pr:`581`)
73
74 Other changes:
75
76 - Drop support for marshmallow 2. Marshmallow 3.x is required. (:pr:`583`)
77 - Drop support for Python 3.5. Python 3.6+ is required. (:pr:`582`)
78
79
80 3.3.2 (2020-08-29)
81 ******************
82
83 Bug fixes:
84
85 - Fix crash when field metadata contains non-string keys (:pr:`596`).
86 Thanks :user:`sanzoghenzo` for the fix.
287
388 3.3.1 (2020-06-06)
489 ******************
893 - Fix ``MarshmallowPlugin`` crash when ``resolve_schema_dict`` is passed a
994 schema as string and ``schema_name_resolver`` returns ``None``
1095 (:issue:`566`). Thanks :user:`black3r` for reporting and thanks
11 :user:`Bangterm` for the PR.
96 :user:`Bangertm` for the PR.
1297
1398 3.3.0 (2020-02-14)
1499 ******************
0 Metadata-Version: 2.1
1 Name: apispec
2 Version: 4.3.0
3 Summary: A pluggable API specification generator. Currently supports the OpenAPI Specification (f.k.a. the Swagger specification).
4 Home-page: https://github.com/marshmallow-code/apispec
5 Author: Steven Loria
6 Author-email: [email protected]
7 License: MIT
8 Project-URL: Funding, https://opencollective.com/marshmallow
9 Project-URL: Issues, https://github.com/marshmallow-code/apispec/issues
10 Project-URL: Tidelift, https://tidelift.com/subscription/pkg/pypi-apispec?utm_source=pypi-apispec&utm_medium=pypi
11 Description: *******
12 apispec
13 *******
14
15 .. image:: https://badgen.net/pypi/v/apispec
16 :target: https://pypi.org/project/apispec/
17 :alt: PyPI version
18
19 .. image:: https://dev.azure.com/sloria/sloria/_apis/build/status/marshmallow-code.apispec?branchName=dev
20 :target: https://dev.azure.com/sloria/sloria/_build/latest?definitionId=8&branchName=dev
21 :alt: Build status
22
23 .. image:: https://readthedocs.org/projects/apispec/badge/
24 :target: https://apispec.readthedocs.io/
25 :alt: Documentation
26
27 .. image:: https://badgen.net/badge/marshmallow/3?list=1
28 :target: https://marshmallow.readthedocs.io/en/latest/upgrading.html
29 :alt: marshmallow 3 only
30
31 .. image:: https://badgen.net/badge/OAS/2,3?list=1&color=cyan
32 :target: https://github.com/OAI/OpenAPI-Specification
33 :alt: OpenAPI Specification 2/3 compatible
34
35 .. image:: https://badgen.net/badge/code%20style/black/000
36 :target: https://github.com/ambv/black
37 :alt: code style: black
38
39 A pluggable API specification generator. Currently supports the `OpenAPI Specification <https://github.com/OAI/OpenAPI-Specification>`_ (f.k.a. the Swagger specification).
40
41 Features
42 ========
43
44 - Supports the OpenAPI Specification (versions 2 and 3)
45 - Framework-agnostic
46 - Built-in support for `marshmallow <https://marshmallow.readthedocs.io/>`_
47 - Utilities for parsing docstrings
48
49 Example Application
50 ===================
51
52 .. code-block:: python
53
54 from apispec import APISpec
55 from apispec.ext.marshmallow import MarshmallowPlugin
56 from apispec_webframeworks.flask import FlaskPlugin
57 from flask import Flask
58 from marshmallow import Schema, fields
59
60
61 # Create an APISpec
62 spec = APISpec(
63 title="Swagger Petstore",
64 version="1.0.0",
65 openapi_version="3.0.2",
66 plugins=[FlaskPlugin(), MarshmallowPlugin()],
67 )
68
69 # Optional marshmallow support
70 class CategorySchema(Schema):
71 id = fields.Int()
72 name = fields.Str(required=True)
73
74
75 class PetSchema(Schema):
76 category = fields.List(fields.Nested(CategorySchema))
77 name = fields.Str()
78
79
80 # Optional security scheme support
81 api_key_scheme = {"type": "apiKey", "in": "header", "name": "X-API-Key"}
82 spec.components.security_scheme("ApiKeyAuth", api_key_scheme)
83
84
85 # Optional Flask support
86 app = Flask(__name__)
87
88
89 @app.route("/random")
90 def random_pet():
91 """A cute furry animal endpoint.
92 ---
93 get:
94 description: Get a random pet
95 security:
96 - ApiKeyAuth: []
97 responses:
98 200:
99 content:
100 application/json:
101 schema: PetSchema
102 """
103 pet = get_random_pet()
104 return PetSchema().dump(pet)
105
106
107 # Register the path and the entities within it
108 with app.test_request_context():
109 spec.path(view=random_pet)
110
111
112 Generated OpenAPI Spec
113 ----------------------
114
115 .. code-block:: python
116
117 import json
118
119 print(json.dumps(spec.to_dict(), indent=2))
120 # {
121 # "paths": {
122 # "/random": {
123 # "get": {
124 # "description": "Get a random pet",
125 # "security": [
126 # {
127 # "ApiKeyAuth": []
128 # }
129 # ],
130 # "responses": {
131 # "200": {
132 # "content": {
133 # "application/json": {
134 # "schema": {
135 # "$ref": "#/components/schemas/Pet"
136 # }
137 # }
138 # }
139 # }
140 # }
141 # }
142 # }
143 # },
144 # "tags": [],
145 # "info": {
146 # "title": "Swagger Petstore",
147 # "version": "1.0.0"
148 # },
149 # "openapi": "3.0.2",
150 # "components": {
151 # "parameters": {},
152 # "responses": {},
153 # "schemas": {
154 # "Category": {
155 # "type": "object",
156 # "properties": {
157 # "name": {
158 # "type": "string"
159 # },
160 # "id": {
161 # "type": "integer",
162 # "format": "int32"
163 # }
164 # },
165 # "required": [
166 # "name"
167 # ]
168 # },
169 # "Pet": {
170 # "type": "object",
171 # "properties": {
172 # "name": {
173 # "type": "string"
174 # },
175 # "category": {
176 # "type": "array",
177 # "items": {
178 # "$ref": "#/components/schemas/Category"
179 # }
180 # }
181 # }
182 # }
183 # "securitySchemes": {
184 # "ApiKeyAuth": {
185 # "type": "apiKey",
186 # "in": "header",
187 # "name": "X-API-Key"
188 # }
189 # }
190 # }
191 # }
192 # }
193
194 print(spec.to_yaml())
195 # components:
196 # parameters: {}
197 # responses: {}
198 # schemas:
199 # Category:
200 # properties:
201 # id: {format: int32, type: integer}
202 # name: {type: string}
203 # required: [name]
204 # type: object
205 # Pet:
206 # properties:
207 # category:
208 # items: {$ref: '#/components/schemas/Category'}
209 # type: array
210 # name: {type: string}
211 # type: object
212 # securitySchemes:
213 # ApiKeyAuth:
214 # in: header
215 # name: X-API-KEY
216 # type: apiKey
217 # info: {title: Swagger Petstore, version: 1.0.0}
218 # openapi: 3.0.2
219 # paths:
220 # /random:
221 # get:
222 # description: Get a random pet
223 # responses:
224 # 200:
225 # content:
226 # application/json:
227 # schema: {$ref: '#/components/schemas/Pet'}
228 # security:
229 # - ApiKeyAuth: []
230 # tags: []
231
232
233 Documentation
234 =============
235
236 Documentation is available at https://apispec.readthedocs.io/ .
237
238 Ecosystem
239 =========
240
241 A list of apispec-related libraries can be found at the GitHub wiki here:
242
243 https://github.com/marshmallow-code/apispec/wiki/Ecosystem
244
245 Support apispec
246 ===============
247
248 apispec is maintained by a group of
249 `volunteers <https://apispec.readthedocs.io/en/latest/authors.html>`_.
250 If you'd like to support the future of the project, please consider
251 contributing to our Open Collective:
252
253 .. image:: https://opencollective.com/marshmallow/donate/button.png
254 :target: https://opencollective.com/marshmallow
255 :width: 200
256 :alt: Donate to our collective
257
258 Professional Support
259 ====================
260
261 Professionally-supported apispec is available through the
262 `Tidelift Subscription <https://tidelift.com/subscription/pkg/pypi-apispec?utm_source=pypi-apispec&utm_medium=referral&utm_campaign=readme>`_.
263
264 Tidelift gives software development teams a single source for purchasing and maintaining their software,
265 with professional-grade assurances from the experts who know it best,
266 while seamlessly integrating with existing tools. [`Get professional support`_]
267
268 .. _`Get professional support`: https://tidelift.com/subscription/pkg/pypi-apispec?utm_source=pypi-apispec&utm_medium=referral&utm_campaign=readme
269
270 .. image:: https://user-images.githubusercontent.com/2379650/45126032-50b69880-b13f-11e8-9c2c-abd16c433495.png
271 :target: https://tidelift.com/subscription/pkg/pypi-apispec?utm_source=pypi-apispec&utm_medium=referral&utm_campaign=readme
272 :alt: Get supported apispec with Tidelift
273
274 Security Contact Information
275 ============================
276
277 To report a security vulnerability, please use the
278 `Tidelift security contact <https://tidelift.com/security>`_.
279 Tidelift will coordinate the fix and disclosure.
280
281 Project Links
282 =============
283
284 - Docs: https://apispec.readthedocs.io/
285 - Changelog: https://apispec.readthedocs.io/en/latest/changelog.html
286 - Contributing Guidelines: https://apispec.readthedocs.io/en/latest/contributing.html
287 - PyPI: https://pypi.python.org/pypi/apispec
288 - Issues: https://github.com/marshmallow-code/apispec/issues
289
290
291 License
292 =======
293
294 MIT licensed. See the bundled `LICENSE <https://github.com/marshmallow-code/apispec/blob/dev/LICENSE>`_ file for more details.
295
296 Keywords: apispec swagger openapi specification oas documentation spec rest api
297 Platform: UNKNOWN
298 Classifier: License :: OSI Approved :: MIT License
299 Classifier: Programming Language :: Python :: 3
300 Classifier: Programming Language :: Python :: 3.6
301 Classifier: Programming Language :: Python :: 3.7
302 Classifier: Programming Language :: Python :: 3.8
303 Classifier: Programming Language :: Python :: 3.9
304 Classifier: Programming Language :: Python :: 3 :: Only
305 Requires-Python: >=3.6
306 Provides-Extra: dev
307 Provides-Extra: docs
308 Provides-Extra: lint
309 Provides-Extra: tests
310 Provides-Extra: validation
311 Provides-Extra: yaml
1313 :target: https://apispec.readthedocs.io/
1414 :alt: Documentation
1515
16 .. image:: https://badgen.net/badge/marshmallow/2,3?list=1
16 .. image:: https://badgen.net/badge/marshmallow/3?list=1
1717 :target: https://marshmallow.readthedocs.io/en/latest/upgrading.html
18 :alt: marshmallow 2/3 compatible
18 :alt: marshmallow 3 only
1919
2020 .. image:: https://badgen.net/badge/OAS/2,3?list=1&color=cyan
2121 :target: https://github.com/OAI/OpenAPI-Specification
6666 name = fields.Str()
6767
6868
69 # Optional security scheme support
70 api_key_scheme = {"type": "apiKey", "in": "header", "name": "X-API-Key"}
71 spec.components.security_scheme("ApiKeyAuth", api_key_scheme)
72
73
6974 # Optional Flask support
7075 app = Flask(__name__)
7176
7681 ---
7782 get:
7883 description: Get a random pet
84 security:
85 - ApiKeyAuth: []
7986 responses:
8087 200:
8188 content:
104111 # "/random": {
105112 # "get": {
106113 # "description": "Get a random pet",
114 # "security": [
115 # {
116 # "ApiKeyAuth": []
117 # }
118 # ],
107119 # "responses": {
108120 # "200": {
109121 # "content": {
157169 # }
158170 # }
159171 # }
172 # "securitySchemes": {
173 # "ApiKeyAuth": {
174 # "type": "apiKey",
175 # "in": "header",
176 # "name": "X-API-Key"
177 # }
178 # }
160179 # }
161180 # }
162181 # }
179198 # type: array
180199 # name: {type: string}
181200 # type: object
201 # securitySchemes:
202 # ApiKeyAuth:
203 # in: header
204 # name: X-API-KEY
205 # type: apiKey
182206 # info: {title: Swagger Petstore, version: 1.0.0}
183207 # openapi: 3.0.2
184208 # paths:
190214 # content:
191215 # application/json:
192216 # schema: {$ref: '#/components/schemas/Pet'}
217 # security:
218 # - ApiKeyAuth: []
193219 # tags: []
194220
195221
+0
-49
azure-pipelines.yml less more
0 trigger:
1 branches:
2 include: [dev, test-me-*]
3 tags:
4 include: ['*']
5
6 # Run builds nightly to catch incompatibilities with new marshmallow releases
7 schedules:
8 - cron: "0 0 * * *"
9 displayName: Daily midnight build
10 branches:
11 include:
12 - dev
13 always: "true"
14
15 resources:
16 repositories:
17 - repository: sloria
18 type: github
19 endpoint: github
20 name: sloria/azure-pipeline-templates
21 ref: refs/heads/sloria
22
23 jobs:
24 - template: job--python-tox.yml@sloria
25 parameters:
26 toxenvs:
27 - lint
28
29 - py35-marshmallow2
30 - py35-marshmallow3
31
32 - py36-marshmallow3
33
34 - py37-marshmallow3
35
36 - py38-marshmallow2
37 - py38-marshmallow3
38
39 - py38-marshmallowdev
40
41 - docs
42 os: linux
43 - template: job--pypi-release.yml@sloria
44 parameters:
45 python: "3.8"
46 distributions: "sdist bdist_wheel"
47 dependsOn:
48 - tox_linux
0 apispec (4.3.0+git20210322.1f66eef-1) UNRELEASED; urgency=low
1 -- Kali Janitor <[email protected]> Fri, 26 Mar 2021 21:18:15 -0000
2
03 apispec (3.3.1-0kali2) kali-dev; urgency=medium
14
25 [ Sophie Brun ]
2525 source_suffix = ".rst"
2626 master_doc = "index"
2727 project = "apispec"
28 copyright = "Steven Loria {:%Y}".format(dt.datetime.utcnow())
28 copyright = f"Steven Loria {dt.datetime.utcnow():%Y}"
2929
3030 version = release = apispec.__version__
3131
4646 name = fields.Str()
4747
4848
49 # Optional security scheme support
50 api_key_scheme = {"type": "apiKey", "in": "header", "name": "X-API-Key"}
51 spec.components.security_scheme("ApiKeyAuth", api_key_scheme)
52
53
4954 # Optional Flask support
5055 app = Flask(__name__)
5156
5661 ---
5762 get:
5863 description: Get a random pet
64 security:
65 - ApiKeyAuth: []
5966 responses:
6067 200:
6168 description: Return a pet
120127 # "type": "string"
121128 # }
122129 # }
123 # }
130 # },
131 # }
132 # },
133 # "securitySchemes": {
134 # "ApiKeyAuth": {
135 # "type": "apiKey",
136 # "in": "header",
137 # "name": "X-API-Key"
124138 # }
125139 # },
126140 # "paths": {
127141 # "/random": {
128142 # "get": {
129143 # "description": "Get a random pet",
144 # "security": [
145 # {
146 # "ApiKeyAuth": []
147 # }
148 # ],
130149 # "responses": {
131150 # "200": {
132151 # "description": "Return a pet",
170189 # name:
171190 # type: string
172191 # type: object
192 # securitySchemes:
193 # ApiKeyAuth:
194 # in: header
195 # name: X-API-KEY
196 # type: apiKey
173197 # paths:
174198 # /random:
175199 # get:
181205 # schema:
182206 # $ref: '#/components/schemas/Pet'
183207 # description: Return a pet
208 # security:
209 # - ApiKeyAuth: []
184210
185211 User Guide
186212 ==========
00 Install
11 =======
22
3 **apispec** requires Python >= 3.5.
3 **apispec** requires Python >= 3.6.
44
55 From the PyPI
66 -------------
2424 .. code-block:: python
2525
2626 from apispec import Path, BasePlugin
27 from apispec.utils import load_operations_from_docstring
27 from apispec.yaml_utils import load_operations_from_docstring
2828
2929
3030 class MyPlugin(BasePlugin):
31 def path_helper(self, path, func, **kwargs):
31 def path_helper(self, path, operations, func, **kwargs):
3232 """Path helper that parses docstrings for operations. Adds a
3333 ``func`` parameter to `apispec.APISpec.path`.
3434 """
35 operations = load_operations_from_docstring(func.__doc__)
36 return Path(path=path, operations=operations)
35 operations.update(load_operations_from_docstring(func.__doc__))
3736
3837
3938 All plugin helpers must accept extra `**kwargs`, allowing custom plugins to define new arguments if required.
0 [tool.black]
1 line-length = 88
2 target-version = ['py36', 'py37', 'py38']
+0
-11
readthedocs.yml less more
0 version: 2
1 sphinx:
2 configuration: docs/conf.py
3 formats: all
4 python:
5 version: 3.7
6 install:
7 - method: pip
8 path: .
9 extra_requirements:
10 - docs
11 license_files = LICENSE
22
33 [bdist_wheel]
4 # This flag says that the code is written to work on both Python 2 and Python
5 # 3. If at all possible, it is good practice to do this. If you cannot, you
6 # will need to generate wheels for each Python version that you support.
7 universal=1
4 universal = 1
85
96 [flake8]
107 ignore = E203, E266, E501, W503
118 max-line-length = 110
129 max-complexity = 18
1310 select = B,C,E,F,W,T4,B9
11
12 [egg_info]
13 tag_build =
14 tag_date = 0
15
33 EXTRAS_REQUIRE = {
44 "yaml": ["PyYAML>=3.10"],
55 "validation": ["prance[osv]>=0.11"],
6 "lint": ["flake8==3.8.2", "flake8-bugbear==20.1.4", "pre-commit~=2.4"],
6 "lint": ["flake8==3.9.0", "flake8-bugbear==21.3.2", "pre-commit~=2.4"],
77 "docs": [
8 "marshmallow>=2.19.2",
9 "pyyaml==5.3.1",
10 "sphinx==3.0.4",
8 "marshmallow>=3.0.0",
9 "pyyaml==5.4.1",
10 "sphinx==3.5.3",
1111 "sphinx-issues==1.2.0",
12 "sphinx-rtd-theme==0.4.3",
12 "sphinx-rtd-theme==0.5.1",
1313 ],
1414 }
1515 EXTRAS_REQUIRE["tests"] = (
1616 EXTRAS_REQUIRE["yaml"]
1717 + EXTRAS_REQUIRE["validation"]
18 + ["marshmallow>=2.19.2", "pytest", "mock"]
18 + ["marshmallow>=3.0.0", "pytest", "mock"]
1919 )
2020 EXTRAS_REQUIRE["dev"] = EXTRAS_REQUIRE["tests"] + EXTRAS_REQUIRE["lint"] + ["tox"]
2121
5959 license="MIT",
6060 zip_safe=False,
6161 keywords="apispec swagger openapi specification oas documentation spec rest api",
62 python_requires=">=3.5",
62 python_requires=">=3.6",
6363 classifiers=[
6464 "License :: OSI Approved :: MIT License",
6565 "Programming Language :: Python :: 3",
66 "Programming Language :: Python :: 3.5",
6766 "Programming Language :: Python :: 3.6",
6867 "Programming Language :: Python :: 3.7",
6968 "Programming Language :: Python :: 3.8",
69 "Programming Language :: Python :: 3.9",
7070 "Programming Language :: Python :: 3 :: Only",
7171 ],
7272 test_suite="tests",
22 from .core import APISpec
33 from .plugin import BasePlugin
44
5 __version__ = "3.3.1"
5 __version__ = "4.3.0"
66 __all__ = ["APISpec", "BasePlugin"]
2828 def __init__(self, plugins, openapi_version):
2929 self._plugins = plugins
3030 self.openapi_version = openapi_version
31 self._schemas = {}
32 self._responses = {}
33 self._parameters = {}
34 self._examples = {}
35 self._security_schemes = {}
31 self.schemas = {}
32 self.responses = {}
33 self.parameters = {}
34 self.headers = {}
35 self.examples = {}
36 self.security_schemes = {}
3637
3738 def to_dict(self):
3839 subsections = {
39 "schema": self._schemas,
40 "response": self._responses,
41 "parameter": self._parameters,
42 "example": self._examples,
43 "security_scheme": self._security_schemes,
40 "schema": self.schemas,
41 "response": self.responses,
42 "parameter": self.parameters,
43 "header": self.headers,
44 "example": self.examples,
45 "security_scheme": self.security_schemes,
4446 }
4547 return {
4648 COMPONENT_SUBSECTIONS[self.openapi_version.major][k]: v
6971
7072 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject
7173 """
72 if name in self._schemas:
73 raise DuplicateComponentNameError(
74 'Another schema with name "{}" is already registered.'.format(name)
74 if name in self.schemas:
75 raise DuplicateComponentNameError(
76 f'Another schema with name "{name}" is already registered.'
7577 )
7678 component = component or {}
7779 ret = component.copy()
8183 ret.update(plugin.schema_helper(name, component, **kwargs) or {})
8284 except PluginMethodNotImplementedError:
8385 continue
84 self._schemas[name] = ret
86 self.schemas[name] = ret
8587 return self
8688
8789 def response(self, component_id, component=None, **kwargs):
8991
9092 :param str component_id: ref_id to use as reference
9193 :param dict component: response fields
92 :param dict kwargs: plugin-specific arguments
93 """
94 if component_id in self._responses:
94 :param kwargs: plugin-specific arguments
95 """
96 if component_id in self.responses:
9597 raise DuplicateComponentNameError(
9698 'Another response with name "{}" is already registered.'.format(
9799 component_id
105107 ret.update(plugin.response_helper(component, **kwargs) or {})
106108 except PluginMethodNotImplementedError:
107109 continue
108 self._responses[component_id] = ret
110 self.responses[component_id] = ret
109111 return self
110112
111113 def parameter(self, component_id, location, component=None, **kwargs):
112114 """ Add a parameter which can be referenced.
113115
114 :param str param_id: identifier by which parameter may be referenced.
116 :param str component_id: identifier by which parameter may be referenced.
115117 :param str location: location of the parameter.
116118 :param dict component: parameter fields.
117 :param dict kwargs: plugin-specific arguments
118 """
119 if component_id in self._parameters:
119 :param kwargs: plugin-specific arguments
120 """
121 if component_id in self.parameters:
120122 raise DuplicateComponentNameError(
121123 'Another parameter with name "{}" is already registered.'.format(
122124 component_id
137139 ret.update(plugin.parameter_helper(component, **kwargs) or {})
138140 except PluginMethodNotImplementedError:
139141 continue
140 self._parameters[component_id] = ret
142 self.parameters[component_id] = ret
143 return self
144
145 def header(self, component_id, component):
146 """ Add a header which can be referenced.
147
148 :param str component_id: identifier by which header may be referenced.
149 :param dict component: header fields.
150
151 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#headerObject
152 """
153 if component_id in self.headers:
154 raise DuplicateComponentNameError(
155 f'Another header with name "{component_id}" is already registered.'
156 )
157 self.headers[component_id] = component
141158 return self
142159
143160 def example(self, name, component, **kwargs):
148165
149166 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#exampleObject
150167 """
151 if name in self._examples:
152 raise DuplicateComponentNameError(
153 'Another example with name "{}" is already registered.'.format(name)
154 )
155 self._examples[name] = component
168 if name in self.examples:
169 raise DuplicateComponentNameError(
170 f'Another example with name "{name}" is already registered.'
171 )
172 self.examples[name] = component
156173 return self
157174
158175 def security_scheme(self, component_id, component):
159176 """Add a security scheme which can be referenced.
160177
161178 :param str component_id: component_id to use as reference
162 :param dict kwargs: security scheme fields
163 """
164 if component_id in self._security_schemes:
179 :param dict component: security scheme fields
180 """
181 if component_id in self.security_schemes:
165182 raise DuplicateComponentNameError(
166183 'Another security scheme with name "{}" is already registered.'.format(
167184 component_id
168185 )
169186 )
170 self._security_schemes[component_id] = component
187 self.security_schemes[component_id] = component
171188 return self
172189
173190
180197 See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#infoObject
181198 :param str|OpenAPIVersion openapi_version: OpenAPI Specification version.
182199 Should be in the form '2.x' or '3.x.x' to comply with the OpenAPI standard.
183 :param dict options: Optional top-level keys
200 :param options: Optional top-level keys
184201 See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#openapi-object
185202 """
186203
242259 summary=None,
243260 description=None,
244261 parameters=None,
245 **kwargs
262 **kwargs,
246263 ):
247264 """Add a new path object to the spec.
248265
253270 :param str summary: short summary relevant to all operations in this path
254271 :param str description: long description relevant to all operations in this path
255272 :param list|None parameters: list of parameters relevant to all operations in this path
256 :param dict kwargs: parameters used by any path helpers see :meth:`register_path_helper`
273 :param kwargs: parameters used by any path helpers see :meth:`register_path_helper`
257274 """
258275 # operations and parameters must be deepcopied because they are mutated
259276 # in clean_operations and operation helpers and path may be called twice
299316 Otherwise, it is assumed to be a reference name as string and the corresponding $ref
300317 string is returned.
301318
302 :param str obj_type: "parameter" or "response"
303 :param dict|str obj: parameter or response in dict form or as ref_id string
319 :param str obj_type: "schema", "parameter", "response" or "security_scheme"
320 :param dict|str obj: object in dict form or as ref_id string
304321 """
305322 if isinstance(obj, dict):
306323 return obj
307324 return build_reference(obj_type, self.openapi_version.major, obj)
325
326 def _resolve_schema(self, obj):
327 """Replace schema reference as string with a $ref if needed."""
328 if not isinstance(obj, dict):
329 return
330 if self.openapi_version.major < 3:
331 if "schema" in obj:
332 obj["schema"] = self.get_ref("schema", obj["schema"])
333 else:
334 if "content" in obj:
335 for content in obj["content"].values():
336 if "schema" in content:
337 content["schema"] = self.get_ref("schema", content["schema"])
308338
309339 def clean_parameters(self, parameters):
310340 """Ensure that all parameters with "in" equal to "path" are also required
322352 missing_attrs = [attr for attr in ("name", "in") if attr not in parameter]
323353 if missing_attrs:
324354 raise InvalidParameterError(
325 "Missing keys {} for parameter".format(missing_attrs)
355 f"Missing keys {missing_attrs} for parameter"
326356 )
327357
328358 # OpenAPI Spec 3 and 2 don't allow for duplicated parameters
364394 for operation in (operations or {}).values():
365395 if "parameters" in operation:
366396 operation["parameters"] = self.clean_parameters(operation["parameters"])
397 # OAS 3
398 if "requestBody" in operation:
399 self._resolve_schema(operation["requestBody"])
367400 if "responses" in operation:
368401 responses = OrderedDict()
369402 for code, response in operation["responses"].items():
372405 except (TypeError, ValueError):
373406 if self.openapi_version.major < 3 and code != "default":
374407 warnings.warn("Non-integer code not allowed in OpenAPI < 3")
375
408 self._resolve_schema(response)
376409 responses[str(code)] = self.get_ref("response", response)
377410 operation["responses"] = responses
5959 # 'format': 'date-time',
6060 # 'readOnly': True,
6161 # 'type': 'string'},
62 # 'id': {'format': 'int32',
63 # 'readOnly': True,
62 # 'id': {'readOnly': True,
6463 # 'type': 'integer'},
6564 # 'name': {'description': "The user's name",
6665 # 'type': 'string'}},
139138 class MyCustomField(Integer):
140139 # ...
141140
142 @ma_plugin.map_to_openapi_type(Integer) # will map to ('integer', 'int32')
141 @ma_plugin.map_to_openapi_type(Integer) # will map to ('integer', None)
143142 class MyCustomFieldThatsKindaLikeAnInteger(Integer):
144143 # ...
145144 """
187186 return response
188187
189188 def operation_helper(self, operations, **kwargs):
190 for operation in operations.values():
191 if not isinstance(operation, dict):
192 continue
193 if "parameters" in operation:
194 operation["parameters"] = self.resolver.resolve_parameters(
195 operation["parameters"]
196 )
197 if self.openapi_version.major >= 3:
198 if "requestBody" in operation:
199 self.resolver.resolve_schema(operation["requestBody"])
200 for response in operation.get("responses", {}).values():
201 self.resolver.resolve_response(response)
189 self.resolver.resolve_operations(operations)
202190
203191 def warn_if_schema_already_in_spec(self, schema_key):
204192 """Method to warn the user if the schema has already been added to the
1919 return schema()
2020 if isinstance(schema, marshmallow.Schema):
2121 return schema
22 try:
23 return marshmallow.class_registry.get_class(schema)()
24 except marshmallow.exceptions.RegistryError:
25 raise ValueError(
26 "{!r} is not a marshmallow.Schema subclass or instance and has not"
27 " been registered in the marshmallow class registry.".format(schema)
28 )
22 return marshmallow.class_registry.get_class(schema)()
2923
3024
3125 def resolve_schema_cls(schema):
3832 return schema
3933 if isinstance(schema, marshmallow.Schema):
4034 return type(schema)
41 try:
42 return marshmallow.class_registry.get_class(schema)
43 except marshmallow.exceptions.RegistryError:
44 raise ValueError(
45 "{!r} is not a marshmallow.Schema subclass or instance and has not"
46 " been registered in the marshmallow class registry.".format(schema)
47 )
35 return marshmallow.class_registry.get_class(schema)
4836
4937
5038 def get_fields(schema, *, exclude_dump_only=False):
6048 fields = copy.deepcopy(schema._declared_fields)
6149 else:
6250 raise ValueError(
63 "{!r} doesn't have either `fields` or `_declared_fields`.".format(schema)
51 f"{schema!r} doesn't have either `fields` or `_declared_fields`."
6452 )
6553 Meta = getattr(schema, "Meta", None)
6654 warn_if_fields_defined_in_meta(fields, Meta)
9078
9179 :param dict fields: A dictionary of fields name field object pairs
9280 :param Meta: the schema's Meta class
93 :param bool exclude_dump_only: whether to filter fields in Meta.dump_only
81 :param bool exclude_dump_only: whether to filter dump_only fields
9482 """
9583 exclude = list(getattr(Meta, "exclude", []))
9684 if exclude_dump_only:
9785 exclude.extend(getattr(Meta, "dump_only", []))
9886
9987 filtered_fields = OrderedDict(
100 (key, value) for key, value in fields.items() if key not in exclude
88 (key, value)
89 for key, value in fields.items()
90 if key not in exclude and not (exclude_dump_only and value.dump_only)
10191 )
10292
10393 return filtered_fields
132122 :param int counter: the counter of the number of recursions
133123 :return: the unique name
134124 """
135 if name not in components._schemas:
125 if name not in components.schemas:
136126 return name
137127 if not counter: # first time through recursion
138128 warnings.warn(
1616
1717 RegexType = type(re.compile(""))
1818
19 MARSHMALLOW_VERSION_INFO = tuple(
20 [int(part) for part in marshmallow.__version__.split(".") if part.isdigit()]
21 )
22
23
2419 # marshmallow field => (JSON Schema type, format)
2520 DEFAULT_FIELD_MAPPING = {
26 marshmallow.fields.Integer: ("integer", "int32"),
21 marshmallow.fields.Integer: ("integer", None),
2722 marshmallow.fields.Number: ("number", None),
28 marshmallow.fields.Float: ("number", "float"),
23 marshmallow.fields.Float: ("number", None),
2924 marshmallow.fields.Decimal: ("number", None),
3025 marshmallow.fields.String: ("string", None),
3126 marshmallow.fields.Boolean: ("boolean", None),
8782
8883 def init_attribute_functions(self):
8984 self.attribute_functions = [
85 # self.field2type_and_format should run first
86 # as other functions may rely on its output
9087 self.field2type_and_format,
9188 self.field2default,
9289 self.field2choices,
211208 else:
212209 default = field.missing
213210 if default is not marshmallow.missing and not callable(default):
214 if MARSHMALLOW_VERSION_INFO[0] >= 3:
215 default = field._serialize(default, None, None)
211 default = field._serialize(default, None, None)
216212 ret["default"] = default
217213 return ret
218214
277273 ] = True
278274 return attributes
279275
280 def field2range(self, field, **kwargs):
276 def field2range(self, field, ret):
281277 """Return the dictionary of OpenAPI field attributes for a set of
282278 :class:`Range <marshmallow.validators.Range>` validators.
283279
294290 )
295291 ]
296292
297 attributes = {}
298 for validator in validators:
299 if validator.min is not None:
300 if hasattr(attributes, "minimum"):
301 attributes["minimum"] = max(attributes["minimum"], validator.min)
302 else:
303 attributes["minimum"] = validator.min
304 if validator.max is not None:
305 if hasattr(attributes, "maximum"):
306 attributes["maximum"] = min(attributes["maximum"], validator.max)
307 else:
308 attributes["maximum"] = validator.max
309 return attributes
293 min_attr, max_attr = (
294 ("minimum", "maximum")
295 if ret.get("type") in {"number", "integer"}
296 else ("x-minimum", "x-maximum")
297 )
298 return make_min_max_attributes(validators, min_attr, max_attr)
310299
311300 def field2length(self, field, **kwargs):
312301 """Return the dictionary of OpenAPI field attributes for a set of
315304 :param Field field: A marshmallow field.
316305 :rtype: dict
317306 """
318 attributes = {}
319
320307 validators = [
321308 validator
322309 for validator in field.validators
333320 min_attr = "minItems" if is_array else "minLength"
334321 max_attr = "maxItems" if is_array else "maxLength"
335322
336 for validator in validators:
337 if validator.min is not None:
338 if hasattr(attributes, min_attr):
339 attributes[min_attr] = max(attributes[min_attr], validator.min)
340 else:
341 attributes[min_attr] = validator.min
342 if validator.max is not None:
343 if hasattr(attributes, max_attr):
344 attributes[max_attr] = min(attributes[max_attr], validator.max)
345 else:
346 attributes[max_attr] = validator.max
347
348 for validator in validators:
349 if validator.equal is not None:
350 attributes[min_attr] = validator.equal
351 attributes[max_attr] = validator.equal
352 return attributes
323 equal_list = [
324 validator.equal for validator in validators if validator.equal is not None
325 ]
326 if equal_list:
327 return {min_attr: equal_list[0], max_attr: equal_list[0]}
328
329 return make_min_max_attributes(validators, min_attr, max_attr)
353330
354331 def field2pattern(self, field, **kwargs):
355332 """Return the dictionary of OpenAPI field attributes for a set of
396373 metadata = {
397374 key.replace("_", "-") if key.startswith("x_") else key: value
398375 for key, value in field.metadata.items()
376 if isinstance(key, str)
399377 }
400378
401379 # Avoid validation error with "Additional properties not allowed"
435413 """
436414 ret = {}
437415 if isinstance(field, marshmallow.fields.List):
438 inner_field = (
439 field.inner if MARSHMALLOW_VERSION_INFO[0] >= 3 else field.container
440 )
441 ret["items"] = self.field2property(inner_field)
416 ret["items"] = self.field2property(field.inner)
442417 return ret
443418
444419 def dict2properties(self, field, **kwargs):
452427 """
453428 ret = {}
454429 if isinstance(field, marshmallow.fields.Dict):
455 if MARSHMALLOW_VERSION_INFO[0] >= 3:
456 value_field = field.value_field
457 if value_field:
458 ret["additionalProperties"] = self.field2property(value_field)
459 return ret
430 value_field = field.value_field
431 if value_field:
432 ret["additionalProperties"] = self.field2property(value_field)
433 return ret
434
435
436 def make_min_max_attributes(validators, min_attr, max_attr):
437 """Return a dictionary of minimum and maximum attributes based on a list
438 of validators. If either minimum or maximum values are not present in any
439 of the validator objects that attribute will be omitted.
440
441 :param validators list: A list of `Marshmallow` validator objects. Each
442 objct is inspected for a minimum and maximum values
443 :param min_attr string: The OpenAPI attribute for the minimum value
444 :param max_attr string: The OpenAPI attribute for the maximum value
445 """
446 attributes = {}
447 min_list = [validator.min for validator in validators if validator.min is not None]
448 max_list = [validator.max for validator in validators if validator.max is not None]
449 if min_list:
450 attributes[min_attr] = max(min_list)
451 if max_list:
452 attributes[max_attr] = min(max_list)
453 return attributes
1818 make_schema_key,
1919 resolve_schema_instance,
2020 get_unique_schema_name,
21 )
22
23
24 MARSHMALLOW_VERSION_INFO = tuple(
25 [int(part) for part in marshmallow.__version__.split(".") if part.isdigit()]
2621 )
2722
2823
5954 # Schema references
6055 self.refs = {}
6156
62 @staticmethod
63 def _observed_name(field, name):
64 """Adjust field name to reflect `dump_to` and `load_from` attributes.
65
66 :param Field field: A marshmallow field.
67 :param str name: Field name
68 :rtype: str
69 """
70 if MARSHMALLOW_VERSION_INFO[0] < 3:
71 # use getattr in case we're running against older versions of marshmallow.
72 dump_to = getattr(field, "dump_to", None)
73 load_from = getattr(field, "load_from", None)
74 return dump_to or load_from or name
75 return field.data_key or name
76
7757 def resolve_nested_schema(self, schema):
7858 """Return the OpenAPI representation of a marshmallow Schema.
7959
8666
8767 :param schema: schema to add to the spec
8868 """
89 schema_instance = resolve_schema_instance(schema)
69 try:
70 schema_instance = resolve_schema_instance(schema)
71 # If schema is a string and is not found in registry,
72 # assume it is a schema reference
73 except marshmallow.exceptions.RegistryError:
74 return build_reference("schema", self.openapi_version.major, schema)
9075 schema_key = make_schema_key(schema_instance)
9176 if schema_key not in self.refs:
9277 name = self.schema_name_resolver(schema)
10994 return self.get_ref_dict(schema_instance)
11095
11196 def schema2parameters(
112 self,
113 schema,
114 *,
115 default_in="body",
116 name="body",
117 required=False,
118 description=None
97 self, schema, *, location, name="body", required=False, description=None
11998 ):
12099 """Return an array of OpenAPI parameters given a given marshmallow
121 :class:`Schema <marshmallow.Schema>`. If `default_in` is "body", then return an array
100 :class:`Schema <marshmallow.Schema>`. If `location` is "body", then return an array
122101 of a single parameter; else return an array of a parameter for each included field in
123102 the :class:`Schema <marshmallow.Schema>`.
124103
104 In OpenAPI 3, only "query", "header", "path" or "cookie" are allowed for the location
105 of parameters. "requestBody" is used when fields are in the body.
106
125107 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#parameterObject
126108 """
127 openapi_default_in = __location_map__.get(default_in, default_in)
128 if self.openapi_version.major < 3 and openapi_default_in == "body":
129 prop = self.resolve_nested_schema(schema)
130
109 location = __location_map__.get(location, location)
110 # OAS 2 body parameter
111 if location == "body":
131112 param = {
132 "in": openapi_default_in,
113 "in": location,
133114 "required": required,
134115 "name": name,
135 "schema": prop,
116 "schema": self.resolve_nested_schema(schema),
136117 }
137
138118 if description:
139119 param["description"] = description
140
141120 return [param]
142121
143122 assert not getattr(
146125
147126 fields = get_fields(schema, exclude_dump_only=True)
148127
149 return self.fields2parameters(fields, default_in=default_in)
150
151 def fields2parameters(self, fields, *, default_in):
152 """Return an array of OpenAPI parameters given a mapping between field names and
153 :class:`Field <marshmallow.Field>` objects. If `default_in` is "body", then return an array
154 of a single parameter; else return an array of a parameter for each included field in
155 the :class:`Schema <marshmallow.Schema>`.
156
157 In OpenAPI3, only "query", "header", "path" or "cookie" are allowed for the location
158 of parameters. In OpenAPI 3, "requestBody" is used when fields are in the body.
159
160 This function always returns a list, with a parameter
161 for each included field in the :class:`Schema <marshmallow.Schema>`.
162
163 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#parameterObject
164 """
165 parameters = []
166 body_param = None
167 for field_name, field_obj in fields.items():
168 if field_obj.dump_only:
169 continue
170 param = self.field2parameter(
171 field_obj,
172 name=self._observed_name(field_obj, field_name),
173 default_in=default_in,
128 return [
129 self._field2parameter(
130 field_obj, name=field_obj.data_key or field_name, location=location,
174131 )
175 if (
176 self.openapi_version.major < 3
177 and param["in"] == "body"
178 and body_param is not None
179 ):
180 body_param["schema"]["properties"].update(param["schema"]["properties"])
181 required_fields = param["schema"].get("required", [])
182 if required_fields:
183 body_param["schema"].setdefault("required", []).extend(
184 required_fields
185 )
186 else:
187 if self.openapi_version.major < 3 and param["in"] == "body":
188 body_param = param
189 parameters.append(param)
190 return parameters
191
192 def field2parameter(self, field, *, name, default_in):
132 for field_name, field_obj in fields.items()
133 ]
134
135 def _field2parameter(self, field, *, name, location):
193136 """Return an OpenAPI parameter as a `dict`, given a marshmallow
194137 :class:`Field <marshmallow.Field>`.
195138
196139 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#parameterObject
197140 """
198 location = field.metadata.get("location", None)
141 ret = {"in": location, "name": name, "required": field.required}
142
199143 prop = self.field2property(field)
200 return self.property2parameter(
201 prop,
202 name=name,
203 required=field.required,
204 multiple=isinstance(field, marshmallow.fields.List),
205 location=location,
206 default_in=default_in,
207 )
208
209 def property2parameter(
210 self, prop, *, name, required, multiple, location, default_in
211 ):
212 """Return the Parameter Object definition for a JSON Schema property.
213
214 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#parameterObject
215
216 :param dict prop: JSON Schema property
217 :param str name: Field name
218 :param bool required: Parameter is required
219 :param bool multiple: Parameter is repeated
220 :param str location: Location to look for ``name``
221 :param str default_in: Default location to look for ``name``
222 :raise: TranslationError if arg object cannot be translated to a Parameter Object schema.
223 :rtype: dict, a Parameter Object
224 """
225 openapi_default_in = __location_map__.get(default_in, default_in)
226 openapi_location = __location_map__.get(location, openapi_default_in)
227 ret = {"in": openapi_location, "name": name}
228
229 if openapi_location == "body":
230 ret["required"] = False
231 ret["name"] = "body"
232 ret["schema"] = {"type": "object", "properties": {name: prop}}
233 if required:
234 ret["schema"]["required"] = [name]
144 multiple = isinstance(field, marshmallow.fields.List)
145
146 if self.openapi_version.major < 3:
147 if multiple:
148 ret["collectionFormat"] = "multi"
149 ret.update(prop)
235150 else:
236 ret["required"] = required
237 if self.openapi_version.major < 3:
238 if multiple:
239 ret["collectionFormat"] = "multi"
240 ret.update(prop)
241 else:
242 if multiple:
243 ret["explode"] = True
244 ret["style"] = "form"
245 if prop.get("description", None):
246 ret["description"] = prop.pop("description")
247 ret["schema"] = prop
151 if multiple:
152 ret["explode"] = True
153 ret["style"] = "form"
154 if prop.get("description", None):
155 ret["description"] = prop.pop("description")
156 ret["schema"] = prop
248157 return ret
249158
250159 def schema2jsonschema(self, schema):
268177 jsonschema["title"] = Meta.title
269178 if hasattr(Meta, "description"):
270179 jsonschema["description"] = Meta.description
180 if hasattr(Meta, "unknown"):
181 jsonschema["additionalProperties"] = Meta.unknown == marshmallow.INCLUDE
271182
272183 return jsonschema
273184
285196 jsonschema = {"type": "object", "properties": OrderedDict() if ordered else {}}
286197
287198 for field_name, field_obj in fields.items():
288 observed_field_name = self._observed_name(field_obj, field_name)
289 property = self.field2property(field_obj)
290 jsonschema["properties"][observed_field_name] = property
199 observed_field_name = field_obj.data_key or field_name
200 prop = self.field2property(field_obj)
201 jsonschema["properties"][observed_field_name] = prop
291202
292203 if field_obj.required:
293204 if not partial or (
1414 self.openapi_version = openapi_version
1515 self.converter = converter
1616
17 def resolve_operations(self, operations, **kwargs):
18 """Resolve marshmallow Schemas in a dict mapping operation to OpenApi `Operation Object
19 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject`_"""
20
21 for operation in operations.values():
22 if not isinstance(operation, dict):
23 continue
24 if "parameters" in operation:
25 operation["parameters"] = self.resolve_parameters(
26 operation["parameters"]
27 )
28 if self.openapi_version.major >= 3:
29 self.resolve_callback(operation.get("callbacks", {}))
30 if "requestBody" in operation:
31 self.resolve_schema(operation["requestBody"])
32 for response in operation.get("responses", {}).values():
33 self.resolve_response(response)
34
35 def resolve_callback(self, callbacks):
36 """Resolve marshmallow Schemas in a dict mapping callback name to OpenApi `Callback Object
37 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#callbackObject`_.
38
39 This is done recursively, so it is possible to define callbacks in your callbacks.
40
41 Example: ::
42
43 #Input
44 {
45 "userEvent": {
46 "https://my.example/user-callback": {
47 "post": {
48 "requestBody": {
49 "content": {
50 "application/json": {
51 "schema": UserSchema
52 }
53 }
54 }
55 },
56 }
57 }
58 }
59
60 #Output
61 {
62 "userEvent": {
63 "https://my.example/user-callback": {
64 "post": {
65 "requestBody": {
66 "content": {
67 "application/json": {
68 "schema": {
69 "$ref": "#/components/schemas/User"
70 }
71 }
72 }
73 }
74 },
75 }
76 }
77 }
78
79
80 """
81 for callback in callbacks.values():
82 if isinstance(callback, dict):
83 for path in callback.values():
84 self.resolve_operations(path)
85
1786 def resolve_parameters(self, parameters):
1887 """Resolve marshmallow Schemas in a list of OpenAPI `Parameter Objects
1988 <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#parameter-object>`_.
37106
38107 #Output
39108 [
40 {"in": "query", "name": "id", "required": False, "schema": {"type": "integer", "format": "int32"}},
109 {"in": "query", "name": "id", "required": False, "schema": {"type": "integer"}},
41110 {"in": "query", "name": "name", "required": False, "schema": {"type": "string"}}
42111 ]
43112
75144 ):
76145 schema_instance = resolve_schema_instance(parameter.pop("schema"))
77146 resolved += self.converter.schema2parameters(
78 schema_instance, default_in=parameter.pop("in"), **parameter
147 schema_instance, location=parameter.pop("in"), **parameter
79148 )
80149 else:
81150 self.resolve_schema(parameter)
161230
162231 def resolve_schema_dict(self, schema):
163232 """Resolve a marshmallow Schema class, object, or a string that resolves
164 to a Schema class or an OpenAPI Schema Object containing one of the above
165 to an OpenAPI Schema Object or Reference Object.
233 to a Schema class or a schema reference or an OpenAPI Schema Object
234 containing one of the above to an OpenAPI Schema Object or Reference Object.
166235
167236 If the input is a marshmallow Schema class, object or a string that resolves
168237 to a Schema class the Schema will be translated to an OpenAPI Schema Object
1717
1818 :param str name: Identifier by which schema may be referenced
1919 :param dict definition: Schema definition
20 :param dict kwargs: All additional keywords arguments sent to `APISpec.schema()`
20 :param kwargs: All additional keywords arguments sent to `APISpec.schema()`
2121 """
2222 raise PluginMethodNotImplementedError
2323
2525 """May return response component description as a dict.
2626
2727 :param dict response: Response fields
28 :param dict kwargs: All additional keywords arguments sent to `APISpec.response()`
28 :param kwargs: All additional keywords arguments sent to `APISpec.response()`
2929 """
3030 raise PluginMethodNotImplementedError
3131
3333 """May return parameter component description as a dict.
3434
3535 :param dict parameter: Parameter fields
36 :param dict kwargs: All additional keywords arguments sent to `APISpec.parameter()`
36 :param kwargs: All additional keywords arguments sent to `APISpec.parameter()`
3737 """
3838 raise PluginMethodNotImplementedError
3939
4646 :param list parameters: A `list` of parameters objects or references for the path. See
4747 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#parameterObject
4848 and https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#referenceObject
49 :param dict kwargs: All additional keywords arguments sent to `APISpec.path()`
49 :param kwargs: All additional keywords arguments sent to `APISpec.path()`
5050
5151 Return value should be a string or None. If a string is returned, it
5252 is set as the path.
6363 :param str path: Path to the resource
6464 :param dict operations: A `dict` mapping HTTP methods to operation object.
6565 See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#operationObject
66 :param dict kwargs: All additional keywords arguments sent to `APISpec.path()`
66 :param kwargs: All additional keywords arguments sent to `APISpec.path()`
6767 """
6868 raise PluginMethodNotImplementedError
1919 "schema": "schemas",
2020 "response": "responses",
2121 "parameter": "parameters",
22 "header": "headers",
2223 "example": "examples",
2324 "security_scheme": "securitySchemes",
2425 },
102103 < self.MAX_EXCLUSIVE_VERSION
103104 ):
104105 raise exceptions.APISpecError(
105 "Not a valid OpenAPI version number: {}".format(openapi_version)
106 f"Not a valid OpenAPI version number: {openapi_version}"
106107 )
107108 super().__init__(openapi_version)
108109
0 Metadata-Version: 2.1
1 Name: apispec
2 Version: 4.3.0
3 Summary: A pluggable API specification generator. Currently supports the OpenAPI Specification (f.k.a. the Swagger specification).
4 Home-page: https://github.com/marshmallow-code/apispec
5 Author: Steven Loria
6 Author-email: [email protected]
7 License: MIT
8 Project-URL: Funding, https://opencollective.com/marshmallow
9 Project-URL: Issues, https://github.com/marshmallow-code/apispec/issues
10 Project-URL: Tidelift, https://tidelift.com/subscription/pkg/pypi-apispec?utm_source=pypi-apispec&utm_medium=pypi
11 Description: *******
12 apispec
13 *******
14
15 .. image:: https://badgen.net/pypi/v/apispec
16 :target: https://pypi.org/project/apispec/
17 :alt: PyPI version
18
19 .. image:: https://dev.azure.com/sloria/sloria/_apis/build/status/marshmallow-code.apispec?branchName=dev
20 :target: https://dev.azure.com/sloria/sloria/_build/latest?definitionId=8&branchName=dev
21 :alt: Build status
22
23 .. image:: https://readthedocs.org/projects/apispec/badge/
24 :target: https://apispec.readthedocs.io/
25 :alt: Documentation
26
27 .. image:: https://badgen.net/badge/marshmallow/3?list=1
28 :target: https://marshmallow.readthedocs.io/en/latest/upgrading.html
29 :alt: marshmallow 3 only
30
31 .. image:: https://badgen.net/badge/OAS/2,3?list=1&color=cyan
32 :target: https://github.com/OAI/OpenAPI-Specification
33 :alt: OpenAPI Specification 2/3 compatible
34
35 .. image:: https://badgen.net/badge/code%20style/black/000
36 :target: https://github.com/ambv/black
37 :alt: code style: black
38
39 A pluggable API specification generator. Currently supports the `OpenAPI Specification <https://github.com/OAI/OpenAPI-Specification>`_ (f.k.a. the Swagger specification).
40
41 Features
42 ========
43
44 - Supports the OpenAPI Specification (versions 2 and 3)
45 - Framework-agnostic
46 - Built-in support for `marshmallow <https://marshmallow.readthedocs.io/>`_
47 - Utilities for parsing docstrings
48
49 Example Application
50 ===================
51
52 .. code-block:: python
53
54 from apispec import APISpec
55 from apispec.ext.marshmallow import MarshmallowPlugin
56 from apispec_webframeworks.flask import FlaskPlugin
57 from flask import Flask
58 from marshmallow import Schema, fields
59
60
61 # Create an APISpec
62 spec = APISpec(
63 title="Swagger Petstore",
64 version="1.0.0",
65 openapi_version="3.0.2",
66 plugins=[FlaskPlugin(), MarshmallowPlugin()],
67 )
68
69 # Optional marshmallow support
70 class CategorySchema(Schema):
71 id = fields.Int()
72 name = fields.Str(required=True)
73
74
75 class PetSchema(Schema):
76 category = fields.List(fields.Nested(CategorySchema))
77 name = fields.Str()
78
79
80 # Optional security scheme support
81 api_key_scheme = {"type": "apiKey", "in": "header", "name": "X-API-Key"}
82 spec.components.security_scheme("ApiKeyAuth", api_key_scheme)
83
84
85 # Optional Flask support
86 app = Flask(__name__)
87
88
89 @app.route("/random")
90 def random_pet():
91 """A cute furry animal endpoint.
92 ---
93 get:
94 description: Get a random pet
95 security:
96 - ApiKeyAuth: []
97 responses:
98 200:
99 content:
100 application/json:
101 schema: PetSchema
102 """
103 pet = get_random_pet()
104 return PetSchema().dump(pet)
105
106
107 # Register the path and the entities within it
108 with app.test_request_context():
109 spec.path(view=random_pet)
110
111
112 Generated OpenAPI Spec
113 ----------------------
114
115 .. code-block:: python
116
117 import json
118
119 print(json.dumps(spec.to_dict(), indent=2))
120 # {
121 # "paths": {
122 # "/random": {
123 # "get": {
124 # "description": "Get a random pet",
125 # "security": [
126 # {
127 # "ApiKeyAuth": []
128 # }
129 # ],
130 # "responses": {
131 # "200": {
132 # "content": {
133 # "application/json": {
134 # "schema": {
135 # "$ref": "#/components/schemas/Pet"
136 # }
137 # }
138 # }
139 # }
140 # }
141 # }
142 # }
143 # },
144 # "tags": [],
145 # "info": {
146 # "title": "Swagger Petstore",
147 # "version": "1.0.0"
148 # },
149 # "openapi": "3.0.2",
150 # "components": {
151 # "parameters": {},
152 # "responses": {},
153 # "schemas": {
154 # "Category": {
155 # "type": "object",
156 # "properties": {
157 # "name": {
158 # "type": "string"
159 # },
160 # "id": {
161 # "type": "integer",
162 # "format": "int32"
163 # }
164 # },
165 # "required": [
166 # "name"
167 # ]
168 # },
169 # "Pet": {
170 # "type": "object",
171 # "properties": {
172 # "name": {
173 # "type": "string"
174 # },
175 # "category": {
176 # "type": "array",
177 # "items": {
178 # "$ref": "#/components/schemas/Category"
179 # }
180 # }
181 # }
182 # }
183 # "securitySchemes": {
184 # "ApiKeyAuth": {
185 # "type": "apiKey",
186 # "in": "header",
187 # "name": "X-API-Key"
188 # }
189 # }
190 # }
191 # }
192 # }
193
194 print(spec.to_yaml())
195 # components:
196 # parameters: {}
197 # responses: {}
198 # schemas:
199 # Category:
200 # properties:
201 # id: {format: int32, type: integer}
202 # name: {type: string}
203 # required: [name]
204 # type: object
205 # Pet:
206 # properties:
207 # category:
208 # items: {$ref: '#/components/schemas/Category'}
209 # type: array
210 # name: {type: string}
211 # type: object
212 # securitySchemes:
213 # ApiKeyAuth:
214 # in: header
215 # name: X-API-KEY
216 # type: apiKey
217 # info: {title: Swagger Petstore, version: 1.0.0}
218 # openapi: 3.0.2
219 # paths:
220 # /random:
221 # get:
222 # description: Get a random pet
223 # responses:
224 # 200:
225 # content:
226 # application/json:
227 # schema: {$ref: '#/components/schemas/Pet'}
228 # security:
229 # - ApiKeyAuth: []
230 # tags: []
231
232
233 Documentation
234 =============
235
236 Documentation is available at https://apispec.readthedocs.io/ .
237
238 Ecosystem
239 =========
240
241 A list of apispec-related libraries can be found at the GitHub wiki here:
242
243 https://github.com/marshmallow-code/apispec/wiki/Ecosystem
244
245 Support apispec
246 ===============
247
248 apispec is maintained by a group of
249 `volunteers <https://apispec.readthedocs.io/en/latest/authors.html>`_.
250 If you'd like to support the future of the project, please consider
251 contributing to our Open Collective:
252
253 .. image:: https://opencollective.com/marshmallow/donate/button.png
254 :target: https://opencollective.com/marshmallow
255 :width: 200
256 :alt: Donate to our collective
257
258 Professional Support
259 ====================
260
261 Professionally-supported apispec is available through the
262 `Tidelift Subscription <https://tidelift.com/subscription/pkg/pypi-apispec?utm_source=pypi-apispec&utm_medium=referral&utm_campaign=readme>`_.
263
264 Tidelift gives software development teams a single source for purchasing and maintaining their software,
265 with professional-grade assurances from the experts who know it best,
266 while seamlessly integrating with existing tools. [`Get professional support`_]
267
268 .. _`Get professional support`: https://tidelift.com/subscription/pkg/pypi-apispec?utm_source=pypi-apispec&utm_medium=referral&utm_campaign=readme
269
270 .. image:: https://user-images.githubusercontent.com/2379650/45126032-50b69880-b13f-11e8-9c2c-abd16c433495.png
271 :target: https://tidelift.com/subscription/pkg/pypi-apispec?utm_source=pypi-apispec&utm_medium=referral&utm_campaign=readme
272 :alt: Get supported apispec with Tidelift
273
274 Security Contact Information
275 ============================
276
277 To report a security vulnerability, please use the
278 `Tidelift security contact <https://tidelift.com/security>`_.
279 Tidelift will coordinate the fix and disclosure.
280
281 Project Links
282 =============
283
284 - Docs: https://apispec.readthedocs.io/
285 - Changelog: https://apispec.readthedocs.io/en/latest/changelog.html
286 - Contributing Guidelines: https://apispec.readthedocs.io/en/latest/contributing.html
287 - PyPI: https://pypi.python.org/pypi/apispec
288 - Issues: https://github.com/marshmallow-code/apispec/issues
289
290
291 License
292 =======
293
294 MIT licensed. See the bundled `LICENSE <https://github.com/marshmallow-code/apispec/blob/dev/LICENSE>`_ file for more details.
295
296 Keywords: apispec swagger openapi specification oas documentation spec rest api
297 Platform: UNKNOWN
298 Classifier: License :: OSI Approved :: MIT License
299 Classifier: Programming Language :: Python :: 3
300 Classifier: Programming Language :: Python :: 3.6
301 Classifier: Programming Language :: Python :: 3.7
302 Classifier: Programming Language :: Python :: 3.8
303 Classifier: Programming Language :: Python :: 3.9
304 Classifier: Programming Language :: Python :: 3 :: Only
305 Requires-Python: >=3.6
306 Provides-Extra: dev
307 Provides-Extra: docs
308 Provides-Extra: lint
309 Provides-Extra: tests
310 Provides-Extra: validation
311 Provides-Extra: yaml
0 AUTHORS.rst
1 CHANGELOG.rst
2 CONTRIBUTING.rst
3 LICENSE
4 MANIFEST.in
5 README.rst
6 pyproject.toml
7 setup.cfg
8 setup.py
9 docs/Makefile
10 docs/api_core.rst
11 docs/api_ext.rst
12 docs/authors.rst
13 docs/changelog.rst
14 docs/conf.py
15 docs/contributing.rst
16 docs/ecosystem.rst
17 docs/index.rst
18 docs/install.rst
19 docs/license.rst
20 docs/make.bat
21 docs/quickstart.rst
22 docs/special_topics.rst
23 docs/upgrading.rst
24 docs/using_plugins.rst
25 docs/writing_plugins.rst
26 src/apispec/__init__.py
27 src/apispec/core.py
28 src/apispec/exceptions.py
29 src/apispec/plugin.py
30 src/apispec/utils.py
31 src/apispec/yaml_utils.py
32 src/apispec.egg-info/PKG-INFO
33 src/apispec.egg-info/SOURCES.txt
34 src/apispec.egg-info/dependency_links.txt
35 src/apispec.egg-info/not-zip-safe
36 src/apispec.egg-info/requires.txt
37 src/apispec.egg-info/top_level.txt
38 src/apispec/ext/__init__.py
39 src/apispec/ext/marshmallow/__init__.py
40 src/apispec/ext/marshmallow/common.py
41 src/apispec/ext/marshmallow/field_converter.py
42 src/apispec/ext/marshmallow/openapi.py
43 src/apispec/ext/marshmallow/schema_resolver.py
44 tests/__init__.py
45 tests/conftest.py
46 tests/schemas.py
47 tests/test_core.py
48 tests/test_ext_marshmallow.py
49 tests/test_ext_marshmallow_common.py
50 tests/test_ext_marshmallow_field.py
51 tests/test_ext_marshmallow_openapi.py
52 tests/test_utils.py
53 tests/test_yaml_utils.py
54 tests/utils.py
55 tests/plugins/__init__.py
56 tests/plugins/dummy_plugin.py
57 tests/plugins/dummy_plugin_no_setup.py
0
1 [dev]
2 PyYAML>=3.10
3 flake8-bugbear==21.3.2
4 flake8==3.9.0
5 marshmallow>=3.0.0
6 mock
7 prance[osv]>=0.11
8 pre-commit~=2.4
9 pytest
10 tox
11
12 [docs]
13 marshmallow>=3.0.0
14 pyyaml==5.4.1
15 sphinx-issues==1.2.0
16 sphinx-rtd-theme==0.5.1
17 sphinx==3.5.3
18
19 [lint]
20 flake8-bugbear==21.3.2
21 flake8==3.9.0
22 pre-commit~=2.4
23
24 [tests]
25 PyYAML>=3.10
26 marshmallow>=3.0.0
27 mock
28 prance[osv]>=0.11
29 pytest
30
31 [validation]
32 prance[osv]>=0.11
33
34 [yaml]
35 PyYAML>=3.10
00 from marshmallow import Schema, fields
1
2 from apispec.ext.marshmallow.openapi import MARSHMALLOW_VERSION_INFO
31
42
53 class PetSchema(Schema):
3937
4038 class SelfReferencingSchema(Schema):
4139 id = fields.Int()
42 if MARSHMALLOW_VERSION_INFO[0] < 3:
43 single = fields.Nested("self")
44 many = fields.Nested("self", many=True)
45 else:
46 single = fields.Nested(lambda: SelfReferencingSchema())
47 many = fields.Nested(lambda: SelfReferencingSchema(many=True))
40 single = fields.Nested(lambda: SelfReferencingSchema())
41 many = fields.Nested(lambda: SelfReferencingSchema(many=True))
4842
4943
5044 class OrderedSchema(Schema):
1616 get_examples,
1717 get_paths,
1818 get_parameters,
19 get_headers,
1920 get_responses,
2021 get_security_schemes,
2122 build_ref,
5960 version="1.0.0",
6061 openapi_version=openapi_version,
6162 info={"description": description},
62 **security_kwargs
63 **security_kwargs,
6364 )
6465
6566
201202 spec.components.response("test_response")
202203
203204 def test_parameter(self, spec):
205 # Note: this is an OpenAPI v2 parameter header
206 # but is does the job for the test even for OpenAPI v3
204207 parameter = {"format": "int64", "type": "integer"}
205208 spec.components.parameter("PetId", "path", parameter)
206209 params = get_parameters(spec)
225228 match='Another parameter with name "test_parameter" is already registered.',
226229 ):
227230 spec.components.parameter("test_parameter", "path")
231
232 # Referenced headers are only supported in OAS 3.x
233 @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
234 def test_header(self, spec):
235 header = {"schema": {"type": "string"}}
236 spec.components.header("test_header", header.copy())
237 headers = get_headers(spec)
238 assert headers["test_header"] == header
239
240 # Referenced headers are only supported in OAS 3.x
241 @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
242 def test_header_is_chainable(self, spec):
243 header = {"schema": {"type": "string"}}
244 spec.components.header("header1", header).header("header2", header)
245 headers = get_headers(spec)
246 assert "header1" in headers
247 assert "header2" in headers
248
249 # Referenced headers are only supported in OAS 3.x
250 @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
251 def test_header_duplicate_name(self, spec):
252 spec.components.header("test_header", {"schema": {"type": "string"}})
253 with pytest.raises(
254 DuplicateComponentNameError,
255 match='Another header with name "test_header" is already registered.',
256 ):
257 spec.components.header("test_header", {"schema": {"type": "integer"}})
228258
229259 # Referenced examples are only supported in OAS 3.x
230260 @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
306336 }
307337 ],
308338 "responses": {
309 "200": {"schema": "Pet", "description": "successful operation"},
339 "200": {"description": "successful operation"},
310340 "400": {"description": "Invalid ID supplied"},
311341 "404": {"description": "Pet not found"},
312342 },
617647 with pytest.raises(APISpecError, match=message):
618648 spec.path("/pet/{petId}", operations={"dummy": {}})
619649
650 def test_path_resolve_response_schema(self, spec):
651 schema = {"schema": "PetSchema"}
652 if spec.openapi_version.major >= 3:
653 schema = {"content": {"application/json": schema}}
654 spec.path("/pet/{petId}", operations={"get": {"responses": {"200": schema}}})
655 resp = get_paths(spec)["/pet/{petId}"]["get"]["responses"]["200"]
656 if spec.openapi_version.major < 3:
657 schema = resp["schema"]
658 else:
659 schema = resp["content"]["application/json"]["schema"]
660 assert schema == build_ref(spec, "schema", "PetSchema")
661
662 # requestBody only exists in OAS 3
663 @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
664 def test_path_resolve_request_body(self, spec):
665 spec.path(
666 "/pet/{petId}",
667 operations={
668 "get": {
669 "requestBody": {
670 "content": {"application/json": {"schema": "PetSchema"}}
671 }
672 }
673 },
674 )
675 assert get_paths(spec)["/pet/{petId}"]["get"]["requestBody"]["content"][
676 "application/json"
677 ]["schema"] == build_ref(spec, "schema", "PetSchema")
678
620679
621680 class TestPlugins:
622681 @staticmethod
741800 self.output = output
742801
743802 def path_helper(self, path, operations, **kwargs):
744 self.output.append("plugin_{}_path".format(self.index))
803 self.output.append(f"plugin_{self.index}_path")
745804
746805 def operation_helper(self, path, operations, **kwargs):
747 self.output.append("plugin_{}_operations".format(self.index))
806 self.output.append(f"plugin_{self.index}_operations")
748807
749808 def test_plugins_order(self):
750809 """Test plugins execution order in APISpec.path
66
77 from apispec import APISpec
88 from apispec.ext.marshmallow import MarshmallowPlugin
9 from apispec.ext.marshmallow.openapi import MARSHMALLOW_VERSION_INFO
109 from apispec.ext.marshmallow import common
1110 from apispec.exceptions import APISpecError
1211 from .schemas import (
232231 reference = parameter["content"]["application/json"]["schema"]
233232 assert reference == build_ref(spec, "schema", "Pet")
234233
235 resolved_schema = spec.components._schemas["Pet"]
234 resolved_schema = spec.components.schemas["Pet"]
236235 assert resolved_schema["properties"]["name"]["type"] == "string"
237236 assert resolved_schema["properties"]["password"]["type"] == "string"
238237 assert resolved_schema["properties"]["id"]["type"] == "integer"
253252 reference = response["content"]["application/json"]["schema"]
254253 assert reference == build_ref(spec, "schema", "Pet")
255254
256 resolved_schema = spec.components._schemas["Pet"]
255 resolved_schema = spec.components.schemas["Pet"]
257256 assert resolved_schema["properties"]["id"]["type"] == "integer"
258257 assert resolved_schema["properties"]["name"]["type"] == "string"
259258 assert resolved_schema["properties"]["password"]["type"] == "string"
266265 reference = response["headers"]["PetHeader"]["schema"]
267266 assert reference == build_ref(spec, "schema", "Pet")
268267
269 resolved_schema = spec.components._schemas["Pet"]
268 resolved_schema = spec.components.schemas["Pet"]
270269 assert resolved_schema["properties"]["id"]["type"] == "integer"
271270 assert resolved_schema["properties"]["name"]["type"] == "string"
272271 assert resolved_schema["properties"]["password"]["type"] == "string"
327326
328327
329328 class TestOperationHelper:
329 @pytest.fixture
330 def make_pet_callback_spec(self, spec_fixture):
331 def _make_pet_spec(operations):
332 spec_fixture.spec.path(
333 path="/pet",
334 operations={
335 "post": {"callbacks": {"petEvent": {"petCallbackUrl": operations}}}
336 },
337 )
338 return spec_fixture
339
340 return _make_pet_spec
341
330342 @pytest.mark.parametrize(
331343 "pet_schema",
332344 (PetSchema, PetSchema(), PetSchema(many=True), "tests.schemas.PetSchema"),
363375 header_reference = get["responses"]["200"]["headers"]["PetHeader"]["schema"]
364376 assert schema_reference == build_ref(spec_fixture.spec, "schema", "Pet")
365377 assert header_reference == build_ref(spec_fixture.spec, "schema", "Pet")
366 assert len(spec_fixture.spec.components._schemas) == 1
367 resolved_schema = spec_fixture.spec.components._schemas["Pet"]
378 assert len(spec_fixture.spec.components.schemas) == 1
379 resolved_schema = spec_fixture.spec.components.schemas["Pet"]
368380 assert resolved_schema == spec_fixture.openapi.schema2jsonschema(PetSchema)
369381 assert get["responses"]["200"]["description"] == "successful operation"
370382
412424
413425 assert schema_reference == build_ref(spec_fixture.spec, "schema", "Pet")
414426 assert header_reference == build_ref(spec_fixture.spec, "schema", "Pet")
415 assert len(spec_fixture.spec.components._schemas) == 1
416 resolved_schema = spec_fixture.spec.components._schemas["Pet"]
427 assert len(spec_fixture.spec.components.schemas) == 1
428 resolved_schema = spec_fixture.spec.components.schemas["Pet"]
429 assert resolved_schema == spec_fixture.openapi.schema2jsonschema(PetSchema)
430 assert get["responses"]["200"]["description"] == "successful operation"
431
432 @pytest.mark.parametrize(
433 "pet_schema",
434 (PetSchema, PetSchema(), PetSchema(many=True), "tests.schemas.PetSchema"),
435 )
436 @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
437 def test_callback_schema_v3(self, make_pet_callback_spec, pet_schema):
438 spec_fixture = make_pet_callback_spec(
439 {
440 "get": {
441 "responses": {
442 "200": {
443 "content": {"application/json": {"schema": pet_schema}},
444 "description": "successful operation",
445 "headers": {"PetHeader": {"schema": pet_schema}},
446 }
447 }
448 }
449 }
450 )
451 p = get_paths(spec_fixture.spec)["/pet"]
452 c = p["post"]["callbacks"]["petEvent"]["petCallbackUrl"]
453 get = c["get"]
454 if isinstance(pet_schema, Schema) and pet_schema.many is True:
455 assert (
456 get["responses"]["200"]["content"]["application/json"]["schema"]["type"]
457 == "array"
458 )
459 schema_reference = get["responses"]["200"]["content"]["application/json"][
460 "schema"
461 ]["items"]
462 assert (
463 get["responses"]["200"]["headers"]["PetHeader"]["schema"]["type"]
464 == "array"
465 )
466 header_reference = get["responses"]["200"]["headers"]["PetHeader"][
467 "schema"
468 ]["items"]
469 else:
470 schema_reference = get["responses"]["200"]["content"]["application/json"][
471 "schema"
472 ]
473 header_reference = get["responses"]["200"]["headers"]["PetHeader"]["schema"]
474
475 assert schema_reference == build_ref(spec_fixture.spec, "schema", "Pet")
476 assert header_reference == build_ref(spec_fixture.spec, "schema", "Pet")
477 assert len(spec_fixture.spec.components.schemas) == 1
478 resolved_schema = spec_fixture.spec.components.schemas["Pet"]
417479 assert resolved_schema == spec_fixture.openapi.schema2jsonschema(PetSchema)
418480 assert get["responses"]["200"]["description"] == "successful operation"
419481
439501 p = get_paths(spec_fixture.spec)["/pet"]
440502 get = p["get"]
441503 assert get["parameters"] == spec_fixture.openapi.schema2parameters(
442 PetSchema(), default_in="query"
504 PetSchema(), location="query"
443505 )
444506 post = p["post"]
445507 assert post["parameters"] == spec_fixture.openapi.schema2parameters(
446508 PetSchema,
447 default_in="body",
509 location="body",
448510 required=True,
449511 name="pet",
450512 description="a pet schema",
468530 p = get_paths(spec_fixture.spec)["/pet"]
469531 get = p["get"]
470532 assert get["parameters"] == spec_fixture.openapi.schema2parameters(
471 PetSchema(), default_in="query"
533 PetSchema(), location="query"
472534 )
473535 for parameter in get["parameters"]:
474536 description = parameter.get("description", False)
485547 assert post["requestBody"]["description"] == "a pet schema"
486548 assert post["requestBody"]["required"]
487549
550 @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
551 def test_callback_schema_expand_parameters_v3(self, make_pet_callback_spec):
552 spec_fixture = make_pet_callback_spec(
553 {
554 "get": {"parameters": [{"in": "query", "schema": PetSchema}]},
555 "post": {
556 "requestBody": {
557 "description": "a pet schema",
558 "required": True,
559 "content": {"application/json": {"schema": PetSchema}},
560 }
561 },
562 }
563 )
564 p = get_paths(spec_fixture.spec)["/pet"]
565 c = p["post"]["callbacks"]["petEvent"]["petCallbackUrl"]
566 get = c["get"]
567 assert get["parameters"] == spec_fixture.openapi.schema2parameters(
568 PetSchema(), location="query"
569 )
570 for parameter in get["parameters"]:
571 description = parameter.get("description", False)
572 assert description
573 name = parameter["name"]
574 assert description == PetSchema.description[name]
575 post = c["post"]
576 post_schema = spec_fixture.marshmallow_plugin.resolver.resolve_schema_dict(
577 PetSchema
578 )
579 assert (
580 post["requestBody"]["content"]["application/json"]["schema"] == post_schema
581 )
582 assert post["requestBody"]["description"] == "a pet schema"
583 assert post["requestBody"]["required"]
584
488585 @pytest.mark.parametrize("spec_fixture", ("2.0",), indirect=True)
489586 def test_schema_uses_ref_if_available_v2(self, spec_fixture):
490587 spec_fixture.spec.components.schema("Pet", schema=PetSchema)
510607 },
511608 )
512609 get = get_paths(spec_fixture.spec)["/pet"]["get"]
610 assert get["responses"]["200"]["content"]["application/json"][
611 "schema"
612 ] == build_ref(spec_fixture.spec, "schema", "Pet")
613
614 @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
615 def test_callback_schema_uses_ref_if_available_v3(self, make_pet_callback_spec):
616 spec_fixture = make_pet_callback_spec(
617 {
618 "get": {
619 "responses": {
620 "200": {"content": {"application/json": {"schema": PetSchema}}}
621 }
622 }
623 }
624 )
625 p = get_paths(spec_fixture.spec)["/pet"]
626 c = p["post"]["callbacks"]["petEvent"]["petCallbackUrl"]
627 get = c["get"]
513628 assert get["responses"]["200"]["content"]["application/json"][
514629 "schema"
515630 ] == build_ref(spec_fixture.spec, "schema", "Pet")
606721 in get["responses"]["200"]["content"]["application/json"]["schema"]
607722 )
608723
724 def test_callback_schema_uses_ref_if_available_name_resolver_returns_none_v3(self):
725 def resolver(schema):
726 return None
727
728 spec = APISpec(
729 title="Test auto-reference",
730 version="0.1",
731 openapi_version="3.0.0",
732 plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
733 )
734 spec.components.schema("Pet", schema=PetSchema)
735 spec.path(
736 path="/pet",
737 operations={
738 "post": {
739 "callbacks": {
740 "petEvent": {
741 "petCallbackUrl": {
742 "get": {
743 "responses": {
744 "200": {
745 "content": {
746 "application/json": {
747 "schema": PetSchema
748 }
749 }
750 }
751 }
752 }
753 }
754 }
755 }
756 }
757 },
758 )
759 p = get_paths(spec)["/pet"]
760 c = p["post"]["callbacks"]["petEvent"]["petCallbackUrl"]
761 get = c["get"]
762 assert get["responses"]["200"]["content"]["application/json"][
763 "schema"
764 ] == build_ref(spec, "schema", "Pet")
765
609766 @pytest.mark.parametrize("spec_fixture", ("2.0",), indirect=True)
610767 def test_schema_uses_ref_in_parameters_and_request_body_if_available_v2(
611768 self, spec_fixture
645802 p = get_paths(spec_fixture.spec)["/pet"]
646803 assert "schema" in p["get"]["parameters"][0]
647804 post = p["post"]
805 schema_ref = post["requestBody"]["content"]["application/json"]["schema"]
806 assert schema_ref == build_ref(spec_fixture.spec, "schema", "Pet")
807
808 @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
809 def test_callback_schema_uses_ref_in_parameters_and_request_body_if_available_v3(
810 self, make_pet_callback_spec
811 ):
812 spec_fixture = make_pet_callback_spec(
813 {
814 "get": {"parameters": [{"in": "query", "schema": PetSchema}]},
815 "post": {
816 "requestBody": {
817 "content": {"application/json": {"schema": PetSchema}}
818 }
819 },
820 }
821 )
822 p = get_paths(spec_fixture.spec)["/pet"]
823 c = p["post"]["callbacks"]["petEvent"]["petCallbackUrl"]
824 assert "schema" in c["get"]["parameters"][0]
825 post = c["post"]
648826 schema_ref = post["requestBody"]["content"]["application/json"]["schema"]
649827 assert schema_ref == build_ref(spec_fixture.spec, "schema", "Pet")
650828
720898 ]
721899 assert response_schema == resolved_schema
722900
901 @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
902 def test_callback_schema_array_uses_ref_if_available_v3(
903 self, make_pet_callback_spec
904 ):
905 spec_fixture = make_pet_callback_spec(
906 {
907 "get": {
908 "parameters": [
909 {
910 "name": "Pet",
911 "in": "query",
912 "content": {
913 "application/json": {
914 "schema": {"type": "array", "items": PetSchema}
915 }
916 },
917 }
918 ],
919 "responses": {
920 "200": {
921 "content": {
922 "application/json": {
923 "schema": {"type": "array", "items": PetSchema}
924 }
925 }
926 }
927 },
928 }
929 }
930 )
931 p = get_paths(spec_fixture.spec)["/pet"]
932 c = p["post"]["callbacks"]["petEvent"]["petCallbackUrl"]
933 get = c["get"]
934 assert len(get["parameters"]) == 1
935 resolved_schema = {
936 "type": "array",
937 "items": build_ref(spec_fixture.spec, "schema", "Pet"),
938 }
939 request_schema = get["parameters"][0]["content"]["application/json"]["schema"]
940 assert request_schema == resolved_schema
941 response_schema = get["responses"]["200"]["content"]["application/json"][
942 "schema"
943 ]
944 assert response_schema == resolved_schema
945
723946 @pytest.mark.parametrize("spec_fixture", ("2.0",), indirect=True)
724947 def test_schema_partially_v2(self, spec_fixture):
725948 spec_fixture.spec.components.schema("Pet", schema=PetSchema)
7841007 },
7851008 }
7861009
1010 @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
1011 def test_callback_schema_partially_v3(self, make_pet_callback_spec):
1012 spec_fixture = make_pet_callback_spec(
1013 {
1014 "get": {
1015 "responses": {
1016 "200": {
1017 "content": {
1018 "application/json": {
1019 "schema": {
1020 "type": "object",
1021 "properties": {
1022 "mother": PetSchema,
1023 "father": PetSchema,
1024 },
1025 }
1026 }
1027 }
1028 }
1029 }
1030 }
1031 }
1032 )
1033 p = get_paths(spec_fixture.spec)["/pet"]
1034 c = p["post"]["callbacks"]["petEvent"]["petCallbackUrl"]
1035 get = c["get"]
1036 assert get["responses"]["200"]["content"]["application/json"]["schema"] == {
1037 "type": "object",
1038 "properties": {
1039 "mother": build_ref(spec_fixture.spec, "schema", "Pet"),
1040 "father": build_ref(spec_fixture.spec, "schema", "Pet"),
1041 },
1042 }
1043
7871044 def test_parameter_reference(self, spec_fixture):
7881045 if spec_fixture.spec.openapi_version.major < 3:
7891046 param = {"schema": PetSchema}
8181075
8191076 def test_schema_global_state_untouched_2parameters(self, spec_fixture):
8201077 assert get_nested_schema(RunSchema, "sample") is None
821 data = spec_fixture.openapi.schema2parameters(RunSchema)
1078 data = spec_fixture.openapi.schema2parameters(RunSchema, location="json")
8221079 json.dumps(data)
8231080 assert get_nested_schema(RunSchema, "sample") is None
1081
1082 def test_resolve_schema_dict_ref_as_string(self, spec):
1083 schema = {"schema": "PetSchema"}
1084 if spec.openapi_version.major >= 3:
1085 schema = {"content": {"application/json": schema}}
1086 spec.path("/pet/{petId}", operations={"get": {"responses": {"200": schema}}})
1087 resp = get_paths(spec)["/pet/{petId}"]["get"]["responses"]["200"]
1088 if spec.openapi_version.major < 3:
1089 schema = resp["schema"]
1090 else:
1091 schema = resp["content"]["application/json"]["schema"]
1092 assert schema == build_ref(spec, "schema", "PetSchema")
8241093
8251094
8261095 class TestCircularReference:
8821151 assert "default" not in props["numbers"]
8831152
8841153
885 @pytest.mark.skipif(
886 MARSHMALLOW_VERSION_INFO[0] < 3, reason="Values ignored in marshmallow 2"
887 )
8881154 class TestDictValues:
8891155 def test_dict_values_resolve_to_additional_properties(self, spec):
8901156 class SchemaWithDict(Schema):
22
33 import pytest
44 from marshmallow import fields, validate
5
6 from apispec.ext.marshmallow.openapi import MARSHMALLOW_VERSION_INFO
75
86 from .schemas import CategorySchema, CustomList, CustomStringField, CustomIntegerField
97 from .utils import build_ref
6058 @pytest.mark.parametrize(
6159 ("FieldClass", "expected_format"),
6260 [
63 (fields.Integer, "int32"),
64 (fields.Float, "float"),
6561 (fields.UUID, "uuid"),
6662 (fields.DateTime, "date-time"),
6763 (fields.Date, "date"),
9490
9591
9692 def test_datetime_field_with_missing(spec_fixture):
97 if MARSHMALLOW_VERSION_INFO[0] < 3:
98 field = fields.Date(missing=dt.date(2014, 7, 18).isoformat())
99 else:
100 field = fields.Date(missing=dt.date(2014, 7, 18))
93 field = fields.Date(missing=dt.date(2014, 7, 18))
10194 res = spec_fixture.openapi.field2property(field)
10295 assert res["default"] == dt.date(2014, 7, 18).isoformat()
10396
292285 assert properties["x-customString"] == (
293286 spec_fixture.openapi.openapi_version == "2.0"
294287 )
288
289
290 def test_field2property_with_non_string_metadata_keys(spec_fixture):
291 class _DesertSentinel:
292 pass
293
294 field = fields.Boolean(description="A description")
295 field.metadata[_DesertSentinel()] = "to be ignored"
296 result = spec_fixture.openapi.field2property(field)
297 assert result == {"description": "A description", "type": "boolean"}
00 import pytest
1
2 from marshmallow import fields, Schema, validate
1 from datetime import datetime
2
3 from marshmallow import fields, INCLUDE, RAISE, Schema, validate
34
45 from apispec.ext.marshmallow import MarshmallowPlugin
5 from apispec.ext.marshmallow.openapi import MARSHMALLOW_VERSION_INFO
66 from apispec import exceptions, utils, APISpec
77
88 from .schemas import CustomList, CustomStringField
1111
1212 class TestMarshmallowFieldToOpenAPI:
1313 def test_fields_with_missing_load(self, openapi):
14 field_dict = {"field": fields.Str(default="foo", missing="bar")}
15 res = openapi.fields2parameters(field_dict, default_in="query")
14 class MySchema(Schema):
15 field = fields.Str(default="foo", missing="bar")
16
17 res = openapi.schema2parameters(MySchema, location="query")
1618 if openapi.openapi_version.major < 3:
1719 assert res[0]["default"] == "bar"
1820 else:
1921 assert res[0]["schema"]["default"] == "bar"
20
21 def test_fields_with_location(self, openapi):
22 field_dict = {"field": fields.Str(location="querystring")}
23 res = openapi.fields2parameters(field_dict, default_in="headers")
24 assert res[0]["in"] == "query"
25
26 # json/body is invalid for OpenAPI 3
27 @pytest.mark.parametrize("openapi", ("2.0",), indirect=True)
28 def test_fields_with_multiple_json_locations(self, openapi):
29 field_dict = {
30 "field1": fields.Str(location="json", required=True),
31 "field2": fields.Str(location="json", required=True),
32 "field3": fields.Str(location="json"),
33 }
34 res = openapi.fields2parameters(field_dict, default_in=None)
35 assert len(res) == 1
36 assert res[0]["in"] == "body"
37 assert res[0]["required"] is False
38 assert "field1" in res[0]["schema"]["properties"]
39 assert "field2" in res[0]["schema"]["properties"]
40 assert "field3" in res[0]["schema"]["properties"]
41 assert "required" in res[0]["schema"]
42 assert len(res[0]["schema"]["required"]) == 2
43 assert "field1" in res[0]["schema"]["required"]
44 assert "field2" in res[0]["schema"]["required"]
45
46 def test_fields2parameters_does_not_modify_metadata(self, openapi):
47 field_dict = {"field": fields.Str(location="querystring")}
48 res = openapi.fields2parameters(field_dict, default_in="headers")
49 assert res[0]["in"] == "query"
50
51 res = openapi.fields2parameters(field_dict, default_in="headers")
52 assert res[0]["in"] == "query"
53
54 def test_fields_location_mapping(self, openapi):
55 field_dict = {"field": fields.Str(location="cookies")}
56 res = openapi.fields2parameters(field_dict, default_in="headers")
57 assert res[0]["in"] == "cookie"
58
59 def test_fields_default_location_mapping(self, openapi):
60 field_dict = {"field": fields.Str()}
61 res = openapi.fields2parameters(field_dict, default_in="headers")
62 assert res[0]["in"] == "header"
6322
6423 # json/body is invalid for OpenAPI 3
6524 @pytest.mark.parametrize("openapi", ("2.0",), indirect=True)
6827 id = fields.Int()
6928
7029 schema = ExampleSchema(many=True)
71 res = openapi.schema2parameters(schema=schema, default_in="json")
30 res = openapi.schema2parameters(schema=schema, location="json")
7231 assert res[0]["in"] == "body"
7332
7433 def test_fields_with_dump_only(self, openapi):
7534 class UserSchema(Schema):
7635 name = fields.Str(dump_only=True)
7736
78 res = openapi.fields2parameters(UserSchema._declared_fields, default_in="query")
37 res = openapi.schema2parameters(schema=UserSchema(), location="query")
7938 assert len(res) == 0
80 res = openapi.fields2parameters(UserSchema().fields, default_in="query")
39
40 class UserSchema(Schema):
41 name = fields.Str()
42
43 class Meta:
44 dump_only = ("name",)
45
46 res = openapi.schema2parameters(schema=UserSchema(), location="query")
8147 assert len(res) == 0
8248
83 class UserSchema(Schema):
84 name = fields.Str()
85
86 class Meta:
87 dump_only = ("name",)
88
89 res = openapi.schema2parameters(schema=UserSchema, default_in="query")
90 assert len(res) == 0
91
9249
9350 class TestMarshmallowSchemaToModelDefinition:
94 def test_invalid_schema(self, openapi):
95 with pytest.raises(ValueError):
96 openapi.schema2jsonschema(None)
97
9851 def test_schema2jsonschema_with_explicit_fields(self, openapi):
9952 class UserSchema(Schema):
10053 _id = fields.Int()
11366 assert props["email"]["format"] == "email"
11467 assert props["email"]["description"] == "email address of the user"
11568
116 @pytest.mark.skipif(
117 MARSHMALLOW_VERSION_INFO[0] >= 3, reason="Behaviour changed in marshmallow 3"
118 )
119 def test_schema2jsonschema_override_name_ma2(self, openapi):
120 class ExampleSchema(Schema):
121 _id = fields.Int(load_from="id", dump_to="id")
122 _dt = fields.Int(load_from="lf_no_match", dump_to="dt")
123 _lf = fields.Int(load_from="lf")
124 _global = fields.Int(load_from="global", dump_to="global")
125
126 class Meta:
127 exclude = ("_global",)
128
129 res = openapi.schema2jsonschema(ExampleSchema)
130 assert res["type"] == "object"
131 props = res["properties"]
132 # `_id` renamed to `id`
133 assert "_id" not in props and props["id"]["type"] == "integer"
134 # `load_from` and `dump_to` do not match, `dump_to` is used
135 assert "lf_no_match" not in props
136 assert props["dt"]["type"] == "integer"
137 # `load_from` and no `dump_to`, `load_from` is used
138 assert props["lf"]["type"] == "integer"
139 # `_global` excluded correctly
140 assert "_global" not in props and "global" not in props
141
142 @pytest.mark.skipif(
143 MARSHMALLOW_VERSION_INFO[0] < 3, reason="Behaviour changed in marshmallow 3"
144 )
145 def test_schema2jsonschema_override_name_ma3(self, openapi):
69 def test_schema2jsonschema_override_name(self, openapi):
14670 class ExampleSchema(Schema):
14771 _id = fields.Int(data_key="id")
14872 _global = fields.Int(data_key="global")
206130
207131 res = openapi.schema2jsonschema(WhiteStripesSchema)
208132 assert set(res["properties"].keys()) == {"guitarist", "drummer"}
133
134 def test_unknown_values_disallow(self, openapi):
135 class UnknownRaiseSchema(Schema):
136 class Meta:
137 unknown = RAISE
138
139 first = fields.Str()
140
141 res = openapi.schema2jsonschema(UnknownRaiseSchema)
142 assert res["additionalProperties"] is False
143
144 def test_unknown_values_allow(self, openapi):
145 class UnknownIncludeSchema(Schema):
146 class Meta:
147 unknown = INCLUDE
148
149 first = fields.Str()
150
151 res = openapi.schema2jsonschema(UnknownIncludeSchema)
152 assert res["additionalProperties"] is True
209153
210154 def test_only_explicitly_declared_fields_are_translated(self, openapi):
211155 class UserSchema(Schema):
226170 assert "email" not in props
227171
228172 def test_observed_field_name_for_required_field(self, openapi):
229 if MARSHMALLOW_VERSION_INFO[0] < 3:
230 fields_dict = {
231 "user_id": fields.Int(load_from="id", dump_to="id", required=True)
232 }
233 else:
234 fields_dict = {"user_id": fields.Int(data_key="id", required=True)}
235
173 fields_dict = {"user_id": fields.Int(data_key="id", required=True)}
236174 res = openapi.fields2jsonschema(fields_dict)
237175 assert res["required"] == ["id"]
238176
260198 class TestMarshmallowSchemaToParameters:
261199 @pytest.mark.parametrize("ListClass", [fields.List, CustomList])
262200 def test_field_multiple(self, ListClass, openapi):
263 field = ListClass(fields.Str, location="querystring")
264 res = openapi.field2parameter(field, name="field", default_in=None)
201 field = ListClass(fields.Str)
202 res = openapi._field2parameter(field, name="field", location="query")
265203 assert res["in"] == "query"
266204 if openapi.openapi_version.major < 3:
267205 assert res["type"] == "array"
274212 assert res["explode"] is True
275213
276214 def test_field_required(self, openapi):
277 field = fields.Str(required=True, location="query")
278 res = openapi.field2parameter(field, name="field", default_in=None)
215 field = fields.Str(required=True)
216 res = openapi._field2parameter(field, name="field", location="query")
279217 assert res["required"] is True
280
281 def test_invalid_schema(self, openapi):
282 with pytest.raises(ValueError):
283 openapi.schema2parameters(None)
284218
285219 # json/body is invalid for OpenAPI 3
286220 @pytest.mark.parametrize("openapi", ("2.0",), indirect=True)
289223 name = fields.Str()
290224 email = fields.Email()
291225
292 res = openapi.schema2parameters(UserSchema, default_in="body")
226 res = openapi.schema2parameters(UserSchema, location="body")
293227 assert len(res) == 1
294228 param = res[0]
295229 assert param["in"] == "body"
302236 name = fields.Str()
303237 email = fields.Email(dump_only=True)
304238
305 res_nodump = openapi.schema2parameters(UserSchema, default_in="body")
239 res_nodump = openapi.schema2parameters(UserSchema, location="body")
306240 assert len(res_nodump) == 1
307241 param = res_nodump[0]
308242 assert param["in"] == "body"
315249 name = fields.Str()
316250 email = fields.Email()
317251
318 res = openapi.schema2parameters(UserSchema(many=True), default_in="body")
252 res = openapi.schema2parameters(UserSchema(many=True), location="body")
319253 assert len(res) == 1
320254 param = res[0]
321255 assert param["in"] == "body"
327261 name = fields.Str()
328262 email = fields.Email()
329263
330 res = openapi.schema2parameters(UserSchema, default_in="query")
264 res = openapi.schema2parameters(UserSchema, location="query")
331265 assert len(res) == 2
332266 res.sort(key=lambda param: param["name"])
333267 assert res[0]["name"] == "email"
340274 name = fields.Str()
341275 email = fields.Email()
342276
343 res = openapi.schema2parameters(UserSchema(), default_in="query")
277 res = openapi.schema2parameters(UserSchema(), location="query")
344278 assert len(res) == 2
345279 res.sort(key=lambda param: param["name"])
346280 assert res[0]["name"] == "email"
354288 email = fields.Email()
355289
356290 with pytest.raises(AssertionError):
357 openapi.schema2parameters(UserSchema(many=True), default_in="query")
291 openapi.schema2parameters(UserSchema(many=True), location="query")
358292
359293 def test_fields_query(self, openapi):
360 field_dict = {"name": fields.Str(), "email": fields.Email()}
361 res = openapi.fields2parameters(field_dict, default_in="query")
294 class MySchema(Schema):
295 name = fields.Str()
296 email = fields.Email()
297
298 res = openapi.schema2parameters(MySchema, location="query")
362299 assert len(res) == 2
363300 res.sort(key=lambda param: param["name"])
364301 assert res[0]["name"] == "email"
476413 "required": True,
477414 "type": "string",
478415 },
479 openapi.field2parameter(
416 openapi._field2parameter(
480417 field=fields.List(
481 fields.Str(),
482 validate=validate.OneOf(["freddie", "roger"]),
483 location="querystring",
418 fields.Str(), validate=validate.OneOf(["freddie", "roger"]),
484419 ),
485 default_in=None,
420 location="query",
486421 name="body",
487422 ),
488423 ]
489 + openapi.schema2parameters(PageSchema, default_in="query"),
424 + openapi.schema2parameters(PageSchema, location="query"),
490425 "responses": {200: {"schema": PetSchema, "description": "A pet"}},
491426 },
492427 "post": {
499434 "type": "string",
500435 }
501436 ]
502 + openapi.schema2parameters(CategorySchema, default_in="body")
437 + openapi.schema2parameters(CategorySchema, location="body")
503438 ),
504439 "responses": {201: {"schema": PetSchema, "description": "A pet"}},
505440 },
534469 "required": True,
535470 "schema": {"type": "string"},
536471 },
537 openapi.field2parameter(
472 openapi._field2parameter(
538473 field=fields.List(
539 fields.Str(),
540 validate=validate.OneOf(["freddie", "roger"]),
541 location="querystring",
474 fields.Str(), validate=validate.OneOf(["freddie", "roger"]),
542475 ),
543 default_in=None,
476 location="query",
544477 name="body",
545478 ),
546479 ]
547 + openapi.schema2parameters(PageSchema, default_in="query"),
480 + openapi.schema2parameters(PageSchema, location="query"),
548481 "responses": {
549482 200: {
550483 "description": "success",
585518 class ValidationSchema(Schema):
586519 id = fields.Int(dump_only=True)
587520 range = fields.Int(validate=validate.Range(min=1, max=10))
521 range_no_upper = fields.Float(validate=validate.Range(min=1))
588522 multiple_ranges = fields.Int(
589523 validate=[
590524 validate.Range(min=1),
610544 equal_length = fields.Str(
611545 validate=[validate.Length(equal=5), validate.Length(min=1, max=10)]
612546 )
547 date_range = fields.DateTime(validate=validate.Range(min=datetime(1900, 1, 1),))
613548
614549 @pytest.mark.parametrize(
615550 ("field", "properties"),
616551 [
617552 ("range", {"minimum": 1, "maximum": 10}),
553 ("range_no_upper", {"minimum": 1}),
618554 ("multiple_ranges", {"minimum": 3, "maximum": 7}),
619555 ("list_length", {"minItems": 1, "maxItems": 10}),
620556 ("custom_list_length", {"minItems": 1, "maxItems": 10}),
622558 ("custom_field_length", {"minLength": 1, "maxLength": 10}),
623559 ("multiple_lengths", {"minLength": 3, "maxLength": 7}),
624560 ("equal_length", {"minLength": 5, "maxLength": 5}),
561 ("date_range", {"x-minimum": datetime(1900, 1, 1)}),
625562 ],
626563 )
627564 def test_properties(self, field, properties, spec):
2020 return spec.to_dict()["components"]["parameters"]
2121
2222
23 def get_headers(spec):
24 if spec.openapi_version.major < 3:
25 return spec.to_dict()["headers"]
26 return spec.to_dict()["components"]["headers"]
27
28
2329 def get_examples(spec):
2430 return spec.to_dict()["components"]["examples"]
2531
+0
-36
tox.ini less more
0 [tox]
1 envlist=
2 lint
3 py{35,36,37,38}-marshmallow2
4 py{35,36,37,38}-marshmallow3
5 py38-marshmallowdev
6 docs
7
8 [testenv]
9 extras = tests
10 deps =
11 marshmallow2: marshmallow>=2.0.0,<3.0.0
12 marshmallow3: marshmallow>=3.0.0,<4.0.0
13 marshmallowdev: https://github.com/marshmallow-code/marshmallow/archive/dev.tar.gz
14 commands = pytest {posargs}
15
16 [testenv:lint]
17 deps = pre-commit~=2.4
18 skip_install = true
19 commands = pre-commit run --all-files --show-diff-on-failure
20
21 [testenv:docs]
22 extras = docs
23 commands = sphinx-build docs/ docs/_build {posargs}
24
25 ; Below tasks are for development only (not run in CI)
26
27 [testenv:watch-docs]
28 deps = sphinx-autobuild
29 extras = docs
30 commands = sphinx-autobuild --open-browser docs/ docs/_build {posargs} -z src/apispec -s 2
31
32 [testenv:watch-readme]
33 deps = restview
34 skip_install = true
35 commands = restview README.rst