pax_global_header00006660000000000000000000000064145626440450014524gustar00rootroot0000000000000052 comment=e62e2986b66465e785a7ebd8905d639f1893dedc openapi-0.8.4/000077500000000000000000000000001456264404500131705ustar00rootroot00000000000000openapi-0.8.4/.github/000077500000000000000000000000001456264404500145305ustar00rootroot00000000000000openapi-0.8.4/.github/workflows/000077500000000000000000000000001456264404500165655ustar00rootroot00000000000000openapi-0.8.4/.github/workflows/tests.yml000066400000000000000000000035331456264404500204560ustar00rootroot00000000000000name: Tests on: - push - pull_request jobs: pre-commit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python 3.11 uses: actions/setup-python@v4 with: python-version: '3.11' - name: Run pre-commit run: | python -m pip install tox python -m tox -e pre-commit pytest: strategy: matrix: os: [ubuntu-latest, macos-latest] python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Pull OpenAPI specs for tests run: | git submodule update --init --recursive - name: Run pytest run: | python -m pip install tox python -m tox -e py docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python 3.11 uses: actions/setup-python@v4 with: python-version: '3.11' - name: Run sphinx run: | python -m pip install tox python -m tox -e docs release: runs-on: ubuntu-latest if: github.event_name == 'push' steps: - uses: actions/checkout@v3 - name: Set up Python 3.11 uses: actions/setup-python@v4 with: python-version: '3.11' - name: Prepare artifacts run: | python -m pip install build python -m build - name: Publish to PyPI if: startsWith(github.ref, 'refs/tags') env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} run: | python -m pip install twine python -m twine upload dist/* openapi-0.8.4/.gitignore000066400000000000000000000012311456264404500151550ustar00rootroot00000000000000# Backup files *.~ .idea/ # Byte-compiles / optimizied __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging bin/ build/ develop-eggs/ dist/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg MANIFEST # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports .tox/ .coverage .cache nosetests.xml coverage.xml # Translations *.mo # Sphinx documentation docs/_build/ # Ignore sublime's project (for fans) .ropeproject/ *.sublime-project *.sublime-workspace # Ignore VSCode folder /.vscode/ # Ignore virtualenvs (who places it near) .venv/ # Various shit from the OS itself .DS_Store openapi-0.8.4/.gitmodules000066400000000000000000000002001456264404500153350ustar00rootroot00000000000000[submodule "OpenAPI-Specification"] path = tests/OpenAPI-Specification url = https://github.com/OAI/OpenAPI-Specification.git openapi-0.8.4/.mailmap000066400000000000000000000000731456264404500146110ustar00rootroot00000000000000Ihor Kalnytskyi openapi-0.8.4/.pre-commit-config.yaml000066400000000000000000000024621456264404500174550ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer exclude: > (?x)( tests/renderers/httpdomain/rendered ) - id: check-docstring-first - id: check-json - id: check-yaml - id: debug-statements - repo: https://github.com/psf/black rev: 23.10.0 hooks: - id: black args: [--line-length=88, --target-version=py35] exclude: > (?x)( # black defaults \.eggs | \.git\ | \.hg | \.mypy_cache | \.nox | \.tox | \.venv | _build | buck-out | build | dist # sphinxcontrib-openapi | sphinxcontrib/openapi/__init__.py | sphinxcontrib/openapi/__main__.py | sphinxcontrib/openapi/openapi20.py | sphinxcontrib/openapi/openapi30.py | sphinxcontrib/openapi/directive.py | sphinxcontrib/openapi/utils.py | tests/test_openapi.py | tests/conftest.py | tests/test_spec_examples.py ) - repo: https://github.com/pycqa/flake8 rev: 6.1.0 hooks: - id: flake8 openapi-0.8.4/CHANGES000066400000000000000000000051661456264404500141730ustar00rootroot00000000000000========= Changes ========= Here you can see the list of changes between each 'sphinxcontrib-openapi' release. 0.8.4 (2024-02-13) ================== - Address issue with path excludes. - Fix namespace ``sphinxcontrib`` to be usable with ``zc.buildout``. 0.8.3 (2023-10-24) ================== - Address issue with remote spec references. 0.8.2 (2023-10-24) ================== - Address issues with nullable types in OpenAPI 3.1. 0.8.1 (2023-01-24) ================== - Add support for OpenAPI 3.1 type arrays. 0.8.0 (2023-01-13) ================== - Add initial support for OpenAPI 3.1. - Replace dependency on abandoned ``m2r`` dependency. - Add support for recursive schemas. - Add support for Python 3.10, 3.11 0.7.0 (2020-05-07) ================== - Drop Python 2.7 support because it reached its end-of-life. - Add support for examples defined in schema. - Add support for 'anyOf' during example generation based on schema. - Fix loading $ref in other YAML files. - Fix freeforom (property-less) obhect schema rendering. - Respect RFC7807 in example generation. - Add new experimental OpenAPI v3 renderer. 0.6.0 (2019-12-20) ================== - Add ``include`` and ``exclude`` options to exclude certain paths out from rendering. - Generate samples for ``GET`` requests. - When rendering by groups, preserve order of groups specified in the spec. 0.5.0 (2019-09-04) ================== - Fix example generation when a byte string is used in a schema. - Support CommonMark in spec where applied. ``m2r`` package is required. - Show "required" marker near required query parameters. - Show request body properties for OpenAPI v2 specs. - CLI command to render OpenAPI spec as reStructuredText is added. 0.4.0 (2019-01-17) ================== - Add OpenAPI 3.x support. - Drop Python 3.3 support because it reached its end-of-life. - Add Python 3.7 support. - Add sample request/response generation from schema for OpenAPI v3. - Add grouping by OpenAPI tags. 0.3.2 (2017-10-05) ================== - Do not crash on Python 2.x if non-ASCII character is met. 0.3.1 (2017-05-19) ================== - Make 'sphinxcontrib-openapi' compatible with Sphinx 1.6. 0.3.0 (2017-01-29) ================== - Add ``:paths:`` option to ``openapi`` directive that allows to render only specified endpoints. - Resolve references inside list objects. - Handle common ``parameters` specified at the endpoint's root. 0.2.1 (2017-01-10) ================== - Resolve relative JSON references. 0.2.0 (2016-11-16) ================== - Resolve JSON references in the spec before attempt to generate a doc. 0.1.0 (2016-10-03) ================== - First public release. openapi-0.8.4/LICENSE000066400000000000000000000024471456264404500142040ustar00rootroot00000000000000Copyright (c) 2016, Igor Kalnitsky . All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. openapi-0.8.4/MANIFEST.in000066400000000000000000000003261456264404500147270ustar00rootroot00000000000000include README.rst LICENSE CHANGES include tox.ini recursive-include docs * recursive-include tests * prune docs/_build recursive-exclude * __pycache__ recursive-exclude * *.py[co] recursive-exclude * .DS_Store openapi-0.8.4/README.rst000066400000000000000000000020671456264404500146640ustar00rootroot00000000000000===================== sphinxcontrib-openapi ===================== **sphinxcontrib-openapi** is a `Sphinx`_ extension to generate APIs docs from `OpenAPI`_ (fka Swagger) spec. It depends on `sphinxcontrib-httpdomain`_ that provides an HTTP domain for describing RESTful HTTP APIs, so we don't need to reinvent the wheel. .. code:: bash $ python3 -m pip install sphinxcontrib-openapi Usage ===== Pass ``sphinxcontrib-openapi`` to ``extensions`` list in Sphinx's ``conf.py`` .. code:: python extensions = [ ... 'sphinxcontrib.openapi', ] and feel free to use the ``openapi`` directive to render OpenAPI specs .. code:: restructuredtext .. openapi:: path/to/openapi.yml Links ===== * Documentation: https://sphinxcontrib-openapi.readthedocs.org/ * Source: https://github.com/sphinx-contrib/openapi * Bugs: https://github.com/sphinx-contrib/openapi/issues .. _Sphinx: https://www.sphinx-doc.org/en/master/ .. _OpenAPI: https://github.com/OAI/OpenAPI-Specification .. _sphinxcontrib-httpdomain: https://sphinxcontrib-httpdomain.readthedocs.io/ openapi-0.8.4/docs/000077500000000000000000000000001456264404500141205ustar00rootroot00000000000000openapi-0.8.4/docs/conf.py000066400000000000000000000014131456264404500154160ustar00rootroot00000000000000import os # Unfortunately, Sphinx doesn't support code highlighting for standard # reStructuredText `code` directive. So let's register 'code' directive # as alias for Sphinx's own implementation. # # https://github.com/sphinx-doc/sphinx/issues/2155 from docutils.parsers.rst import directives from sphinx.directives.code import CodeBlock directives.register_directive("code", CodeBlock) project = "sphinxcontrib-openapi" copyright = "2016, Ihor Kalnytskyi" extensions = ["sphinxcontrib.openapi"] source_suffix = ".rst" master_doc = "index" exclude_patterns = ["_build"] pygments_style = "default" if not os.environ.get("READTHEDOCS") == "True": import sphinx_rtd_theme html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] openapi-0.8.4/docs/index.rst000066400000000000000000000070221456264404500157620ustar00rootroot00000000000000===================== sphinxcontrib-openapi ===================== .. hint:: Check out `sphinxcontrib-redoc`_ if you are interested in separate three-panel OpenAPI spec rendering. **sphinxcontrib-openapi** is a `Sphinx`_ extension to generate APIs docs from `OpenAPI`_ (fka Swagger) spec. It depends on `sphinxcontrib-httpdomain`_ that provides an HTTP domain for describing RESTful HTTP APIs, so we don't need to reinvent the wheel. .. code:: bash pip install sphinxcontrib-openapi How To Use? =========== Consider you have the following OpenAPI spec saved at ``specs/openapi.yml``: .. literalinclude:: specs/openapi.yml :language: yaml You can render it by using the ``openapi`` directive: .. code:: restructuredtext .. openapi:: specs/openapi.yml and it will be rendered into something like: .. openapi:: specs/openapi.yml Options ======= The ``openapi`` directive supports the following options: ``encoding`` Encoding to be used to read an OpenAPI spec. If not passed, Sphinx's source encoding will be used. ``paths`` A comma separated list of paths to filter the included OpenAPI spec by. For example: .. code:: restructuredtext .. openapi:: specs/openapi.yml :paths: /persons /evidence :encoding: utf-8 Would only render the endpoints at ``/persons`` and ``/evidence``, ignoring all others. ``examples`` If passed, both request and response examples will be rendered. Please note, if examples are not provided in a spec, they will be generated by internal logic based on a corresponding schema. ``group`` If passed, paths will be grouped by tags. If a path has no tag assigned, it will be grouped in a ``default`` group. ``format`` The format of text in the spec, either ``rst`` or ``markdown``. If not supplied, ReStructured Text is assumed. ``include`` A line separated list of regular expressions to filter the included openapi spec by. For example: .. code:: restructuredtext .. openapi:: specs/openapi.yml :include: /evid.* :encoding: utf-8 Would render the endpoints at ``/evidence`` and ``/evidence/{pk}`` ``exclude`` A line separated list of regular expressions to filter the included openapi spec by (excluding matches). For example: .. code:: restructuredtext .. openapi:: specs/openapi.yml :exclude: /evidence/{pk} :encoding: utf-8 Would render ``/persons`` and ``/evidence`` endpoints, but not ``/evidence/{pk}`` endpoints ``methods`` A line separated list of http methods to filter included openapi spec. For example: .. code:: restructuredtext .. openapi:: specs/openapi.yml :methods: get post put :encoding: utf-8 Would render paths with get, post or put method ``exclude``, ``include`` and ``paths`` can also be used together (``exclude`` taking precedence over ``include`` and ``paths``) ``http-methods-order`` A whitespace delimited list of HTTP methods to render first. For example: .. code:: restructuredtext .. openapi:: specs/openapi.yml :http-methods-order: head get Would render the ``head`` method, followed by the ``get`` method, followed by the rest of the methods in their declared ordered. .. _Sphinx: https://www.sphinx-doc.org/en/master/ .. _OpenAPI: https://github.com/OAI/OpenAPI-Specification .. _sphinxcontrib-httpdomain: https://sphinxcontrib-httpdomain.readthedocs.io/ .. _sphinxcontrib-redoc: https://sphinxcontrib-redoc.readthedocs.io/ openapi-0.8.4/docs/specs/000077500000000000000000000000001456264404500152355ustar00rootroot00000000000000openapi-0.8.4/docs/specs/openapi.yml000066400000000000000000000070331456264404500174160ustar00rootroot00000000000000swagger: "2.0" info: title: Batcomputer API version: "1.0.0" host: api.batcomputer.com paths: /persons: get: summary: List Persons description: | Retrieves a list of all persons on file in the bat computer. responses: 200: description: An array of Persons schema: type: array items: $ref: '#/definitions/Person' /evidence: get: summary: List Evidence description: | Retrieves a list of evidence ever found by world's greatest detective and his family. parameters: - name: marker in: query type: integer minimum: -1 default: -1 format: int64 required: false description: | The id of the last seen evidence. It's used for pagination purpose by returning 'limit' number of evidence after this one. - name: limit in: query type: integer format: int32 minimum: 1 maximum: 1000 default: 20 required: false description: | The maximum number of evidence to be returned by the query. responses: 200: description: An array of evidence. schema: type: array items: $ref: '#/definitions/Evidence' post: summary: Create an Evidence description: | Creates a new evidence record in database. parameters: - name: evidence in: body schema: $ref: '#/definitions/Evidence' responses: 201: description: An evidence. schema: $ref: '#/definitions/Evidence' /evidence/{id}: get: summary: Show Requested Evidence description: | Queries and returns an evidence with a passed ID. parameters: - name: id in: path type: integer format: int64 required: true description: | A unique evidence identifier to query. - name: If-None-Match in: header type: string description: | Executes a request only if passed ETag doesn't equal to current resource one (i.e. resource has been changed). responses: 200: description: An evidence. schema: $ref: '#/definitions/Evidence' headers: ETag: description: | Entity tag is part of HTTP provided for web cache validation problem, but also used for optimistic concurrency control. type: string 404: description: Evidence not found. schema: $ref: '#/definitions/Error' definitions: Evidence: type: object properties: id: type: integer format: int64 description: A unique evidence identifier. case: type: string description: A case when the evidence is found. data: type: string format: binary description: An evidence itself. Error: type: object properties: code: type: string description: A uniqe identifier of error. message: type: string description: A human readable error message. Person: type: object properties: id: type: integer format: int64 description: Unique ID for a person name: type: string description: Name of a person openapi-0.8.4/setup.py000077500000000000000000000036421456264404500147120ustar00rootroot00000000000000#!/usr/bin/env python import os from setuptools import setup, find_namespace_packages here = os.path.dirname(__file__) with open(os.path.join(here, "README.rst"), "r", encoding="utf-8") as f: long_description = f.read() setup( name="sphinxcontrib-openapi", description="OpenAPI (fka Swagger) spec renderer for Sphinx", long_description=long_description, license="BSD", url="https://github.com/sphinx-contrib/openapi", keywords="sphinx openapi swagger rest api renderer docs", author="Ihor Kalnytskyi", author_email="ihor@kalnytskyi.com", packages=find_namespace_packages(include=["sphinxcontrib.*"]), include_package_data=True, zip_safe=False, use_scm_version={"root": here}, setup_requires=["setuptools_scm >= 1.15"], install_requires=[ "sphinx >= 2.0", "sphinxcontrib-httpdomain >= 1.5.0", "PyYAML >= 3.12", "jsonschema >= 2.5.1", "sphinx-mdinclude >= 0.5.2", "picobox >= 2.2", "deepmerge >= 0.1", "importlib-metadata; python_version < '3.8'", ], project_urls={ "Documentation": "https://sphinxcontrib-openapi.readthedocs.io/", "Source": "https://github.com/sphinx-contrib/openapi", "Bugs": "https://github.com/sphinx-contrib/openapi/issues", }, classifiers=[ "Topic :: Documentation", "Topic :: Documentation :: Sphinx", "License :: OSI Approved :: BSD License", "Environment :: Console", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython", "Framework :: Setuptools Plugin", "Framework :: Sphinx", "Framework :: Sphinx :: Extension", ], python_requires=">=3.7", ) openapi-0.8.4/sphinxcontrib/000077500000000000000000000000001456264404500160625ustar00rootroot00000000000000openapi-0.8.4/sphinxcontrib/openapi/000077500000000000000000000000001456264404500175155ustar00rootroot00000000000000openapi-0.8.4/sphinxcontrib/openapi/__init__.py000066400000000000000000000053141456264404500216310ustar00rootroot00000000000000""" sphinxcontrib.openapi --------------------- The OpenAPI spec renderer for Sphinx. It's a new way to document your RESTful API. Based on ``sphinxcontrib-httpdomain``. :copyright: (c) 2016, Ihor Kalnytskyi. :license: BSD, see LICENSE for details. """ try: from importlib.metadata import distribution, PackageNotFoundError except ImportError: # python < 3.8 from importlib_metadata import distribution, PackageNotFoundError from sphinxcontrib.openapi import renderers, directive try: __version__ = distribution(__name__).version except PackageNotFoundError: # package is not installed __version__ = None _BUILTIN_RENDERERS = { "httpdomain": renderers.HttpdomainRenderer, "httpdomain:old": renderers.HttpdomainOldRenderer, } _DEFAULT_RENDERER_NAME = "httpdomain:old" def _register_rendering_directives(app, conf): """Register rendering directives based on effective configuration.""" renderers_map = dict(_BUILTIN_RENDERERS, **conf.openapi_renderers) for renderer_name, renderer_cls in renderers_map.items(): app.add_directive( "openapi:%s" % renderer_name, directive.create_directive_from_renderer(renderer_cls), ) if conf.openapi_default_renderer not in renderers_map: raise ValueError( "invalid 'openapi_default_renderer' value: " "no such renderer: '%s'" % conf.openapi_default_renderer ) app.add_directive( "openapi", directive.create_directive_from_renderer( renderers_map[conf.openapi_default_renderer] ), ) def setup(app): app.add_config_value("openapi_default_renderer", _DEFAULT_RENDERER_NAME, "html") app.add_config_value("openapi_renderers", {}, "html") from sphinxcontrib import httpdomain for idx, fieldtype in enumerate(httpdomain.HTTPResource.doc_field_types): if fieldtype.name == 'requestheader': httpdomain.HTTPResource.doc_field_types[idx] = httpdomain.TypedField( fieldtype.name, label=fieldtype.label, names=fieldtype.names, typerolename='header', typenames=('reqheadertype', ), ) if fieldtype.name == 'responseheader': httpdomain.HTTPResource.doc_field_types[idx] = httpdomain.TypedField( fieldtype.name, label=fieldtype.label, names=fieldtype.names, typerolename='header', typenames=('resheadertype', ), ) app.setup_extension("sphinxcontrib.httpdomain") app.connect("config-inited", _register_rendering_directives) return {"version": __version__, "parallel_read_safe": True} openapi-0.8.4/sphinxcontrib/openapi/__main__.py000066400000000000000000000036531456264404500216160ustar00rootroot00000000000000import argparse import logging from sphinxcontrib.openapi import directive, renderers def main(): parser = argparse.ArgumentParser( prog='oas2rst', description='Export OpenAPI Specification files to reStructuredText \ files') parser.add_argument( "-l", "--level", action='store', default=logging.INFO, dest='level', help="Logging level") parser.add_argument( "-e", "--encoding", action='store', default="UTF-8", dest='encoding', help="Source file encoding") parser.add_argument( "-p", "--paths", action='append', dest='paths', help="Endpoints to be rendered") parser.add_argument( "-x", "--examples", action='store_true', dest='examples', help="Include examples") parser.add_argument( "-g", "--group", action='store_true', dest='group', help="Group paths by tag") parser.add_argument( "-i", "--input", dest='input', required=True, help="Input file") parser.add_argument( "-o", "--output", type=argparse.FileType('w'), required=True, dest='output', help="Output file") options = parser.parse_args() logging.getLogger().setLevel(options.level) openapi_options = {} if options.paths: openapi_options['paths'] = options.paths if options.examples: openapi_options['examples'] = True if options.group: openapi_options['group'] = True openapi_options.setdefault('uri', 'file://%s' % options.input) spec = directive._get_spec(options.input, options.encoding) renderer = renderers.HttpdomainOldRenderer(None, openapi_options) for line in renderer.render_restructuredtext_markup(spec): options.output.write(line+'\n') logging.debug(line) if __name__ == '__main__': main() openapi-0.8.4/sphinxcontrib/openapi/_lib2to3.py000066400000000000000000000324671456264404500215200ustar00rootroot00000000000000"""Partial OpenAPI v2.x (fka Swagger) to OpenAPI v3.x converter.""" import functools import urllib import picobox __all__ = [ "convert", ] def convert(spec): """Convert a given OAS 2 spec to OAS 3.""" return Lib2to3().convert(spec) def _is_vendor_extension(key): """Return 'True' if a given key is a vendor extension.""" return key.startswith("x-") def _get_properties(node, properties, *, vendor_extensions=False): """Return a subset of 'node' properties w/ or wo/ vendor extensions.""" return { key: value for key, value in node.items() if any([key in properties, vendor_extensions and _is_vendor_extension(key)]) } def _get_schema_properties(node, *, except_for=None): """Find and return 'Schema Object' properties.""" except_for = except_for or set() schema = _get_properties( node, { "additionalProperties", "allOf", "default", "description", "discriminator", "enum", "example", "exclusiveMaximum", "exclusiveMinimum", "externalDocs", "format", "items", "maxItems", "maxLength", "maxProperties", "maximum", "minItems", "minLength", "minProperties", "minimum", "multipleOf", "pattern", "properties", "readOnly", "required", "title", "type", "uniqueItems", "xml", } - set(except_for), ) if "discriminator" in schema: schema["discriminator"] = {"propertyName": schema["discriminator"]} return schema def _items_wo_vendor_extensions(node): """Iterate over 'node' properties excluding vendor extensions.""" for key, value in node.items(): if _is_vendor_extension(key): continue yield key, value class Lib2to3: _target_version = "3.0.3" _injector = picobox.Stack() def _insert_into_injector(name): def decorator(fn): @functools.wraps(fn) def wrapper(self, node, *args, **kwargs): with Lib2to3._injector.push(picobox.Box(), chain=True) as box: box.put(name, factory=lambda: node, scope=picobox.threadlocal) return fn(self, node, *args, **kwargs) return wrapper return decorator def __init__(self): self._schemes = set() @_insert_into_injector("spec") def convert(self, spec): # The following OAS 2 fields are ignored and not converted. Mostly due # to the fact that we expect *resolved* spec as input, and most of its # fields are used to group shared (i.e. referenced) objects that will # not exist in the resolved spec. # # - definitions # - parameters # - responses # - securityDefinitions # - security # # By no means one must assume that these fields will never be # converted. I simply have no time to work on this, and for # sphixcontrib-openapi purposes it's not actually needed. converted = { "info": spec["info"], "openapi": self._target_version, "paths": self.convert_paths(spec["paths"]), } converted.update( _get_properties(spec, {"tags", "externalDocs"}, vendor_extensions=True), ) servers = self.convert_servers(spec) if servers: converted["servers"] = servers return converted @_insert_into_injector("paths") def convert_paths(self, paths): converted = _get_properties(paths, {}, vendor_extensions=True) for endpoint, path in _items_wo_vendor_extensions(paths): converted[endpoint] = self.convert_path(path) return converted @_insert_into_injector("path") def convert_path(self, path): converted = _get_properties(path, {}, vendor_extensions=True) for key, value in _items_wo_vendor_extensions(path): if key == "parameters": converted[key] = self.convert_parameters(value) else: converted[key] = self.convert_operation(value) return converted @_insert_into_injector("operation") def convert_operation(self, operation): converted = _get_properties( operation, { "tags", "summary", "description", "externalDocs", "operationId", "deprecated", "security", }, vendor_extensions=True, ) # Memorize every encountered 'schemes'. Since this property does not # exist in OAS 3, it seems the best we can do is to use them in OAS 3 # 'servers' object. self._schemes.update(operation.get("schemes", [])) if "parameters" in operation: parameters = self.convert_parameters(operation["parameters"]) # Both 'body' and 'formData' parameters are mutually exclusive, # therefore there's no way we may end up with both kinds at once. request_body = self.convert_request_body(operation) request_body = request_body or self.convert_request_body_formdata(operation) if parameters: converted["parameters"] = parameters if request_body: converted["requestBody"] = request_body converted["responses"] = self.convert_responses(operation["responses"]) return converted @_injector.pass_("spec") def convert_request_body(self, operation, *, spec): # OAS 3 expects an explicitly specified mimetype of the request body. # It's not clear what to do if OAS 2 'consumes' is not defined. Let's # start with a glob pattern and figure out what a better option could # be later on. consumes = operation.get("consumes") or spec.get("consumes") or ["*/*"] for parameter in operation["parameters"]: if parameter["in"] == "body": # Since 'requestBody' is completely new and nested object in # OAS 3, it's not clear what should we insert possible vendor # extensions. Thus, let's ignore them until we figure it out. converted = _get_properties(parameter, {"description", "required"}) converted["content"] = { consume: {"schema": parameter["schema"]} for consume in consumes } return converted return None @_injector.pass_("spec") def convert_request_body_formdata(self, operation, *, spec): consumes = ( operation.get("consumes") or spec.get("consumes") or ["application/x-www-form-urlencoded"] ) supported = { "application/x-www-form-urlencoded", "multipart/form-data", } mimetypes = supported.intersection(consumes) schema = {"type": "object", "properties": {}} for parameter in operation["parameters"]: if parameter["in"] == "formData": schema["properties"][parameter["name"]] = _get_schema_properties( parameter, except_for={"name", "in", "required"} ) if parameter.get("required"): schema.setdefault("required", []).append(parameter["name"]) # Excerpt from OpenAPI 2.x spec: # # > If type is "file", the consumes MUST be either # > "multipart/form-data", "application/x-www-form-urlencoded" # > or both and the parameter MUST be in "formData". # # This is weird since HTTP does not allow file uploading in # 'application/x-www-form-urlencoded'. Moreover, Swagger # editor complains if 'file' is detected and there's no # 'multipart/form-data' in `consumes'. if parameter["type"] == "file": mimetypes = ["multipart/form-data"] if not schema["properties"]: return None return {"content": {mimetype: {"schema": schema} for mimetype in mimetypes}} @_insert_into_injector("parameters") def convert_parameters(self, parameters): return [ self.convert_parameter(parameter) for parameter in parameters # If a parameter is one of the backward compatible type, delegate # the call to the converter function. Incompatible types, such as # 'formData' and 'body', must be handled separately since they are # reflected in 'Operation Object' in OAS 3. if parameter["in"] in {"query", "header", "path"} ] @_insert_into_injector("parameter") def convert_parameter(self, parameter): schema = _get_schema_properties( parameter, # Some of 'Parameter Object' properties have the same name as some # of 'Schema Object' properties. Since we know for sure that in # this context they are part of 'Parameter Object', we should # ignore their meaning as part of 'Schema Object'. except_for={"name", "in", "description", "required"}, ) converted = { key: value for key, value in parameter.items() if key not in schema } converted["schema"] = schema collection_format = converted.pop("collectionFormat", None) if converted["in"] in {"path", "header"} and collection_format == "csv": converted["style"] = "simple" elif converted["in"] in {"query"} and collection_format: styles = { "csv": {"style": "form", "explode": False}, "multi": {"style": "form", "explode": True}, "ssv": {"style": "spaceDelimited"}, "pipes": {"style": "pipeDelimited"}, # OAS 3 does not explicitly say what is the alternative to # 'collectionFormat=tsv'. We have no other option but to ignore # it. Fortunately, we don't care much as it's not used by the # renderer. "tsv": {}, } converted.update(styles[collection_format]) return converted @_insert_into_injector("responses") def convert_responses(self, responses): converted = _get_properties(responses, {}, vendor_extensions=True) for status_code, response in _items_wo_vendor_extensions(responses): converted[status_code] = self.convert_response(response) return converted @_injector.pass_("spec") @_injector.pass_("operation") @_insert_into_injector("response") def convert_response(self, response, *, spec, operation): converted = _get_properties(response, {"description"}, vendor_extensions=True) # OAS 3 expects an explicitly specified mimetype in the response. It's # not clear what to do if OAS 2 'produces' is not defined. Let's start # with a glob pattern and figure out what a better option could be # later on. produces = operation.get("produces") or spec.get("produces") or ["*/*"] schema = response.get("schema") examples = response.get("examples") if schema or examples: content = converted.setdefault("content", {}) if schema is not None: for mimetype in produces: content.setdefault(mimetype, {})["schema"] = schema if examples is not None: # According to OAS2, mimetypes in 'examples' property MUST be # one of the operation's 'produces'. for mimetype, example in examples.items(): content.setdefault(mimetype, {})["example"] = example if "headers" in response: converted["headers"] = { key: dict( _get_properties(value, "description", vendor_extensions=True), schema=_get_schema_properties(value, except_for={"description"}), ) for key, value in response["headers"].items() } return converted def convert_servers(self, spec): """Convert OAS2 '(host, basePath, schemes)' triplet into OAS3 'servers' node.""" host = spec.get("host", "") basepath = spec.get("basePath", "") schemes = self._schemes.union(spec.get("schemes", set())) # Since 'host', 'basePath' and 'schemes' are optional in OAS 2, there # may be the case when they aren't set. If that's happened it means # there's nothing to convert, and thus we simply return an empty list. if not host and not basepath and not schemes: return [] if not schemes: # If 'host' is not set, the url will contain a bare basePath. # According to OAS 3, it's a valid URL, and both the host and the # scheme must be assumed to be the same as the server that shared # this OAS 3 spec. return [{"url": urllib.parse.urljoin(host, basepath)}] return [ {"url": urllib.parse.urlunsplit([scheme, host, basepath, None, None])} for scheme in sorted(schemes) ] openapi-0.8.4/sphinxcontrib/openapi/directive.py000066400000000000000000000037671456264404500220620ustar00rootroot00000000000000""" sphinxcontrib.openapi.directive ------------------------------- The main directive for the extension. :copyright: (c) 2016, Ihor Kalnytskyi. :license: BSD, see LICENSE for details. """ import functools from docutils.parsers.rst import directives from sphinx.util.docutils import SphinxDirective import yaml # Locally cache spec to speedup processing of same spec file in multiple # openapi directives @functools.lru_cache() def _get_spec(abspath, encoding): with open(abspath, 'rt', encoding=encoding) as stream: return yaml.safe_load(stream) def create_directive_from_renderer(renderer_cls): """Create rendering directive from a renderer class.""" class _RenderingDirective(SphinxDirective): required_arguments = 1 # path to openapi spec final_argument_whitespace = True # path may contain whitespaces option_spec = dict( { 'encoding': directives.encoding, # useful for non-ascii cases :) }, **renderer_cls.option_spec ) def run(self): relpath, abspath = self.env.relfn2path(directives.path(self.arguments[0])) # URI parameter is crucial for resolving relative references. So we # need to set this option properly as it's used later down the # stack. self.options.setdefault('uri', 'file://%s' % abspath) # Add a given OpenAPI spec as a dependency of the referring # reStructuredText document, so the document is rebuilt each time # the spec is changed. self.env.note_dependency(relpath) # Read the spec using encoding passed to the directive or fallback to # the one specified in Sphinx's config. encoding = self.options.get('encoding', self.config.source_encoding) spec = _get_spec(abspath, encoding) return renderer_cls(self.state, self.options).render(spec) return _RenderingDirective openapi-0.8.4/sphinxcontrib/openapi/openapi20.py000066400000000000000000000214501456264404500216660ustar00rootroot00000000000000""" sphinxcontrib.openapi.openapi20 ------------------------------- The OpenAPI 2.0 (f.k.a. Swagger) spec renderer. Based on ``sphinxcontrib-httpdomain``. :copyright: (c) 2016, Ihor Kalnytskyi. :license: BSD, see LICENSE for details. """ import collections import itertools import re from sphinxcontrib.openapi import utils def _httpresource(endpoint, method, properties, convert): parameters = properties.get('parameters', []) responses = properties['responses'] indent = ' ' yield '.. http:{0}:: {1}'.format(method, endpoint) yield ' :synopsis: {0}'.format(properties.get('summary', 'null')) yield '' if 'summary' in properties: for line in properties['summary'].splitlines(): yield '{indent}**{line}**'.format(**locals()) yield '' if 'description' in properties: for line in convert(properties['description']).splitlines(): yield '{indent}{line}'.format(**locals()) yield '' for param in filter(lambda p: p['in'] == 'path', parameters): yield indent + ':param {type} {name}:'.format(**param) for line in convert(param.get('description', '')).splitlines(): yield '{indent}{indent}{line}'.format(**locals()) # print request's query params for param in filter(lambda p: p['in'] == 'query', parameters): yield indent + ':query {type} {name}:'.format(**param) for line in convert(param.get('description', '')).splitlines(): yield '{indent}{indent}{line}'.format(**locals()) # print the json body params for param in filter(lambda p: p['in'] == 'body', parameters): if 'schema' in param: yield '' for line in convert_json_schema(param['schema']): yield '{indent}{line}'.format(**locals()) yield '' # print response status codes for status, response in sorted(responses.items()): yield '{indent}:status {status}:'.format(**locals()) for line in convert(response.get('description', '')).splitlines(): yield '{indent}{indent}{line}'.format(**locals()) # print request header params for param in filter(lambda p: p['in'] == 'header', parameters): yield indent + ':reqheader {name}:'.format(**param) for line in convert(param.get('description', '')).splitlines(): yield '{indent}{indent}{line}'.format(**locals()) # print response headers for status, response in responses.items(): for headername, header in response.get('headers', {}).items(): yield indent + ':resheader {name}:'.format(name=headername) for line in convert(header.get('description', '')).splitlines(): yield '{indent}{indent}{line}'.format(**locals()) for status, response in responses.items(): if not is_2xx_response(status): continue if 'schema' in response: yield '' for line in convert_json_schema( response['schema'], directive=':>json'): yield '{indent}{line}'.format(**locals()) yield '' yield '' def convert_json_schema(schema, directive=': } _READONLY_PROPERTY = object() # sentinel for values not included in requests def _dict_merge(dct, merge_dct): """Recursive dict merge. Inspired by :meth:``dict.update()``, instead of updating only top-level keys, dict_merge recurses down into dicts nested to an arbitrary depth, updating keys. The ``merge_dct`` is merged into ``dct``. From https://gist.github.com/angstwad/bf22d1822c38a92ec0a9 Arguments: dct: dict onto which the merge is executed merge_dct: dct merged into dct """ for k in merge_dct.keys(): if (k in dct and isinstance(dct[k], dict) and isinstance(merge_dct[k], collections.abc.Mapping)): _dict_merge(dct[k], merge_dct[k]) else: dct[k] = merge_dct[k] def _parse_schema(schema, method): """ Convert a Schema Object to a Python object. Args: schema: An ``OrderedDict`` representing the schema object. """ if method and schema.get('readOnly', False): return _READONLY_PROPERTY # allOf: Must be valid against all of the subschemas if 'allOf' in schema: schema_ = copy.deepcopy(schema['allOf'][0]) for x in schema['allOf'][1:]: _dict_merge(schema_, x) return _parse_schema(schema_, method) # anyOf: Must be valid against any of the subschemas # TODO(stephenfin): Handle anyOf # oneOf: Must be valid against exactly one of the subschemas if 'oneOf' in schema: # we only show the first one since we can't show everything return _parse_schema(schema['oneOf'][0], method) if 'enum' in schema: # we only show the first one since we can't show everything return schema['enum'][0] schema_type = schema.get('type', 'object') if schema_type == 'array': # special case oneOf and anyOf so that we can show examples for all # possible combinations if 'oneOf' in schema['items']: return [ _parse_schema(x, method) for x in schema['items']['oneOf'] ] if 'anyOf' in schema['items']: return [ _parse_schema(x, method) for x in schema['items']['anyOf'] ] return [_parse_schema(schema['items'], method)] if schema_type == 'object': if method and 'properties' in schema and \ all(v.get('readOnly', False) for v in schema['properties'].values()): return _READONLY_PROPERTY results = [] for name, prop in schema.get('properties', {}).items(): result = _parse_schema(prop, method) if result != _READONLY_PROPERTY: results.append((name, result)) return collections.OrderedDict(results) if (schema_type, schema.get('format')) in _TYPE_MAPPING: return _TYPE_MAPPING[(schema_type, schema.get('format'))] return _TYPE_MAPPING[(schema_type, None)] # unrecognized format def _example(media_type_objects, method=None, endpoint=None, status=None, nb_indent=0): """ Format examples in `Media Type Object` openapi v3 to HTTP request or HTTP response example. If method and endpoint is provided, this function prints a request example else status should be provided to print a response example. Arguments: media_type_objects (Dict[str, Dict]): Dict containing Media Type Objects. method: The HTTP method to use in example. endpoint: The HTTP route to use in example. status: The HTTP status to use in example. """ indent = ' ' extra_indent = indent * nb_indent if method is not None: method = method.upper() else: try: # one of possible values for status might be 'default'. # in the case, just fallback to '-' status_text = http_status_codes[int(status)] except (ValueError, KeyError): status_text = '-' # Provide request samples for GET requests if method == 'GET': media_type_objects[''] = { 'examples': {'Example request': {'value': ''}}} for content_type, content in media_type_objects.items(): examples = content.get('examples') example = content.get('example') # Try to get the example from the schema if example is None and 'schema' in content: example = content['schema'].get('example') if examples is None: examples = {} if not example: if re.match(r"application/[a-zA-Z\+]*json", content_type) is \ None: LOG.info('skipping non-JSON example generation.') continue example = _parse_schema(content['schema'], method=method) if method is None: examples['Example response'] = { 'value': example, } else: examples['Example request'] = { 'value': example, } for example in examples.values(): # According to OpenAPI v3 specs, string examples should be left unchanged if not isinstance(example['value'], str): example['value'] = json.dumps( example['value'], indent=4, separators=(',', ': ')) for example_name, example in examples.items(): if 'summary' in example: example_title = '{example_name} - {example[summary]}'.format( **locals()) else: example_title = example_name yield '' yield '{extra_indent}**{example_title}:**'.format(**locals()) yield '' yield '{extra_indent}.. sourcecode:: http'.format(**locals()) yield '' # Print http request example if method: yield '{extra_indent}{indent}{method} {endpoint} HTTP/1.1' \ .format(**locals()) yield '{extra_indent}{indent}Host: example.com' \ .format(**locals()) if content_type: yield '{extra_indent}{indent}Content-Type: {content_type}'\ .format(**locals()) # Print http response example else: yield '{extra_indent}{indent}HTTP/1.1 {status} {status_text}' \ .format(**locals()) yield '{extra_indent}{indent}Content-Type: {content_type}' \ .format(**locals()) yield '' for example_line in example['value'].splitlines(): yield '{extra_indent}{indent}{example_line}'.format(**locals()) if example['value'].splitlines(): yield '' def _httpresource(endpoint, method, properties, convert, render_examples, render_request): # https://github.com/OAI/OpenAPI-Specification/blob/3.0.2/versions/3.0.0.md#operation-object parameters = properties.get('parameters', []) responses = properties['responses'] query_param_examples = [] indent = ' ' yield '.. http:{0}:: {1}'.format(method, endpoint) yield ' :synopsis: {0}'.format(properties.get('summary', 'null')) yield '' if 'summary' in properties: for line in properties['summary'].splitlines(): yield '{indent}**{line}**'.format(**locals()) yield '' if 'description' in properties: for line in convert(properties.get('description', '')).splitlines(): yield '{indent}{line}'.format(**locals()) yield '' # print request's path params for param in filter(lambda p: p['in'] == 'path', parameters): yield indent + ':param {type} {name}:'.format( type=param['schema']['type'], name=param['name']) for line in convert(param.get('description', '')).splitlines(): yield '{indent}{indent}{line}'.format(**locals()) # print request's query params for param in filter(lambda p: p['in'] == 'query', parameters): yield indent + ':query {type} {name}:'.format( type=param['schema']['type'], name=param['name']) for line in convert(param.get('description', '')).splitlines(): yield '{indent}{indent}{line}'.format(**locals()) if param.get('required', False): yield '{indent}{indent}(Required)'.format(**locals()) example = _parse_schema(param['schema'], method) example = param.get('example', example) if param.get('explode', False) and isinstance(example, list): for v in example: query_param_examples.append((param['name'], v)) elif param.get('explode', False) and isinstance(example, dict): for k, v in example.items(): query_param_examples.append((k, v)) else: query_param_examples.append((param['name'], example)) # print request content if render_request: request_content = properties.get('requestBody', {}).get('content', {}) if request_content and 'application/json' in request_content: schema = request_content['application/json']['schema'] req_properties = json.dumps(schema['properties'], indent=2, separators=(',', ':')) yield '{indent}**Request body:**'.format(**locals()) yield '' yield '{indent}.. sourcecode:: json'.format(**locals()) yield '' for line in req_properties.splitlines(): # yield indent + line yield '{indent}{indent}{line}'.format(**locals()) # yield '' # print request example if render_examples: endpoint_examples = endpoint if query_param_examples: endpoint_examples = endpoint + "?" + \ parse.urlencode(query_param_examples) # print request example request_content = properties.get('requestBody', {}).get('content', {}) for line in _example( request_content, method, endpoint=endpoint_examples, nb_indent=1): yield line # print response status codes for status, response in responses.items(): yield '{indent}:status {status}:'.format(**locals()) for line in convert(response.get('description', '')).splitlines(): yield '{indent}{indent}{line}'.format(**locals()) # print response example if render_examples: for line in _example( response.get('content', {}), status=status, nb_indent=2): yield line # print request header params for param in filter(lambda p: p['in'] == 'header', parameters): yield indent + ':reqheader {name}:'.format(**param) for line in convert(param.get('description', '')).splitlines(): yield '{indent}{indent}{line}'.format(**locals()) if param.get('required', False): yield '{indent}{indent}(Required)'.format(**locals()) # print response headers for status, response in responses.items(): for headername, header in response.get('headers', {}).items(): yield indent + ':resheader {name}:'.format(name=headername) for line in convert(header.get('description', '')).splitlines(): yield '{indent}{indent}{line}'.format(**locals()) for cb_name, cb_specs in properties.get('callbacks', {}).items(): yield '' yield indent + '.. admonition:: Callback: ' + cb_name yield '' for cb_endpoint in cb_specs.keys(): for cb_method, cb_properties in cb_specs[cb_endpoint].items(): for line in _httpresource( cb_endpoint, cb_method, cb_properties, convert=convert, render_examples=render_examples, render_request=render_request): if line: yield indent+indent+line else: yield '' yield '' def _header(title): yield title yield '=' * len(title) yield '' def openapihttpdomain(spec, **options): generators = [] # OpenAPI spec may contain JSON references, common properties, etc. # Trying to render the spec "As Is" will require to put multiple # if-s around the code. In order to simplify flow, let's make the # spec to have only one (expected) schema, i.e. normalize it. utils.normalize_spec(spec, **options) # Paths list to be processed paths = [] # If 'paths' are passed we've got to ensure they exist within an OpenAPI # spec; otherwise raise error and ask user to fix that. if 'paths' in options: if not set(options['paths']).issubset(spec['paths']): raise ValueError( 'One or more paths are not defined in the spec: %s.' % ( ', '.join(set(options['paths']) - set(spec['paths'])), ) ) paths = options['paths'] # Check against regular expressions to be included if 'include' in options: for i in options['include']: ir = re.compile(i) for path in spec['paths']: if ir.match(path): paths.append(path) # If no include nor paths option, then take full path if 'include' not in options and 'paths' not in options: paths = spec['paths'] # Remove paths matching regexp if 'exclude' in options: _paths = [] for e in options['exclude']: er = re.compile(e) for path in paths: if not er.match(path): _paths.append(path) paths = _paths render_request = False if 'request' in options: render_request = True convert = utils.get_text_converter(options) # https://github.com/OAI/OpenAPI-Specification/blob/3.0.2/versions/3.0.0.md#paths-object if 'group' in options: groups = collections.OrderedDict( [(x['name'], []) for x in spec.get('tags', {})] ) for endpoint in paths: for method, properties in spec['paths'][endpoint].items(): key = properties.get('tags', [''])[0] groups.setdefault(key, []).append(_httpresource( endpoint, method, properties, convert, render_examples='examples' in options, render_request=render_request)) for key in groups.keys(): if key: generators.append(_header(key)) else: generators.append(_header('default')) generators.extend(groups[key]) else: for endpoint in paths: for method, properties in spec['paths'][endpoint].items(): generators.append(_httpresource( endpoint, method, properties, convert, render_examples='examples' in options, render_request=render_request)) return iter(itertools.chain(*generators)) openapi-0.8.4/sphinxcontrib/openapi/openapi31.py000066400000000000000000000450351456264404500216750ustar00rootroot00000000000000""" sphinxcontrib.openapi.openapi31 ------------------------------- The OpenAPI 3.1 spec renderer. Based on ``sphinxcontrib-httpdomain``. :copyright: (c) 2016, Ihor Kalnytskyi. :license: BSD, see LICENSE for details. """ import copy import collections import collections.abc from datetime import datetime import itertools import json import re from urllib import parse from http.client import responses as http_status_codes from sphinx.util import logging from sphinxcontrib.openapi import utils LOG = logging.getLogger(__name__) # Based on the spec: # # https://github.com/OAI/OpenAPI-Specification/blob/3.1.0/versions/3.1.0.md#dataTypes # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-00#section-4.2.1 # # Note that array and object are excluded since these are handled separately _TYPE_MAPPING = { ("integer", "int32"): 1, # integer ("integer", "int64"): 1, # long ("number", "float"): 1.0, # float ("number", "double"): 1.0, # double ("boolean", None): True, # boolean ("string", None): "string", # string ("string", "byte"): "c3RyaW5n", # b'string' encoded in base64, # byte ("string", "binary"): "01010101", # binary ("string", "date"): datetime.now().date().isoformat(), # date ("string", "date-time"): datetime.now().isoformat(), # dateTime ("string", "password"): "********", # password ("null", None): None, # null # custom extensions to handle common formats ("string", "email"): "name@example.com", ("string", "zip-code"): "90210", ("string", "uri"): "https://example.com", # additional fallthrough cases ("integer", None): 1, # integer ("number", None): 1.0, # } _READONLY_PROPERTY = object() # sentinel for values not included in requests def _dict_merge(dct, merge_dct): """Recursive dict merge. Inspired by :meth:``dict.update()``, instead of updating only top-level keys, dict_merge recurses down into dicts nested to an arbitrary depth, updating keys. The ``merge_dct`` is merged into ``dct``. From https://gist.github.com/angstwad/bf22d1822c38a92ec0a9 Arguments: dct: dict onto which the merge is executed merge_dct: dct merged into dct """ for k in merge_dct.keys(): if ( k in dct and isinstance(dct[k], dict) and isinstance(merge_dct[k], collections.abc.Mapping) ): _dict_merge(dct[k], merge_dct[k]) else: dct[k] = merge_dct[k] def _parse_schema(schema, method): """ Convert a Schema Object to a Python object. Args: schema: An ``OrderedDict`` representing the schema object. """ if method and schema.get("readOnly", False): return _READONLY_PROPERTY # allOf: Must be valid against all of the subschemas if "allOf" in schema: schema_ = copy.deepcopy(schema["allOf"][0]) for x in schema["allOf"][1:]: _dict_merge(schema_, x) return _parse_schema(schema_, method) # anyOf: Must be valid against any of the subschemas if "anyOf" in schema: # we only show the one since we can't show everything, but we need to # figure out which one for sub_schema in schema["anyOf"]: if sub_schema["type"] == "null": continue return _parse_schema(sub_schema, method) # oneOf: Must be valid against exactly one of the subschemas if "oneOf" in schema: # we only show the first one since we can't show everything for sub_schema in schema["oneOf"]: if sub_schema["type"] == "null": continue return _parse_schema(sub_schema, method) if "enum" in schema: # we only show the first one since we can't show everything return schema["enum"][0] schema_type = schema.get("type", "object") if isinstance(schema_type, list): if ( len(schema_type) > 2 or "null" not in schema_type or schema_type == ("null", "null") ): raise Exception( "Support for arrays of types with more than two types or " "containing multiple non-null types is not currently " "supported." ) schema_type = [x for x in schema_type if x != "null"][0] if schema_type == "array": # special case oneOf and anyOf so that we can show examples for all # possible combinations if "oneOf" in schema["items"]: return [_parse_schema(x, method) for x in schema["items"]["oneOf"]] if "anyOf" in schema["items"]: return [_parse_schema(x, method) for x in schema["items"]["anyOf"]] return [_parse_schema(schema["items"], method)] if schema_type == "object": if ( method and "properties" in schema and all(v.get("readOnly", False) for v in schema["properties"].values()) ): return _READONLY_PROPERTY results = [] for name, prop in schema.get("properties", {}).items(): result = _parse_schema(prop, method) if result != _READONLY_PROPERTY: results.append((name, result)) return collections.OrderedDict(results) if (schema_type, schema.get("format")) in _TYPE_MAPPING: return _TYPE_MAPPING[(schema_type, schema.get("format"))] return _TYPE_MAPPING[(schema_type, None)] # unrecognized format def _example(media_type_objects, method=None, endpoint=None, status=None, nb_indent=0): """ Format examples in `Media Type Object` openapi v3 to HTTP request or HTTP response example. If method and endpoint is provided, this function prints a request example else status should be provided to print a response example. Arguments: media_type_objects (Dict[str, Dict]): Dict containing Media Type Objects. method: The HTTP method to use in example. endpoint: The HTTP route to use in example. status: The HTTP status to use in example. """ indent = " " extra_indent = indent * nb_indent if method is not None: method = method.upper() else: try: # one of possible values for status might be 'default'. # in the case, just fallback to '-' status_text = http_status_codes[int(status)] except (ValueError, KeyError): status_text = "-" # Provide request samples for GET requests if method == "GET": media_type_objects[""] = {"examples": {"Example request": {"value": ""}}} for content_type, content in media_type_objects.items(): examples = content.get("examples") example = content.get("example") # Try to get the example from the schema if example is None and "schema" in content: example = content["schema"].get("example") if examples is None: examples = {} if not example: if re.match(r"application/[a-zA-Z\+]*json", content_type) is None: LOG.info("skipping non-JSON example generation.") continue example = _parse_schema(content["schema"], method=method) if method is None: examples["Example response"] = { "value": example, } else: examples["Example request"] = { "value": example, } for example in examples.values(): # According to OpenAPI v3 specs, string examples should be left unchanged if not isinstance(example["value"], str): example["value"] = json.dumps( example["value"], indent=4, separators=(",", ": ") ) for example_name, example in examples.items(): if "summary" in example: example_title = "{example_name} - {example[summary]}".format(**locals()) else: example_title = example_name yield "" yield "{extra_indent}**{example_title}:**".format(**locals()) yield "" yield "{extra_indent}.. sourcecode:: http".format(**locals()) yield "" # Print http request example if method: yield "{extra_indent}{indent}{method} {endpoint} HTTP/1.1".format( **locals() ) yield "{extra_indent}{indent}Host: example.com".format(**locals()) if content_type: yield "{extra_indent}{indent}Content-Type: {content_type}".format( **locals() ) # Print http response example else: yield "{extra_indent}{indent}HTTP/1.1 {status} {status_text}".format( **locals() ) yield "{extra_indent}{indent}Content-Type: {content_type}".format( **locals() ) yield "" for example_line in example["value"].splitlines(): yield "{extra_indent}{indent}{example_line}".format(**locals()) if example["value"].splitlines(): yield "" def _httpresource( endpoint, method, properties, convert, render_examples, render_request ): # https://github.com/OAI/OpenAPI-Specification/blob/3.1.0/versions/3.1.0.md#operation-object parameters = properties.get("parameters", []) responses = properties["responses"] query_param_examples = [] indent = " " yield ".. http:{0}:: {1}".format(method, endpoint) yield " :synopsis: {0}".format(properties.get("summary", "null")) yield "" if "summary" in properties: for line in properties["summary"].splitlines(): yield "{indent}**{line}**".format(**locals()) yield "" if "description" in properties: for line in convert(properties.get("description", "")).splitlines(): yield "{indent}{line}".format(**locals()) yield "" def _get_type_from_schema(schema): if "type" in schema.keys(): dtype = schema["type"] else: dtype = set() for t in schema["anyOf"]: if "format" in t.keys(): dtype.add(t["format"]) else: dtype.add(t["type"]) return dtype # print request's path params for param in filter(lambda p: p["in"] == "path", parameters): type_ = _get_type_from_schema(param["schema"]) yield indent + ":param {type} {name}:".format(type=type_, name=param["name"]) for line in convert(param.get("description", "")).splitlines(): yield "{indent}{indent}{line}".format(**locals()) # print request's query params for param in filter(lambda p: p["in"] == "query", parameters): type_ = _get_type_from_schema(param["schema"]) yield indent + ":query {type} {name}:".format(type=type_, name=param["name"]) for line in convert(param.get("description", "")).splitlines(): yield "{indent}{indent}{line}".format(**locals()) if param.get("required", False): yield "{indent}{indent}(Required)".format(**locals()) example = _parse_schema(param["schema"], method) example = param.get("example", example) if param.get("explode", False) and isinstance(example, list): for v in example: query_param_examples.append((param["name"], v)) elif param.get("explode", False) and isinstance(example, dict): for k, v in example.items(): query_param_examples.append((k, v)) else: query_param_examples.append((param["name"], example)) # print request content if render_request: request_content = properties.get("requestBody", {}).get("content", {}) if request_content and "application/json" in request_content: schema = request_content["application/json"]["schema"] req_properties = json.dumps( schema["properties"], indent=2, separators=(",", ":") ) yield "{indent}**Request body:**".format(**locals()) yield "" yield "{indent}.. sourcecode:: json".format(**locals()) yield "" for line in req_properties.splitlines(): # yield indent + line yield "{indent}{indent}{line}".format(**locals()) # yield '' # print request example if render_examples: endpoint_examples = endpoint if query_param_examples: endpoint_examples = endpoint + "?" + parse.urlencode(query_param_examples) # print request example request_content = properties.get("requestBody", {}).get("content", {}) for line in _example( request_content, method, endpoint=endpoint_examples, nb_indent=1 ): yield line # print response status codes for status, response in responses.items(): yield "{indent}:status {status}:".format(**locals()) for line in convert(response.get("description", "")).splitlines(): yield "{indent}{indent}{line}".format(**locals()) # print response example if render_examples: for line in _example( response.get("content", {}), status=status, nb_indent=2 ): yield line # print request header params for param in filter(lambda p: p["in"] == "header", parameters): yield indent + ":reqheader {name}:".format(**param) for line in convert(param.get("description", "")).splitlines(): yield "{indent}{indent}{line}".format(**locals()) if param.get("required", False): yield "{indent}{indent}(Required)".format(**locals()) # print response headers for status, response in responses.items(): for headername, header in response.get("headers", {}).items(): yield indent + ":resheader {name}:".format(name=headername) for line in convert(header.get("description", "")).splitlines(): yield "{indent}{indent}{line}".format(**locals()) for cb_name, cb_specs in properties.get("callbacks", {}).items(): yield "" yield indent + ".. admonition:: Callback: " + cb_name yield "" for cb_endpoint in cb_specs.keys(): for cb_method, cb_properties in cb_specs[cb_endpoint].items(): for line in _httpresource( cb_endpoint, cb_method, cb_properties, convert=convert, render_examples=render_examples, render_request=render_request, ): if line: yield indent + indent + line else: yield "" yield "" def _header(title): yield title yield "=" * len(title) yield "" def openapihttpdomain(spec, **options): generators = [] # OpenAPI spec may contain JSON references, common properties, etc. # Trying to render the spec "As Is" will require to put multiple # if-s around the code. In order to simplify flow, let's make the # spec to have only one (expected) schema, i.e. normalize it. utils.normalize_spec(spec, **options) # Paths list to be processed paths = [] # If a path-related option was provided, we need to first ensure we have # paths within the spec; otherwise raise error and ask user to fix that. if "paths" not in spec and ( "paths" in options or "include" in options or "exclude" in options or "group" in options ): raise ValueError( "Spec does not define any paths and the 'include', 'exclude', " "'paths' and 'group' options are therefore invalid." ) # If 'paths' are passed we've got to ensure they exist within an OpenAPI # spec; otherwise raise error and ask user to fix that. if "paths" in options: if not set(options["paths"]).issubset(spec["paths"]): raise ValueError( "One or more paths are not defined in the spec: %s." % (", ".join(set(options["paths"]) - set(spec["paths"])),) ) paths = options["paths"] # Check against regular expressions to be included if "include" in options: for i in options["include"]: ir = re.compile(i) for path in spec["paths"]: if ir.match(path): paths.append(path) # If no include nor paths option, then take full path if "include" not in options and "paths" not in options: paths = spec.get("paths", []) # Remove paths matching regexp if "exclude" in options: _paths = [] for path in paths: if not any(re.match(pattern, path) for pattern in options["exclude"]): _paths.append(path) paths = _paths render_request = False if "request" in options: render_request = True convert = utils.get_text_converter(options) # https://github.com/OAI/OpenAPI-Specification/blob/3.1.0/versions/3.1.0.md#paths-object if "group" in options: groups = collections.OrderedDict( [(x["name"], []) for x in spec.get("tags", {})] ) for endpoint in paths: for method, properties in spec["paths"][endpoint].items(): key = properties.get("tags", [""])[0] groups.setdefault(key, []).append( _httpresource( endpoint, method, properties, convert, render_examples="examples" in options, render_request=render_request, ) ) for key in groups.keys(): if key: generators.append(_header(key)) else: generators.append(_header("default")) generators.extend(groups[key]) else: for endpoint in paths: for method, properties in spec["paths"][endpoint].items(): generators.append( _httpresource( endpoint, method, properties, convert, render_examples="examples" in options, render_request=render_request, ) ) return iter(itertools.chain(*generators)) openapi-0.8.4/sphinxcontrib/openapi/renderers/000077500000000000000000000000001456264404500215065ustar00rootroot00000000000000openapi-0.8.4/sphinxcontrib/openapi/renderers/__init__.py000066400000000000000000000003471456264404500236230ustar00rootroot00000000000000"""Here lies OpenAPI renderers.""" from . import abc from ._httpdomain_old import HttpdomainOldRenderer from ._httpdomain import HttpdomainRenderer __all__ = [ "abc", "HttpdomainOldRenderer", "HttpdomainRenderer", ] openapi-0.8.4/sphinxcontrib/openapi/renderers/_httpdomain.py000066400000000000000000000601671456264404500244000ustar00rootroot00000000000000"""OpenAPI spec renderer.""" import collections import collections.abc import copy import functools import http.client import json import deepmerge import docutils.parsers.rst.directives as directives import requests import sphinx.util.logging as logging import sphinx_mdinclude from sphinxcontrib.openapi import _lib2to3 as lib2to3 from sphinxcontrib.openapi.renderers import abc from sphinxcontrib.openapi.schema_utils import example_from_schema CaseInsensitiveDict = requests.structures.CaseInsensitiveDict logger = logging.getLogger(__name__) def indented(generator, indent=3): for item in generator: if item: item = " " * indent + item yield item def _iterinorder(iterable, order_by, key=lambda x: x, case_sensitive=False): """Iterate over iterable in a given order.""" order_by = collections.defaultdict( # Assume default priority is `Infinity` which means the lowest one. # This value is effectively used if there's no corresponding value in a # given 'order_by' array. lambda: float("Inf"), # Passed 'order_by' may be 'None' which means *do not reorder, use # natural order*. In order to avoid special cases in the code, we're # simply falling back to an empty 'order_by' array since it effectively # means *assume every item in 'iterable' has equal priority*. ((value, i) for i, value in enumerate(order_by or [])), ) yield from sorted( iterable, key=lambda value: order_by[ key(value) if case_sensitive else key(value).lower() ], ) def _iterexamples(media_types, example_preference, examples_from_schemas): """Iterate over examples and return them according to the caller preference.""" for content_type in _iterinorder(media_types, example_preference): media_type = media_types[content_type] # Look for a example in a bunch of possible places. According to # OpenAPI v3 spec, `examples` and `example` keys are mutually # exclusive, so there's no much difference between their # inspection order, while both must take precedence over a # schema example. if media_type.get("examples", {}): for example in media_type["examples"].values(): if "externalValue" in example: if not example["externalValue"].startswith(("http://", "https://")): logger.warning( "Not supported protocol in 'externalValue': %s", example["externalValue"], ) continue try: response = requests.get(example["externalValue"]) response.raise_for_status() example["value"] = response.text example.pop("externalValue") except Exception: logger.error( "Cannot retrieve example from: '%s'", example["externalValue"], ) continue break else: # If the loop over examples has not been interrupted, we # probably didn't find an example to render. In that case, # let's try and go next media type. continue elif media_type.get("example"): # Save example from "example" in "examples" compatible format. This # allows to treat all returned examples the same way. example = {"value": media_type["example"]} elif media_type.get("schema", {}).get("example"): # Save example from "schema" in "examples" compatible format. This # allows to treat all returned examples the same way. example = {"value": media_type["schema"]["example"]} elif "schema" in media_type and examples_from_schemas: # Convert schema to example example = {"value": example_from_schema(media_type["schema"])} pass else: continue yield content_type, example def _get_markers_from_object(oas_object, schema): """Retrieve a bunch of OAS object markers.""" markers = [] schema_type = _get_schema_type(schema) if schema_type: if schema.get("format"): schema_type = f"{schema_type}:{schema['format']}" elif schema.get("enum"): schema_type = f"{schema_type}:enum" if isinstance(schema_type, list): markers = schema_type else: markers.append(schema_type) elif schema.get("enum"): markers.append("enum") if oas_object.get("required"): markers.append("required") if oas_object.get("deprecated"): markers.append("deprecated") if schema.get("deprecated"): markers.append("deprecated") return markers def _is_json_mimetype(mimetype): """Returns 'True' if a given mimetype implies JSON data.""" return any( [ mimetype == "application/json", mimetype.startswith("application/") and mimetype.endswith("+json"), ] ) def _is_2xx_status(status_code): """Returns 'True' if a given status code is one of successful.""" return str(status_code).startswith("2") def _get_schema_type(schema): """Retrieve schema type either by reading 'type' or guessing.""" # There are a lot of OpenAPI specs out there that may lack 'type' property # in their schemas. I fount no explanations on what is expected behaviour # in this case neither in OpenAPI nor in JSON Schema specifications. Thus # let's assume what everyone assumes, and try to guess schema type at least # for two most popular types: 'object' and 'array'. if "type" not in schema: if "properties" in schema: schema_type = "object" elif "items" in schema: schema_type = "array" else: schema_type = None else: schema_type = schema["type"] return schema_type _merge_mappings = deepmerge.Merger( [(collections.abc.Mapping, deepmerge.strategy.dict.DictStrategies("merge"))], ["override"], ["override"], ).merge class HttpdomainRenderer(abc.RestructuredTextRenderer): """Render OpenAPI v3 using `sphinxcontrib-httpdomain` extension.""" _markup_converters = { "commonmark": sphinx_mdinclude.convert, "restructuredtext": lambda x: x, } _response_examples_for = {"200", "201", "202", "2XX"} _request_parameters_order = ["header", "path", "query", "cookie"] option_spec = { "markup": functools.partial(directives.choice, values=_markup_converters), "http-methods-order": lambda s: s.split(), "response-examples-for": None, "request-parameters-order": None, "example-preference": None, "request-example-preference": None, "response-example-preference": None, "generate-examples-from-schemas": directives.flag, "no-json-schema-description": directives.flag, } def __init__(self, state, options): super().__init__(state, options) self._convert_markup = self._markup_converters[ options.get("markup", "commonmark") ] self._http_methods_order = [ http_method.lower() for http_method in options.get("http-methods-order", []) ] self._response_examples_for = options.get( "response-examples-for", self._response_examples_for ) self._request_parameters_order = [ parameter_type.lower() for parameter_type in options.get( "request-parameters-order", self._request_parameters_order ) ] self._example_preference = options.get("example-preference") self._request_example_preference = options.get( "request-example-preference", self._example_preference ) self._response_example_preference = options.get( "response-example-preference", self._example_preference ) self._generate_example_from_schema = "generate-examples-from-schemas" in options self._json_schema_description = "no-json-schema-description" not in options def render_restructuredtext_markup(self, spec): """Spec render entry point.""" if spec.get("swagger") == "2.0": spec = lib2to3.convert(spec) yield from self.render_paths(spec.get("paths", {})) def render_paths(self, paths): """Render OAS paths item.""" for endpoint, path in paths.items(): common_parameters = path.pop("parameters", []) # OpenAPI's path description may contain objects of different # types. Since we're interested in rendering only objects of # operation type, let's remove irrelevant one from the definition # in order to simplify further code. for key in {"summary", "description", "servers"}: path.pop(key, None) for method in _iterinorder(path, self._http_methods_order): operation = path[method] operation.setdefault("parameters", []) operation_parameters_ids = set( (parameter["name"], parameter["in"]) for parameter in operation["parameters"] ) operation["parameters"] = [ parameter for parameter in common_parameters if (parameter["name"], parameter["in"]) not in operation_parameters_ids ] + operation["parameters"] yield from self.render_operation(endpoint, method, operation) yield "" def render_operation(self, endpoint, method, operation): """Render OAS operation item.""" yield f".. http:{method}:: {endpoint}" if operation.get("deprecated"): yield " :deprecated:" yield "" if operation.get("summary"): yield f" **{operation['summary']}**" yield "" if operation.get("description"): yield from indented( self._convert_markup(operation["description"]).strip().splitlines() ) yield "" yield from indented(self.render_parameters(operation.get("parameters", []))) if "requestBody" in operation: yield from indented( self.render_request_body(operation["requestBody"], endpoint, method) ) yield from indented(self.render_responses(operation["responses"])) def render_parameters(self, parameters): """Render OAS operation's parameters.""" for parameter in _iterinorder( parameters, self._request_parameters_order, key=lambda value: value["in"] ): yield from self.render_parameter(parameter) def render_parameter(self, parameter): """Render OAS operation's parameter.""" kinds = CaseInsensitiveDict( {"path": "param", "query": "queryparam", "header": "reqheader"} ) schema = parameter.get("schema", {}) if "content" in parameter: # According to OpenAPI v3 spec, 'content' in this case may # have one and only one entry. Hence casting its values to # list is not expensive and should be acceptable. schema = list(parameter["content"].values())[0].get("schema", {}) if parameter["in"] not in kinds: logger.warning( "OpenAPI spec contains parameter '%s' (in: '%s') that cannot " "be rendererd.", parameter["name"], parameter["in"], ) return yield f":{kinds[parameter['in']]} {parameter['name']}:" if parameter.get("description"): yield from indented( self._convert_markup(parameter["description"]).strip().splitlines() ) markers = _get_markers_from_object(parameter, schema) if markers: markers = ", ".join(markers) yield f":{kinds[parameter['in']]}type {parameter['name']}: {markers}" def render_request_body(self, request_body, endpoint, method): """Render OAS operation's requestBody.""" if self._json_schema_description: for content_type, content in request_body["content"].items(): if _is_json_mimetype(content_type) and content.get("schema"): yield from self.render_json_schema_description( content["schema"], "req" ) yield "" break yield from self.render_request_body_example(request_body, endpoint, method) yield "" def render_request_body_example(self, request_body, endpoint, method): """Render OAS operation's requestBody's example.""" content_type, example = next( _iterexamples( request_body["content"], self._request_example_preference, self._generate_example_from_schema, ), (None, None), ) if content_type and example: example = example["value"] if not isinstance(example, str): example = json.dumps(example, indent=2) yield ".. sourcecode:: http" yield "" yield f" {method.upper()} {endpoint} HTTP/1.1" yield f" Content-Type: {content_type}" yield "" yield from indented(example.splitlines()) def render_responses(self, responses): """Render OAS operation's responses.""" if self._json_schema_description: for status_code, response in responses.items(): if _is_2xx_status(status_code): for content_type, content in response.get("content", {}).items(): if _is_json_mimetype(content_type) and content.get("schema"): yield from self.render_json_schema_description( content["schema"], "res" ) yield "" break break for status_code, response in responses.items(): # Due to the way how YAML spec is parsed, status code may be # infered as integer. In order to spare some cycles on type # guessing going on, let's ensure it's always string at this point. yield from self.render_response(str(status_code), response) def render_response(self, status_code, response): """Render OAS operation's response.""" yield f":statuscode {status_code}:" yield from indented( self._convert_markup(response["description"]).strip().splitlines() ) if "content" in response and status_code in self._response_examples_for: yield "" yield from indented( self.render_response_example(response["content"], status_code) ) if "headers" in response: yield "" for header_name, header_value in response["headers"].items(): # According to OpenAPI v3 specification, if a response header # is defined with the name 'Content-Type', it shall be ignored. if header_name.lower() == "content-type": continue yield f":resheader {header_name}:" if header_value.get("description"): yield from indented( self._convert_markup(header_value["description"]) .strip() .splitlines() ) schema = header_value.get("schema", {}) if "content" in header_value: # According to OpenAPI v3 spec, 'content' in this case may # have one and only one entry. Hence casting its values to # list is not expensive and should be acceptable. schema = list(header_value["content"].values())[0].get("schema", {}) markers = _get_markers_from_object(header_value, schema) if markers: markers = ", ".join(markers) yield f":resheadertype {header_name}: {markers}" def render_response_example(self, media_type, status_code): # OpenAPI 3.0 spec may contain more than one response media type, and # each media type may contain more than one example. Rendering all # invariants normally is not an option because the result will be hard # to read and follow. The best option we can go with at this moment is # to render first found example of either response media type. Users # should control what to render by putting recommended example first in # the list. content_type, example = next( _iterexamples( media_type, self._response_example_preference, self._generate_example_from_schema, ), (None, None), ) if content_type and example: example = example["value"] if not isinstance(example, str): example = json.dumps(example, indent=2) # According to OpenAPI v3 spec, status code may be a special value # - "default". It's not quite clear what to render in this case. # One possible option is to avoid rendering status code at all. # This option, however, suffers from broken code highlighting # because Pygments relies on the snippet to start with HTTP # protocol line. That said, probably the best we can do at the # moment is to render some generic status. if status_code == "default": status_code = "000" status_text = "Reason-Phrase" else: # According to OpenAPI v3 spec, status code may define a range # of response codes. Since we're talking about rendered example # here, we may show either code from range, but for the sake of # simplicity let's pick the first one. status_code = status_code.replace("XX", "00") status_text = http.client.responses.get(int(status_code), "-") yield ".. sourcecode:: http" yield "" yield f" HTTP/1.1 {status_code} {status_text}" yield f" Content-Type: {content_type}" yield "" yield from indented(example.splitlines()) def render_json_schema_description(self, schema, req_or_res): """Render JSON schema's description.""" def _resolve_combining_schema(schema): if "oneOf" in schema: # The part with merging is a vague one since I only found a # single 'oneOf' example where such merging was assumed, and no # explanations in the spec itself. merged_schema = schema.copy() merged_schema.update(merged_schema.pop("oneOf")[0]) return merged_schema elif "anyOf" in schema: # The part with merging is a vague one since I only found a # single 'oneOf' example where such merging was assumed, and no # explanations in the spec itself. merged_schema = schema.copy() merged_schema.update(merged_schema.pop("anyOf")[0]) return merged_schema elif "allOf" in schema: # Since the item is represented by all schemas from the array, # the best we can do is to render them all at once # sequentially. Please note, the only way the end result will # ever make sense is when all schemas from the array are of # object type. merged_schema = schema.copy() for item in merged_schema.pop("allOf"): merged_schema = _merge_mappings(merged_schema, copy.deepcopy(item)) return merged_schema elif "not" in schema: # Eh.. do nothing because I have no idea what can we do. return {} return schema def _traverse_schema(schema, name, is_required=False): schema_type = _get_schema_type(schema) if {"oneOf", "anyOf", "allOf"} & schema.keys(): # Since an item can represented by either or any schema from # the array of schema in case of `oneOf` and `anyOf` # respectively, the best we can do for them is to render the # first found variant. In other words, we are going to traverse # only a single schema variant and leave the rest out. This is # by design and it was decided so in order to keep produced # description clear and simple. yield from _traverse_schema(_resolve_combining_schema(schema), name) elif "not" in schema: yield name, {}, is_required elif schema_type == "object": if name: yield name, schema, is_required required = set(schema.get("required", [])) for key, value in schema.get("properties", {}).items(): # In case of the first recursion call, when 'name' is an # empty string, we should go with 'key' only in order to # avoid leading dot at the beginning. yield from _traverse_schema( value, f"{name}.{key}" if name else key, is_required=key in required, ) elif schema_type == "array": yield from _traverse_schema(schema["items"], f"{name}[]") elif "enum" in schema: yield name, schema, is_required elif schema_type is not None: yield name, schema, is_required schema = _resolve_combining_schema(schema) schema_type = _get_schema_type(schema) # On root level, httpdomain supports only 'object' and 'array' response # types. If it's something else, let's do not even try to render it. if schema_type not in {"object", "array"}: return # According to httpdomain's documentation, 'reqjsonobj' is an alias for # 'reqjson'. However, since the same name is passed as a type directive # internally, it actually can be used to specify its type. The same # goes for 'resjsonobj'. directives_map = { "req": { "object": ("reqjson", "reqjsonobj"), "array": ("reqjsonarr", "reqjsonarrtype"), }, "res": { "object": ("resjson", "resjsonobj"), "array": ("resjsonarr", "resjsonarrtype"), }, } # These httpdomain's fields always expect either JSON Object or JSON # Array. No primitive types are allowed as input. directive, typedirective = directives_map[req_or_res][schema_type] # Since we use JSON array specific httpdomain directives if a schema # we're about to render is an array, there's no need to render that # array in the first place. if schema_type == "array": schema = schema["items"] # Even if a root element is an array, items it contain must not be # of a primitive types. if _get_schema_type(schema) not in {"object", "array"}: return for name, schema, is_required in _traverse_schema(schema, ""): yield f":{directive} {name}:" if schema.get("description"): yield from indented( self._convert_markup(schema["description"]).strip().splitlines() ) markers = _get_markers_from_object({}, schema) if is_required: markers.append("required") if markers: markers = ", ".join(markers) yield f":{typedirective} {name}: {markers}" openapi-0.8.4/sphinxcontrib/openapi/renderers/_httpdomain_old.py000066400000000000000000000045601456264404500252310ustar00rootroot00000000000000"""Here lies still breathing and only renderer implementation.""" from docutils.parsers.rst import directives from . import abc from .. import openapi20, openapi30, openapi31, utils class HttpdomainOldRenderer(abc.RestructuredTextRenderer): option_spec = { # A list of endpoints to be rendered. Endpoints must be whitespace # delimited. "paths": lambda s: s.split(), # Regular expression patterns to includes/excludes endpoints to/from # rendering. Similar to paths, the patterns must be whitespace # delimited. "include": lambda s: s.split(), "exclude": lambda s: s.split(), # Endpoints to be included based on HTTP method names. "methods": lambda s: s.split(), # Render the request body structure when passed. "request": directives.flag, # Render request/response examples when passed. "examples": directives.flag, # render examples when passed # Group endpoints by tags when passed. By default, no grouping is # applied and endpoints are rendered in the order they met in spec. "group": directives.flag, # Markup format to render OpenAPI descriptions. "format": str, } def __init__(self, state, options): self._state = state self._options = options def render_restructuredtext_markup(self, spec): # OpenAPI spec may contain JSON references, common properties, etc. # Trying to render the spec "As Is" will require to put multiple if-s # around the code. In order to simplify rendering flow, let's make it # have only one (expected) schema, i.e. normalize it. utils.normalize_spec(spec, **self._options) # We support OpenAPI 2.0 (f.k.a. Swagger), OpenAPI 3.0 and OpenAPI 3.1, # so determine which version we are parsing here. spec_version = spec.get("openapi", spec.get("swagger", "2.0")) if spec_version.startswith("2."): openapihttpdomain = openapi20.openapihttpdomain elif spec_version.startswith("3.0."): openapihttpdomain = openapi30.openapihttpdomain elif spec_version.startswith("3.1."): openapihttpdomain = openapi31.openapihttpdomain else: raise ValueError("Unsupported OpenAPI version (%s)" % spec_version) yield from openapihttpdomain(spec, **self._options) openapi-0.8.4/sphinxcontrib/openapi/renderers/abc.py000066400000000000000000000027151456264404500226120ustar00rootroot00000000000000"""Abstract Base Classes (ABCs) for OpenAPI renderers.""" import abc from docutils import nodes from docutils.statemachine import ViewList from sphinx.util.nodes import nested_parse_with_titles class Renderer(metaclass=abc.ABCMeta): """Base class for OpenAPI renderers.""" def __init__(self, state, options): self._state = state self._options = options @property @abc.abstractmethod def option_spec(self): """Renderer options and their converting functions.""" @abc.abstractmethod def render(self, spec): """Render a given OpenAPI spec.""" class RestructuredTextRenderer(Renderer): """Base class for reStructuredText OpenAPI renderers. Docutils DOM manipulation is quite a tricky task that requires passing dozen arguments around. Because of that a lot of Sphinx extensions instead of constructing DOM nodes directly produce and parse reStructuredText. This Sphinx extension is not an exception, and that's why this class exists. It's a convenient extension of :class:`Renderer` that converts produced markup text into docutils DOM elements. """ def render(self, spec): viewlist = ViewList() for line in self.render_restructuredtext_markup(spec): viewlist.append(line, "") node = nodes.section() node.document = self._state.document nested_parse_with_titles(self._state, viewlist, node) return node.children openapi-0.8.4/sphinxcontrib/openapi/schema_utils.py000066400000000000000000000105361456264404500225540ustar00rootroot00000000000000"""OpenAPI schema utility functions.""" from io import StringIO _DEFAULT_EXAMPLES = { "string": "string", "integer": 1, "number": 1.0, "boolean": True, "array": [], } _DEFAULT_STRING_EXAMPLES = { "date": "2020-01-01", "date-time": "2020-01-01T01:01:01Z", "password": "********", "byte": "QG1pY2hhZWxncmFoYW1ldmFucw==", "ipv4": "127.0.0.1", "ipv6": "::1", } def example_from_schema(schema): """ Generates an example request/response body from the provided schema. >>> schema = { ... "type": "object", ... "required": ["id", "name"], ... "properties": { ... "id": { ... "type": "integer", ... "format": "int64" ... }, ... "name": { ... "type": "string", ... "example": "John Smith" ... }, ... "tag": { ... "type": "string" ... } ... } ... } >>> example = example_from_schema(schema) >>> assert example == { ... "id": 1, ... "name": "John Smith", ... "tag": "string" ... } """ # If an example was provided then we use that if "example" in schema: return schema["example"] elif "oneOf" in schema: return example_from_schema(schema["oneOf"][0]) elif "anyOf" in schema: return example_from_schema(schema["anyOf"][0]) elif "allOf" in schema: # Combine schema examples example = {} for sub_schema in schema["allOf"]: example.update(example_from_schema(sub_schema)) return example elif "enum" in schema: return schema["enum"][0] elif "type" not in schema: # Any type return _DEFAULT_EXAMPLES["integer"] elif schema["type"] == "object" or "properties" in schema: example = {} for prop, prop_schema in schema.get("properties", {}).items(): example[prop] = example_from_schema(prop_schema) return example elif schema["type"] == "array": items = schema["items"] min_length = schema.get("minItems", 0) max_length = schema.get("maxItems", max(min_length, 2)) assert min_length <= max_length # Try generate at least 2 example array items gen_length = min(2, max_length) if min_length <= 2 else min_length example_items = [] if items == {}: # Any-type arrays example_items.extend(_DEFAULT_EXAMPLES.values()) elif isinstance(items, dict) and "oneOf" in items: # Mixed-type arrays example_items.append(_DEFAULT_EXAMPLES[sorted(items["oneOf"])[0]]) else: example_items.append(example_from_schema(items)) # Generate array containing example_items and satisfying min_length and max_length return [example_items[i % len(example_items)] for i in range(gen_length)] elif schema["type"] == "string": example_string = _DEFAULT_STRING_EXAMPLES.get( schema.get("format", None), _DEFAULT_EXAMPLES["string"] ) min_length = schema.get("minLength", 0) max_length = schema.get("maxLength", max(min_length, len(example_string))) gen_length = ( min(len(example_string), max_length) if min_length <= len(example_string) else min_length ) assert 0 <= min_length <= max_length if min_length <= len(example_string) <= max_length: return example_string else: example_builder = StringIO() for i in range(gen_length): example_builder.write(example_string[i % len(example_string)]) example_builder.seek(0) return example_builder.read() elif schema["type"] in ("integer", "number"): example = _DEFAULT_EXAMPLES[schema["type"]] if "minimum" in schema and "maximum" in schema: # Take average example = schema["minimum"] + (schema["maximum"] - schema["minimum"]) / 2 elif "minimum" in schema and example <= schema["minimum"]: example = schema["minimum"] + 1 elif "maximum" in schema and example >= schema["maximum"]: example = schema["maximum"] - 1 return float(example) if schema["type"] == "number" else int(example) else: return _DEFAULT_EXAMPLES[schema["type"]] openapi-0.8.4/sphinxcontrib/openapi/utils.py000066400000000000000000000075161456264404500212400ustar00rootroot00000000000000""" sphinxcontrib.openapi.utils --------------------------- Common functionality shared across the various renderers. :copyright: (c) 2016, Ihor Kalnytskyi. :license: BSD, see LICENSE for details. """ from __future__ import unicode_literals import collections import collections.abc from contextlib import closing import jsonschema import yaml import sphinx_mdinclude from urllib.parse import urlsplit from urllib.request import urlopen import os.path class OpenApiRefResolver(jsonschema.RefResolver): """ Overrides resolve_remote to support both YAML and JSON OpenAPI schemas. """ try: import requests _requests = requests except ImportError: _requests = None def resolve_remote(self, uri): scheme, _, path, _, _ = urlsplit(uri) _, extension = os.path.splitext(path) if extension not in [".yml", ".yaml"] or scheme in self.handlers: return super(OpenApiRefResolver, self).resolve_remote(uri) if scheme in [u"http", u"https"] and self._requests: response = self._requests.get(uri) result = yaml.safe_load(response.content) else: # Otherwise, pass off to urllib and assume utf-8 with closing(urlopen(uri)) as url: response = url.read().decode("utf-8") result = yaml.safe_load(response) if self.cache_remote: self.store[uri] = result return result def _resolve_refs(uri, spec): """Resolve JSON references in a given dictionary. OpenAPI spec may contain JSON references to its nodes or external sources, so any attempt to rely that there's some expected attribute in the spec may fail. So we need to resolve JSON references before we use it (i.e. replace with referenced object). For details see: https://tools.ietf.org/html/draft-pbryan-zyp-json-ref-02 The input spec is modified in-place despite being returned from the function. """ resolver = OpenApiRefResolver(uri, spec) def _do_resolve(node, seen=[]): if isinstance(node, collections.abc.Mapping) and '$ref' in node: ref = node['$ref'] with resolver.resolving(ref) as resolved: if ref in seen: return {type: 'object'} # return a distinct object for recursive data type return _do_resolve(resolved, seen + [ref]) # might have other references elif isinstance(node, collections.abc.Mapping): for k, v in node.items(): node[k] = _do_resolve(v, seen) elif isinstance(node, (list, tuple)): for i in range(len(node)): node[i] = _do_resolve(node[i], seen) return node return _do_resolve(spec) def normalize_spec(spec, **options): # OpenAPI spec may contain JSON references, so we need resolve them # before we access the actual values trying to build an httpdomain # markup. Since JSON references may be relative, it's crucial to # pass a document URI in order to properly resolve them. spec = _resolve_refs(options.get('uri', ''), spec) # OpenAPI spec may contain common endpoint's parameters top-level. # In order to do not place if-s around the code to handle special # cases, let's normalize the spec and push common parameters inside # endpoints definitions. for endpoint in spec.get('paths', {}).values(): parameters = endpoint.pop('parameters', []) for method in endpoint.values(): method.setdefault('parameters', []) method['parameters'].extend(parameters) def get_text_converter(options): """Decide on a text converter for prose.""" if 'format' in options: if options['format'] == 'markdown': return sphinx_mdinclude.convert # No conversion needed. return lambda s: s openapi-0.8.4/tests/000077500000000000000000000000001456264404500143325ustar00rootroot00000000000000openapi-0.8.4/tests/OpenAPI-Specification/000077500000000000000000000000001456264404500203435ustar00rootroot00000000000000openapi-0.8.4/tests/conftest.py000066400000000000000000000050201456264404500165260ustar00rootroot00000000000000import os import pathlib import textwrap import pytest import yaml from sphinx.application import Sphinx from sphinxcontrib.openapi import utils _testspecs_dir = pathlib.Path(os.path.dirname(__file__), "testspecs") _testspecs = [str(path.relative_to(_testspecs_dir)) for path in _testspecs_dir.glob("*/*")] def pytest_addoption(parser): parser.addoption("--regenerate-rendered-specs", action="store_true") def pytest_collection_modifyitems(items): items_new = [] for item in items: has_mark = bool(list(item.iter_markers(name="regenerate_rendered_specs"))) if any( [ item.config.getoption("--regenerate-rendered-specs") and has_mark, not item.config.getoption("--regenerate-rendered-specs") and not has_mark, ] ): items_new.append(item) items[:] = items_new def _format_option_raw(key, val): if isinstance(val, bool) and val: return ':%s:' % key return ':%s: %s' % (key, val) @pytest.fixture(scope='function') def run_sphinx(tmpdir): src = tmpdir.ensure('src', dir=True) out = tmpdir.ensure('out', dir=True) def run(spec, options={}): options_raw = '\n'.join([ ' %s' % _format_option_raw(key, val) for key, val in options.items()]) src.join('conf.py').write_text( textwrap.dedent(''' import os project = 'sphinxcontrib-openapi-test' copyright = '2017, Ihor Kalnytskyi' extensions = ['sphinxcontrib.openapi'] source_suffix = '.rst' master_doc = 'index' '''), encoding='utf-8') src.join('index.rst').write_text( '.. openapi:: %s\n%s' % (spec, options_raw), encoding='utf-8') Sphinx( srcdir=src.strpath, confdir=src.strpath, outdir=out.strpath, doctreedir=out.join('.doctrees').strpath, buildername='html' ).build() yield run @pytest.fixture(scope="function") def get_testspec(): def get_testspec(*args, encoding="utf-8", resolve_refs=True): with _testspecs_dir.joinpath(*args).open(encoding=encoding) as f: spec = yaml.safe_load(f) if resolve_refs: spec = utils._resolve_refs("", spec) return spec return get_testspec @pytest.fixture(scope="function", params=_testspecs) def testspec(request, get_testspec): return request.param, get_testspec(request.param) openapi-0.8.4/tests/lib2to3/000077500000000000000000000000001456264404500156105ustar00rootroot00000000000000openapi-0.8.4/tests/lib2to3/conftest.py000066400000000000000000000003251456264404500200070ustar00rootroot00000000000000import textwrap import pytest import yaml @pytest.fixture(scope="function") def oas_fragment(): def oas_fragment(fragment): return yaml.safe_load(textwrap.dedent(fragment)) return oas_fragment openapi-0.8.4/tests/lib2to3/test_convert.py000066400000000000000000000277371456264404500207210ustar00rootroot00000000000000""".convert() test suite.""" import sphinxcontrib.openapi._lib2to3 as lib2to3 def test_minimal(oas_fragment): converted = lib2to3.convert( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 paths: /test: get: responses: '200': description: a response description """ ) ) assert converted == oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 paths: /test: get: responses: '200': description: a response description """ ) def test_complete(oas_fragment): converted = lib2to3.convert( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 tags: - tag_a externalDocs: https://docs.example.com/ paths: /test: get: responses: '200': description: a response description """ ) ) assert converted == oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 tags: - tag_a externalDocs: https://docs.example.com/ paths: /test: get: responses: '200': description: a response description """ ) def test_servers_complete(oas_fragment): converted = lib2to3.convert( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 host: example.com basePath: /v1 schemes: - https paths: /test: get: responses: '200': description: a response description """ ), ) assert converted == oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 servers: - url: https://example.com/v1 paths: /test: get: responses: '200': description: a response description """ ) def test_servers_host_only(oas_fragment): converted = lib2to3.convert( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 host: example.com paths: /test: get: responses: '200': description: a response description """ ), ) assert converted == oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 servers: - url: example.com paths: /test: get: responses: '200': description: a response description """ ) def test_servers_basepath_only(oas_fragment): converted = lib2to3.convert( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 basePath: /v1 paths: /test: get: responses: '200': description: a response description """ ), ) assert converted == oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 servers: - url: /v1 paths: /test: get: responses: '200': description: a response description """ ) def test_servers_schemes_multiple(oas_fragment): converted = lib2to3.convert( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 host: example.com schemes: - http - https paths: /test: get: responses: '200': description: a response description """ ), ) assert converted == oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 servers: - url: http://example.com - url: https://example.com paths: /test: get: responses: '200': description: a response description """ ) def test_servers_schemes_from_operation(oas_fragment): converted = lib2to3.convert( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 host: example.com schemes: - http paths: /test: get: schemes: - ws responses: '200': description: a response description """ ), ) assert converted == oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 servers: - url: http://example.com - url: ws://example.com paths: /test: get: responses: '200': description: a response description """ ) def test_consumes(oas_fragment): converted = lib2to3.convert( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 consumes: - application/json paths: /test: post: parameters: - in: query name: marker type: string - in: body name: payload schema: type: string responses: '201': description: a response description """ ) ) assert converted == oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 paths: /test: post: parameters: - in: query name: marker schema: type: string requestBody: content: application/json: schema: type: string responses: '201': description: a response description """ ) def test_consumes_operation_override(oas_fragment): converted = lib2to3.convert( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 consumes: - application/xml paths: /test: post: consumes: - application/json parameters: - in: query name: marker type: string - in: body name: payload schema: type: string responses: '201': description: a response description """ ) ) assert converted == oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 paths: /test: post: parameters: - in: query name: marker schema: type: string requestBody: content: application/json: schema: type: string responses: '201': description: a response description """ ) def test_produces(oas_fragment): converted = lib2to3.convert( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 produces: - application/json paths: /test: get: responses: '200': schema: items: format: int32 type: integer type: array description: a response description """ ) ) assert converted == oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 paths: /test: get: responses: '200': content: application/json: schema: items: format: int32 type: integer type: array description: a response description """ ) def test_produces_operation_override(oas_fragment): converted = lib2to3.convert( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 produces: - application/xml paths: /test: get: produces: - application/json responses: '200': schema: items: format: int32 type: integer type: array description: a response description """ ) ) assert converted == oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 paths: /test: get: responses: '200': content: application/json: schema: items: format: int32 type: integer type: array description: a response description """ ) def test_vendor_extensions(oas_fragment): converted = lib2to3.convert( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 paths: /test: get: responses: '200': description: a response description x-vendor-ext: vendor-ext """ ) ) assert converted == oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 paths: /test: get: responses: '200': description: a response description x-vendor-ext: vendor-ext """ ) openapi-0.8.4/tests/lib2to3/test_convert_operation.py000066400000000000000000000157421456264404500227720ustar00rootroot00000000000000""".convert_operation() test suite.""" import pytest import sphinxcontrib.openapi._lib2to3 as lib2to3 @pytest.fixture(scope="function") def convert_operation(oas_fragment): def _wrapper(operation): oas2 = oas_fragment( """ swagger: "2.0" info: title: An example spec version: "1.0" paths: /test: get: responses: '200': description: a response description """ ) oas2["paths"]["/test"]["get"] = operation oas3 = lib2to3.convert(oas2) return oas3["paths"]["/test"]["get"] return _wrapper def test_minimal(convert_operation, oas_fragment): converted = convert_operation( oas_fragment( """ responses: '200': description: a response description """ ), ) assert converted == oas_fragment( """ responses: '200': description: a response description """ ) def test_complete(convert_operation, oas_fragment): converted = convert_operation( oas_fragment( """ tags: - tag_a - tag_b summary: an operation summary description: an operation description externalDocs: https://docs.example.com/ operationId: myOperation produces: - application/json parameters: - in: header name: token type: string - in: path name: username required: true type: string - in: query name: id type: string deprecated: false responses: '200': description: a response description schema: items: format: int32 type: integer type: array """ ), ) assert converted == oas_fragment( """ tags: - tag_a - tag_b summary: an operation summary description: an operation description externalDocs: https://docs.example.com/ operationId: myOperation parameters: - in: header name: token schema: type: string - in: path name: username required: true schema: type: string - in: query name: id schema: type: string deprecated: false responses: '200': content: application/json: schema: items: format: int32 type: integer type: array description: a response description """ ) def test_request_body(convert_operation, oas_fragment): converted = convert_operation( oas_fragment( """ description: an operation description consumes: - application/json parameters: - in: path name: username required: true type: string - in: body name: inventory schema: type: object properties: t-shirt: type: boolean required: true responses: '200': description: a response description """ ), ) assert converted == oas_fragment( """ description: an operation description parameters: - in: path name: username required: true schema: type: string requestBody: content: application/json: schema: type: object properties: t-shirt: type: boolean required: true responses: '200': description: a response description """ ) def test_request_body_formdata(convert_operation, oas_fragment): converted = convert_operation( oas_fragment( """ description: an operation description consumes: - application/x-www-form-urlencoded parameters: - in: path name: username required: true type: string - in: formData name: t-shirt type: boolean responses: '200': description: a response description """ ), ) assert converted == oas_fragment( """ description: an operation description parameters: - in: path name: username required: true schema: type: string requestBody: content: application/x-www-form-urlencoded: schema: type: object properties: t-shirt: type: boolean responses: '200': description: a response description """ ) def test_only_request_body(convert_operation, oas_fragment): converted = convert_operation( oas_fragment( """ description: an operation description consumes: - application/json parameters: - in: body name: inventory schema: type: object properties: t-shirt: type: boolean required: true responses: '200': description: a response description """ ), ) assert converted == oas_fragment( """ description: an operation description requestBody: content: application/json: schema: type: object properties: t-shirt: type: boolean required: true responses: '200': description: a response description """ ) def test_vendor_extensions(convert_operation, oas_fragment): converted = convert_operation( oas_fragment( """ responses: '200': description: a response description x-vendor-ext: vendor-ext """ ), ) assert converted == oas_fragment( """ responses: '200': description: a response description x-vendor-ext: vendor-ext """ ) openapi-0.8.4/tests/lib2to3/test_convert_parameter.py000066400000000000000000000215111456264404500227410ustar00rootroot00000000000000""".convert_parameter() test suite.""" import pytest import sphinxcontrib.openapi._lib2to3 as lib2to3 @pytest.fixture(scope="function") def convert_parameter(oas_fragment): def _wrapper(parameter): oas2 = oas_fragment( """ swagger: "2.0" info: title: An example spec version: "1.0" paths: /test: get: responses: '200': description: a response description """ ) oas2["paths"]["/test"]["get"]["parameters"] = [parameter] oas3 = lib2to3.convert(oas2) return oas3["paths"]["/test"]["get"]["parameters"][0] return _wrapper def test_in_header_complete(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ description: token to be passed as a header in: header items: format: int64 type: integer name: token required: true type: array """ ) ) assert converted == oas_fragment( """ description: token to be passed as a header in: header name: token required: true schema: items: format: int64 type: integer type: array """ ) def test_in_path_complete(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ description: username to fetch in: path name: username required: true type: string """ ), ) assert converted == oas_fragment( """ description: username to fetch in: path name: username required: true schema: type: string """ ) def test_in_query_complete(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ description: ID of the object to fetch in: query items: type: string name: id required: false type: array """ ), ) assert converted == oas_fragment( """ description: ID of the object to fetch in: query name: id required: false schema: items: type: string type: array """ ) def test_in_header_minimal(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ in: header name: token type: string """ ), ) assert converted == oas_fragment( """ in: header name: token schema: type: string """ ) def test_in_path_minimal(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ in: path name: username required: true type: string """ ), ) assert converted == oas_fragment( """ in: path name: username required: true schema: type: string """ ) def test_in_query_minimal(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ in: query name: id type: string """ ), ) assert converted == oas_fragment( """ in: query name: id schema: type: string """ ) def test_collectionFormat_is_csv_path(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ collectionFormat: csv in: path items: type: string name: username required: true type: array """ ), ) assert converted == oas_fragment( """ in: path name: username required: true schema: items: type: string type: array style: simple """ ) def test_collectionFormat_is_csv_header(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ collectionFormat: csv in: header items: type: string name: username type: array """ ), ) assert converted == oas_fragment( """ in: header name: username schema: items: type: string type: array style: simple """ ) def test_collectionFormat_is_csv(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ collectionFormat: csv in: query items: type: string name: id type: array """ ), ) assert converted == oas_fragment( """ explode: false in: query name: id schema: items: type: string type: array style: form """ ) def test_collectionFormat_is_multi(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ collectionFormat: multi in: query items: type: string name: id type: array """ ), ) assert converted == oas_fragment( """ explode: true in: query name: id schema: items: type: string type: array style: form """ ) def test_collectionFormat_is_ssv(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ collectionFormat: ssv in: query items: type: string name: id type: array """ ), ) assert converted == oas_fragment( """ in: query name: id schema: items: type: string type: array style: spaceDelimited """ ) def test_collectionFormat_is_pipes(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ collectionFormat: pipes in: query items: type: string name: id type: array """ ), ) assert converted == oas_fragment( """ in: query name: id schema: items: type: string type: array style: pipeDelimited """ ) def test_collectionFormat_is_tsv(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ collectionFormat: tsv in: query items: type: string name: id type: array """ ), ) assert converted == oas_fragment( """ in: query name: id schema: items: type: string type: array """ ) def test_in_header_vendor_extensions(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ in: header name: token type: string x-vendor-ext: vendor-ext """ ), ) assert converted == oas_fragment( """ in: header name: token schema: type: string x-vendor-ext: vendor-ext """ ) def test_in_path_vendor_extensions(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ in: path name: username required: true type: string x-vendor-ext: vendor-ext """ ), ) assert converted == oas_fragment( """ in: path name: username required: true schema: type: string x-vendor-ext: vendor-ext """ ) def test_in_query_vendor_extensions(convert_parameter, oas_fragment): converted = convert_parameter( oas_fragment( """ in: query name: id type: string x-vendor-ext: vendor-ext """ ), ) assert converted == oas_fragment( """ in: query name: id schema: type: string x-vendor-ext: vendor-ext """ ) openapi-0.8.4/tests/lib2to3/test_convert_parameters.py000066400000000000000000000043711456264404500231310ustar00rootroot00000000000000""".convert_parameters() test suite.""" import pytest import sphinxcontrib.openapi._lib2to3 as lib2to3 _MISSING = object() @pytest.fixture(scope="function") def convert_parameters(oas_fragment): def _wrapper(parameters): oas2 = oas_fragment( """ swagger: "2.0" info: title: An example spec version: "1.0" paths: /test: get: responses: '200': description: a response description """ ) oas2["paths"]["/test"]["get"]["parameters"] = parameters oas3 = lib2to3.convert(oas2) return oas3["paths"]["/test"]["get"].get("parameters", _MISSING) return _wrapper def test_header_path_query(convert_parameters, oas_fragment): converted = convert_parameters( oas_fragment( """ - in: header name: token type: string - in: path name: username required: true type: string - in: query name: id type: string """ ), ) assert converted == oas_fragment( """ - in: header name: token schema: type: string - in: path name: username required: true schema: type: string - in: query name: id schema: type: string """ ) def test_body_is_ignored(convert_parameters, oas_fragment): converted = convert_parameters( oas_fragment( """ - description: user to add to the system in: body name: user required: true schema: $ref: '#/definitions/User' """ ), ) assert converted is _MISSING def test_formData_is_ignored(convert_parameters, oas_fragment): converted = convert_parameters( oas_fragment( """ - description: The avatar of the user in: formData name: avatar type: file """ ), ) assert converted is _MISSING openapi-0.8.4/tests/lib2to3/test_convert_path.py000066400000000000000000000130371456264404500217210ustar00rootroot00000000000000""".convert_path() test suite.""" import pytest import sphinxcontrib.openapi._lib2to3 as lib2to3 @pytest.fixture(scope="function") def convert_path(oas_fragment): def _wrapper(path): oas2 = oas_fragment( """ swagger: "2.0" info: title: An example spec version: "1.0" paths: /test: get: responses: '200': description: a response description """ ) oas2["paths"]["/test"] = path oas3 = lib2to3.convert(oas2) return oas3["paths"]["/test"] return _wrapper @pytest.mark.parametrize( "method", ["get", "put", "post", "delete", "options", "head", "patch"] ) def test_minimal(convert_path, oas_fragment, method): converted = convert_path( oas_fragment( f""" {method}: responses: '200': description: a response description """ ), ) assert converted == oas_fragment( f""" {method}: responses: '200': description: a response description """ ) def test_complete(convert_path, oas_fragment): converted = convert_path( oas_fragment( """ parameters: - in: path name: username required: true type: string get: tags: - tag_a - tag_b summary: an operation summary description: an operation description externalDocs: https://docs.example.com/ operationId: myOperation produces: - application/json parameters: - in: header name: token type: string - in: query name: id type: string responses: '200': schema: items: format: int32 type: integer type: array description: a response description """ ), ) assert converted == oas_fragment( """ parameters: - in: path name: username schema: type: string required: true get: tags: - tag_a - tag_b summary: an operation summary description: an operation description externalDocs: https://docs.example.com/ operationId: myOperation parameters: - in: header name: token schema: type: string - in: query name: id schema: type: string responses: '200': content: application/json: schema: items: format: int32 type: integer type: array description: a response description """ ) def test_shared_parameters(convert_path, oas_fragment): converted = convert_path( oas_fragment( """ parameters: - in: path name: username required: true type: string get: parameters: - in: header name: token type: string - in: query name: id type: string responses: '200': description: a response description """ ), ) assert converted == oas_fragment( """ parameters: - in: path name: username schema: type: string required: true get: parameters: - in: header name: token schema: type: string - in: query name: id schema: type: string responses: '200': description: a response description """ ) def test_multiple(convert_path, oas_fragment): converted = convert_path( oas_fragment( """ post: responses: '201': description: a post response description get: responses: '200': description: a get response description """ ), ) assert converted == oas_fragment( """ post: responses: '201': description: a post response description get: responses: '200': description: a get response description """ ) def test_vendor_extensions(convert_path, oas_fragment): converted = convert_path( oas_fragment( """ get: responses: '200': description: a response description x-vendor-ext: vendor-ext """ ), ) assert converted == oas_fragment( """ get: responses: '200': description: a response description x-vendor-ext: vendor-ext """ ) openapi-0.8.4/tests/lib2to3/test_convert_paths.py000066400000000000000000000112341456264404500221010ustar00rootroot00000000000000""".convert_paths() test suite.""" import pytest import sphinxcontrib.openapi._lib2to3 as lib2to3 @pytest.fixture(scope="function") def convert_paths(oas_fragment): def _wrapper(paths): oas2 = oas_fragment( """ swagger: "2.0" info: title: An example spec version: "1.0" paths: /test: get: responses: '200': description: a response description """ ) oas2["paths"] = paths oas3 = lib2to3.convert(oas2) return oas3["paths"] return _wrapper def test_minimal(convert_paths, oas_fragment): converted = convert_paths( oas_fragment( """ /test: get: responses: '200': description: a response description """ ), ) assert converted == oas_fragment( """ /test: get: responses: '200': description: a response description """ ) def test_complete(convert_paths, oas_fragment): pass converted = convert_paths( oas_fragment( """ /{username}: parameters: - in: path name: username required: true type: string get: tags: - tag_a - tag_b summary: an operation summary description: an operation description externalDocs: https://docs.example.com/ operationId: myOperation produces: - application/json parameters: - in: header name: token type: string - in: query name: id type: string responses: '200': schema: items: format: int32 type: integer type: array description: a response description """ ), ) assert converted == oas_fragment( """ /{username}: parameters: - in: path name: username schema: type: string required: true get: tags: - tag_a - tag_b summary: an operation summary description: an operation description externalDocs: https://docs.example.com/ operationId: myOperation parameters: - in: header name: token schema: type: string - in: query name: id schema: type: string responses: '200': content: application/json: schema: items: format: int32 type: integer type: array description: a response description """ ) def test_multiple(convert_paths, oas_fragment): converted = convert_paths( oas_fragment( """ /test: get: responses: '200': description: a test response description /eggs: post: responses: '201': description: an eggs response description """ ), ) assert converted == oas_fragment( """ /test: get: responses: '200': description: a test response description /eggs: post: responses: '201': description: an eggs response description """ ) def test_vendor_extensions(convert_paths, oas_fragment): converted = convert_paths( oas_fragment( """ /test: get: responses: '200': description: a response description x-vendor-ext: vendor-ext """ ), ) assert converted == oas_fragment( """ /test: get: responses: '200': description: a response description x-vendor-ext: vendor-ext """ ) openapi-0.8.4/tests/lib2to3/test_convert_request_body.py000066400000000000000000000073231456264404500234730ustar00rootroot00000000000000""".convert_request_body() test suite.""" import pytest import sphinxcontrib.openapi._lib2to3 as lib2to3 _MISSING = object() @pytest.fixture(scope="function") def convert_request_body(oas_fragment): def _wrapper(operation_fragment): oas2 = oas_fragment( """ swagger: "2.0" info: title: An example spec version: "1.0" paths: /test: get: responses: '200': description: a response description """ ) oas2["paths"]["/test"]["get"].update(operation_fragment) oas3 = lib2to3.convert(oas2) return oas3["paths"]["/test"]["get"].get("requestBody", _MISSING) return _wrapper def test_minimal(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ consumes: - application/json parameters: - in: body name: user schema: $ref: '#/definitions/User' """ ), ) assert converted == oas_fragment( """ content: application/json: schema: $ref: '#/definitions/User' """ ) def test_complete(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ consumes: - application/json parameters: - description: user to add to the system in: body name: user required: true schema: $ref: '#/definitions/User' """ ), ) assert converted == oas_fragment( """ content: application/json: schema: $ref: '#/definitions/User' description: user to add to the system required: true """ ) def test_no_consumes(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ parameters: - in: body name: user schema: $ref: '#/definitions/User' """ ), ) assert converted == oas_fragment( """ content: '*/*': schema: $ref: '#/definitions/User' """ ) def test_header_path_query_are_ignored(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ parameters: - in: header name: token type: string - in: path name: username required: true type: string - in: query name: id type: string """ ), ) assert converted is _MISSING def test_body_and_others(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ parameters: - in: header name: token type: string - in: path name: username required: true type: string - in: body name: user schema: $ref: '#/definitions/User' - in: query name: id type: string """ ), ) assert converted == oas_fragment( """ content: '*/*': schema: $ref: '#/definitions/User' """ ) openapi-0.8.4/tests/lib2to3/test_convert_request_body_formdata.py000066400000000000000000000223151456264404500253460ustar00rootroot00000000000000""".convert_request_body() test suite.""" import pytest import sphinxcontrib.openapi._lib2to3 as lib2to3 @pytest.fixture(scope="function") def convert_request_body(oas_fragment): def _wrapper(operation_fragment): oas2 = oas_fragment( """ swagger: "2.0" info: title: An example spec version: "1.0" paths: /test: get: responses: '200': description: a response description """ ) oas2["paths"]["/test"]["get"].update(operation_fragment) oas3 = lib2to3.convert(oas2) return oas3["paths"]["/test"]["get"]["requestBody"] return _wrapper def test_minimal(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ parameters: - in: formData name: user type: string """ ), ) assert converted == oas_fragment( """ content: application/x-www-form-urlencoded: schema: properties: user: type: string type: object """ ) def test_complete(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ parameters: - description: a name of the user in: formData name: user type: string required: true """ ), ) assert converted == oas_fragment( """ content: application/x-www-form-urlencoded: schema: type: object properties: user: description: a name of the user type: string required: [user] """ ) def test_complex_schema(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ parameters: - format: int32 in: formData maximum: 100 minimum: 5 name: age type: integer """ ), ) assert converted == oas_fragment( """ content: application/x-www-form-urlencoded: schema: properties: age: format: int32 maximum: 100 minimum: 5 type: integer type: object """ ) def test_consumes_urlencoded(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ consumes: - application/x-www-form-urlencoded parameters: - description: a name of the user in: formData name: user type: string """ ), ) assert converted == oas_fragment( """ content: application/x-www-form-urlencoded: schema: properties: user: description: a name of the user type: string type: object """ ) def test_consumes_form_data(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ consumes: - multipart/form-data parameters: - description: a name of the user in: formData name: user type: string """ ), ) assert converted == oas_fragment( """ content: multipart/form-data: schema: properties: user: description: a name of the user type: string type: object """ ) def test_consumes_urlencoded_and_form_data(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ consumes: - application/x-www-form-urlencoded - multipart/form-data parameters: - description: a name of the user in: formData name: user type: string """ ), ) assert converted == oas_fragment( """ content: application/x-www-form-urlencoded: schema: properties: user: description: a name of the user type: string type: object multipart/form-data: schema: properties: user: description: a name of the user type: string type: object """ ) def test_required(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ parameters: - description: a name of the user in: formData name: user required: true type: string """ ), ) assert converted == oas_fragment( """ content: application/x-www-form-urlencoded: schema: properties: user: description: a name of the user type: string required: - user type: object """ ) def test_multiple(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ parameters: - description: a name of the user in: formData name: user type: string - description: a status of the user in: formData name: status type: string """ ), ) assert converted == oas_fragment( """ content: application/x-www-form-urlencoded: schema: properties: status: description: a status of the user type: string user: description: a name of the user type: string type: object """ ) def test_type_file_implicit_form_data(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ parameters: - description: a user pic in: formData name: userpic type: file """ ), ) assert converted == oas_fragment( """ content: multipart/form-data: schema: properties: userpic: description: a user pic type: file type: object """ ) def test_type_file_consumes_form_data(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ consumes: - multipart/form-data parameters: - description: a user pic in: formData name: userpic type: file """ ), ) assert converted == oas_fragment( """ content: multipart/form-data: schema: properties: userpic: description: a user pic type: file type: object """ ) def test_consumes_json_and_urlencoded(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ consumes: - application/json - application/x-www-form-urlencoded parameters: - description: a name of the user in: formData name: user type: string """ ), ) assert converted == oas_fragment( """ content: application/x-www-form-urlencoded: schema: properties: user: description: a name of the user type: string type: object """ ) def test_consumes_json_and_form_data(convert_request_body, oas_fragment): converted = convert_request_body( oas_fragment( """ consumes: - application/json - multipart/form-data parameters: - description: a name of the user in: formData name: user type: string """ ), ) assert converted == oas_fragment( """ content: multipart/form-data: schema: properties: user: description: a name of the user type: string type: object """ ) openapi-0.8.4/tests/lib2to3/test_convert_response.py000066400000000000000000000240331456264404500226210ustar00rootroot00000000000000""".convert_response() test suite.""" import pytest import sphinxcontrib.openapi._lib2to3 as lib2to3 @pytest.fixture(scope="function") def convert_response(oas_fragment): def _wrapper(response, produces): oas2 = oas_fragment( """ swagger: "2.0" info: title: An example spec version: "1.0" paths: /test: get: responses: '200': description: a response description """ ) oas2["paths"]["/test"]["get"]["responses"]["200"] = response oas2["paths"]["/test"]["get"]["produces"] = produces oas3 = lib2to3.convert(oas2) return oas3["paths"]["/test"]["get"]["responses"]["200"] return _wrapper def test_minimal(convert_response, oas_fragment): converted = convert_response( oas_fragment( """ description: a response description """ ), produces=["application/json"], ) assert converted == oas_fragment( """ description: a response description """ ) def test_schema(convert_response, oas_fragment): converted = convert_response( oas_fragment( """ description: a response description schema: items: format: int32 type: integer type: array """ ), produces=["application/json"], ) assert converted == oas_fragment( """ content: application/json: schema: items: format: int32 type: integer type: array description: a response description """ ) def test_schema_mimetypes(convert_response, oas_fragment): converted = convert_response( oas_fragment( """ description: a response description schema: items: format: int32 type: integer type: array """ ), produces=["application/json", "text/plain"], ) assert converted == oas_fragment( """ content: application/json: schema: items: format: int32 type: integer type: array text/plain: schema: items: format: int32 type: integer type: array description: a response description """ ) def test_schema_no_mimetypes(convert_response, oas_fragment): converted = convert_response( oas_fragment( """ description: a response description schema: items: format: int32 type: integer type: array """ ), produces=None, ) assert converted == oas_fragment( """ content: '*/*': schema: items: format: int32 type: integer type: array description: a response description """ ) def test_examples(convert_response, oas_fragment): converted = convert_response( oas_fragment( """ description: a response description examples: application/json: something: important """ ), produces=["application/json"], ) assert converted == oas_fragment( """ content: application/json: example: something: important description: a response description """ ) def test_examples_any_type(convert_response, oas_fragment): converted = convert_response( oas_fragment( """ description: a response description examples: application/json: '{"something": "important"}' """ ), produces=["application/json"], ) assert converted == oas_fragment( """ content: application/json: example: '{"something": "important"}' description: a response description """ ) def test_examples_mimetypes(convert_response, oas_fragment): converted = convert_response( oas_fragment( """ description: a response description examples: application/json: something: important text/plain: something=imporant """ ), produces=["application/json", "text/plain"], ) assert converted == oas_fragment( """ content: application/json: example: something: important text/plain: example: something=imporant description: a response description """ ) def test_headers_schema_only(convert_response, oas_fragment): converted = convert_response( oas_fragment( """ description: a response description headers: X-Test: type: string """ ), produces=["application/json"], ) assert converted == oas_fragment( """ description: a response description headers: X-Test: schema: type: string """ ) def test_headers_schema_extra(convert_response, oas_fragment): converted = convert_response( oas_fragment( """ description: a response description headers: X-Test: description: Is it a test? type: string """ ), produces=["application/json"], ) assert converted == oas_fragment( """ description: a response description headers: X-Test: description: Is it a test? schema: type: string """ ) def test_headers_multiple(convert_response, oas_fragment): converted = convert_response( oas_fragment( """ description: a response description headers: X-Bar: format: int32 type: integer X-Foo: type: string """ ), produces=["application/json"], ) assert converted == oas_fragment( """ description: a response description headers: X-Bar: schema: format: int32 type: integer X-Foo: schema: type: string """ ) def test_schema_examples_headers(convert_response, oas_fragment): converted = convert_response( oas_fragment( """ description: a response description examples: application/json: something: important headers: X-Test: description: Is it a test? type: string schema: items: format: int32 type: integer type: array """ ), produces=["application/json"], ) assert converted == oas_fragment( """ description: a response description content: application/json: example: something: important schema: items: format: int32 type: integer type: array headers: X-Test: description: Is it a test? schema: type: string """ ) def test_complete(convert_response, oas_fragment): converted = convert_response( oas_fragment( """ description: a response description examples: application/json: something: important headers: X-Test: description: Is it a test? type: string schema: items: format: int32 type: integer type: array """ ), produces=["application/json"], ) assert converted == oas_fragment( """ description: a response description content: application/json: example: something: important schema: items: format: int32 type: integer type: array headers: X-Test: description: Is it a test? schema: type: string """ ) def test_vendor_extensions(convert_response, oas_fragment): converted = convert_response( oas_fragment( """ description: a response description examples: application/json: something: important headers: X-Test: description: Is it a test? type: string x-header-ext: header-ext schema: items: format: int32 type: integer type: array x-schema-ext: schema-ext x-response-ext: response-ext """ ), produces=["application/json"], ) assert converted == oas_fragment( """ description: a response description content: application/json: example: something: important schema: items: format: int32 type: integer type: array x-schema-ext: schema-ext headers: X-Test: description: Is it a test? schema: type: string x-header-ext: header-ext x-response-ext: response-ext """ ) openapi-0.8.4/tests/lib2to3/test_convert_responses.py000066400000000000000000000076271456264404500230160ustar00rootroot00000000000000""".convert_responses() test suite.""" import pytest import sphinxcontrib.openapi._lib2to3 as lib2to3 @pytest.fixture(scope="function") def convert_responses(oas_fragment): def _wrapper(responses, produces): oas2 = oas_fragment( """ swagger: "2.0" info: title: An example spec version: "1.0" paths: /test: get: responses: '200': description: a response description """ ) oas2["paths"]["/test"]["get"]["responses"] = responses oas2["paths"]["/test"]["get"]["produces"] = produces oas3 = lib2to3.convert(oas2) return oas3["paths"]["/test"]["get"]["responses"] return _wrapper def test_minimal(convert_responses, oas_fragment): converted = convert_responses( oas_fragment( """ '200': description: a response description """ ), produces=["application/json"], ) assert converted == oas_fragment( """ '200': description: a response description """ ) def test_complete(convert_responses, oas_fragment): converted = convert_responses( oas_fragment( """ '200': description: a response description examples: application/json: something: important headers: X-Test: description: Is it a test? type: string schema: items: format: int32 type: integer type: array """ ), produces=["application/json"], ) assert converted == oas_fragment( """ '200': description: a response description content: application/json: example: something: important schema: items: format: int32 type: integer type: array headers: X-Test: description: Is it a test? schema: type: string """ ) def test_multiple(convert_responses, oas_fragment): converted = convert_responses( oas_fragment( """ '200': description: OK schema: items: format: int32 type: integer type: array '400': description: Bad Request headers: X-Test: description: Is it a test? type: string default: description: Internal Server Error """ ), produces=["application/json"], ) assert converted == oas_fragment( """ '200': content: application/json: schema: items: format: int32 type: integer type: array description: OK '400': description: Bad Request headers: X-Test: description: Is it a test? schema: type: string default: description: Internal Server Error """ ) def test_vendor_extensions(convert_responses, oas_fragment): converted = convert_responses( oas_fragment( """ '200': description: a response description x-vendor-ext: vendor-ext """ ), produces=["application/json"], ) assert converted == oas_fragment( """ '200': description: a response description x-vendor-ext: vendor-ext """ ) openapi-0.8.4/tests/renderers/000077500000000000000000000000001456264404500163235ustar00rootroot00000000000000openapi-0.8.4/tests/renderers/httpdomain/000077500000000000000000000000001456264404500204725ustar00rootroot00000000000000openapi-0.8.4/tests/renderers/httpdomain/conftest.py000066400000000000000000000007331456264404500226740ustar00rootroot00000000000000"""Some shared goodies.""" import textwrap import pytest import yaml from sphinxcontrib.openapi import renderers @pytest.fixture(scope="function") def fakestate(): return None @pytest.fixture(scope="function") def testrenderer(fakestate): return renderers.HttpdomainRenderer(fakestate, {}) @pytest.fixture(scope="function") def oas_fragment(): def oas_fragment(fragment): return yaml.safe_load(textwrap.dedent(fragment)) return oas_fragment openapi-0.8.4/tests/renderers/httpdomain/rendered/000077500000000000000000000000001456264404500222625ustar00rootroot00000000000000openapi-0.8.4/tests/renderers/httpdomain/rendered/v2.0/000077500000000000000000000000001456264404500227475ustar00rootroot00000000000000openapi-0.8.4/tests/renderers/httpdomain/rendered/v2.0/api-with-examples.yaml.rst000066400000000000000000000055101456264404500300010ustar00rootroot00000000000000.. http:get:: / **List API versions** :statuscode 200: 200 300 response .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "versions": [ { "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", "id": "v2.0", "links": [ { "href": "http://127.0.0.1:8774/v2/", "rel": "self" } ] }, { "status": "EXPERIMENTAL", "updated": "2013-07-23T11:33:21Z", "id": "v3.0", "links": [ { "href": "http://127.0.0.1:8774/v3/", "rel": "self" } ] } ] } :statuscode 300: 200 300 response .. http:get:: /v2 **Show API version details** :statuscode 200: 200 203 response .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "version": { "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", "media-types": [ { "base": "application/xml", "type": "application/vnd.openstack.compute+xml;version=2" }, { "base": "application/json", "type": "application/vnd.openstack.compute+json;version=2" } ], "id": "v2.0", "links": [ { "href": "http://127.0.0.1:8774/v2/", "rel": "self" }, { "href": "http://docs.openstack.org/api/openstack-compute/2/os-compute-devguide-2.pdf", "type": "application/pdf", "rel": "describedby" }, { "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", "type": "application/vnd.sun.wadl+xml", "rel": "describedby" }, { "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", "type": "application/vnd.sun.wadl+xml", "rel": "describedby" } ] } } :statuscode 203: 200 203 response openapi-0.8.4/tests/renderers/httpdomain/rendered/v2.0/petstore-expanded.yaml.rst000066400000000000000000000026551456264404500301050ustar00rootroot00000000000000.. http:get:: /pets Returns all pets from the system that the user has access to :queryparam tags: tags to filter by :queryparamtype tags: array :queryparam limit: maximum number of results to return :queryparamtype limit: integer:int32 :statuscode 200: pet response :statuscode default: unexpected error .. http:post:: /pets Creates a new pet in the store. Duplicates are allowed :reqjson name: :reqjsonobj name: string, required :reqjson tag: :reqjsonobj tag: string :resjson name: :resjsonobj name: string :resjson tag: :resjsonobj tag: string :resjson id: :resjsonobj id: integer:int64, required :statuscode 200: pet response :statuscode default: unexpected error .. http:get:: /pets/{id} Returns a user based on a single ID, if the user does not have access to the pet :param id: ID of pet to fetch :paramtype id: integer:int64, required :resjson name: :resjsonobj name: string :resjson tag: :resjsonobj tag: string :resjson id: :resjsonobj id: integer:int64, required :statuscode 200: pet response :statuscode default: unexpected error .. http:delete:: /pets/{id} deletes a single pet based on the ID supplied :param id: ID of pet to delete :paramtype id: integer:int64, required :statuscode 204: pet deleted :statuscode default: unexpected error openapi-0.8.4/tests/renderers/httpdomain/rendered/v2.0/petstore.yaml.rst000066400000000000000000000021261456264404500263100ustar00rootroot00000000000000.. http:get:: /pets **List all pets** :queryparam limit: How many items to return at one time (max 100) :queryparamtype limit: integer:int32 :resjsonarr id: :resjsonarrtype id: integer:int64, required :resjsonarr name: :resjsonarrtype name: string, required :resjsonarr tag: :resjsonarrtype tag: string :statuscode 200: A paged array of pets :resheader x-next: A link to the next page of responses :resheadertype x-next: string :statuscode default: unexpected error .. http:post:: /pets **Create a pet** :statuscode 201: Null response :statuscode default: unexpected error .. http:get:: /pets/{petId} **Info for a specific pet** :param petId: The id of the pet to retrieve :paramtype petId: string, required :resjsonarr id: :resjsonarrtype id: integer:int64, required :resjsonarr name: :resjsonarrtype name: string, required :resjsonarr tag: :resjsonarrtype tag: string :statuscode 200: Expected response to a valid request :statuscode default: unexpected error openapi-0.8.4/tests/renderers/httpdomain/rendered/v2.0/uber.json.rst000066400000000000000000000165121456264404500254130ustar00rootroot00000000000000.. http:get:: /products **Product Types** The Products endpoint returns information about the Uber products offered at a given location. The response includes the display name and other details about each product, and lists the products in the proper display order. :queryparam latitude: Latitude component of location. :queryparamtype latitude: number:double, required :queryparam longitude: Longitude component of location. :queryparamtype longitude: number:double, required :resjsonarr product_id: Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles. :resjsonarrtype product_id: string :resjsonarr description: Description of product. :resjsonarrtype description: string :resjsonarr display_name: Display name of product. :resjsonarrtype display_name: string :resjsonarr capacity: Capacity of product. For example, 4 people. :resjsonarrtype capacity: string :resjsonarr image: Image URL representing the product. :resjsonarrtype image: string :statuscode 200: An array of products :statuscode default: Unexpected error .. http:get:: /estimates/price **Price Estimates** .. role:: raw-html-md(raw) :format: html The Price Estimates endpoint returns an estimated price range for each product offered at a given location. The price estimate is provided as a formatted string with the full price range and the localized currency symbol.:raw-html-md:`
`:raw-html-md:`
`The response also includes low and high estimates, and the `ISO 4217 `_ currency code for situations requiring currency conversion. When surge is active for a particular product, its surge_multiplier will be greater than 1, but the price estimate already factors in this multiplier. :queryparam start_latitude: Latitude component of start location. :queryparamtype start_latitude: number:double, required :queryparam start_longitude: Longitude component of start location. :queryparamtype start_longitude: number:double, required :queryparam end_latitude: Latitude component of end location. :queryparamtype end_latitude: number:double, required :queryparam end_longitude: Longitude component of end location. :queryparamtype end_longitude: number:double, required :resjsonarr product_id: Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles :resjsonarrtype product_id: string :resjsonarr currency_code: `ISO 4217 `_ currency code. :resjsonarrtype currency_code: string :resjsonarr display_name: Display name of product. :resjsonarrtype display_name: string :resjsonarr estimate: Formatted string of estimate in local currency of the start location. Estimate could be a range, a single number (flat rate) or "Metered" for TAXI. :resjsonarrtype estimate: string :resjsonarr low_estimate: Lower bound of the estimated price. :resjsonarrtype low_estimate: number :resjsonarr high_estimate: Upper bound of the estimated price. :resjsonarrtype high_estimate: number :resjsonarr surge_multiplier: Expected surge multiplier. Surge is active if surge_multiplier is greater than 1. Price estimate already factors in the surge multiplier. :resjsonarrtype surge_multiplier: number :statuscode 200: An array of price estimates by product :statuscode default: Unexpected error .. http:get:: /estimates/time **Time Estimates** The Time Estimates endpoint returns ETAs for all products offered at a given location, with the responses expressed as integers in seconds. We recommend that this endpoint be called every minute to provide the most accurate, up-to-date ETAs. :queryparam start_latitude: Latitude component of start location. :queryparamtype start_latitude: number:double, required :queryparam start_longitude: Longitude component of start location. :queryparamtype start_longitude: number:double, required :queryparam customer_uuid: Unique customer identifier to be used for experience customization. :queryparamtype customer_uuid: string:uuid :queryparam product_id: Unique identifier representing a specific product for a given latitude & longitude. :queryparamtype product_id: string :resjsonarr product_id: Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles. :resjsonarrtype product_id: string :resjsonarr description: Description of product. :resjsonarrtype description: string :resjsonarr display_name: Display name of product. :resjsonarrtype display_name: string :resjsonarr capacity: Capacity of product. For example, 4 people. :resjsonarrtype capacity: string :resjsonarr image: Image URL representing the product. :resjsonarrtype image: string :statuscode 200: An array of products :statuscode default: Unexpected error .. http:get:: /me **User Profile** The User Profile endpoint returns information about the Uber user that has authorized with the application. :resjson first_name: First name of the Uber user. :resjsonobj first_name: string :resjson last_name: Last name of the Uber user. :resjsonobj last_name: string :resjson email: Email address of the Uber user :resjsonobj email: string :resjson picture: Image URL of the Uber user. :resjsonobj picture: string :resjson promo_code: Promo code of the Uber user. :resjsonobj promo_code: string :statuscode 200: Profile information for a user :statuscode default: Unexpected error .. http:get:: /history **User Activity** .. role:: raw-html-md(raw) :format: html The User Activity endpoint returns data about a user's lifetime activity with Uber. The response will include pickup locations and times, dropoff locations and times, the distance of past requests, and information about which products were requested.:raw-html-md:`
`:raw-html-md:`
`The history array in the response will have a maximum length based on the limit parameter. The response value count may exceed limit, therefore subsequent API requests may be necessary. :queryparam offset: Offset the list of returned results by this amount. Default is zero. :queryparamtype offset: integer:int32 :queryparam limit: Number of items to retrieve. Default is 5, maximum is 100. :queryparamtype limit: integer:int32 :resjson offset: Position in pagination. :resjsonobj offset: integer:int32 :resjson limit: Number of items to retrieve (100 max). :resjsonobj limit: integer:int32 :resjson count: Total number of items available. :resjsonobj count: integer:int32 :resjson history[]: :resjsonobj history[]: object :resjson history[].uuid: Unique identifier for the activity :resjsonobj history[].uuid: string :statuscode 200: History information for the given user :statuscode default: Unexpected error openapi-0.8.4/tests/renderers/httpdomain/rendered/v2.0/uber.yaml.rst000066400000000000000000000165141456264404500254060ustar00rootroot00000000000000.. http:get:: /products **Product Types** The Products endpoint returns information about the Uber products offered at a given location. The response includes the display name and other details about each product, and lists the products in the proper display order. :queryparam latitude: Latitude component of location. :queryparamtype latitude: number:double, required :queryparam longitude: Longitude component of location. :queryparamtype longitude: number:double, required :resjsonarr product_id: Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles. :resjsonarrtype product_id: string :resjsonarr description: Description of product. :resjsonarrtype description: string :resjsonarr display_name: Display name of product. :resjsonarrtype display_name: string :resjsonarr capacity: Capacity of product. For example, 4 people. :resjsonarrtype capacity: integer :resjsonarr image: Image URL representing the product. :resjsonarrtype image: string :statuscode 200: An array of products :statuscode default: Unexpected error .. http:get:: /estimates/price **Price Estimates** .. role:: raw-html-md(raw) :format: html The Price Estimates endpoint returns an estimated price range for each product offered at a given location. The price estimate is provided as a formatted string with the full price range and the localized currency symbol.:raw-html-md:`
`:raw-html-md:`
`The response also includes low and high estimates, and the `ISO 4217 `_ currency code for situations requiring currency conversion. When surge is active for a particular product, its surge_multiplier will be greater than 1, but the price estimate already factors in this multiplier. :queryparam start_latitude: Latitude component of start location. :queryparamtype start_latitude: number:double, required :queryparam start_longitude: Longitude component of start location. :queryparamtype start_longitude: number:double, required :queryparam end_latitude: Latitude component of end location. :queryparamtype end_latitude: number:double, required :queryparam end_longitude: Longitude component of end location. :queryparamtype end_longitude: number:double, required :resjsonarr product_id: Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles :resjsonarrtype product_id: string :resjsonarr currency_code: `ISO 4217 `_ currency code. :resjsonarrtype currency_code: string :resjsonarr display_name: Display name of product. :resjsonarrtype display_name: string :resjsonarr estimate: Formatted string of estimate in local currency of the start location. Estimate could be a range, a single number (flat rate) or "Metered" for TAXI. :resjsonarrtype estimate: string :resjsonarr low_estimate: Lower bound of the estimated price. :resjsonarrtype low_estimate: number :resjsonarr high_estimate: Upper bound of the estimated price. :resjsonarrtype high_estimate: number :resjsonarr surge_multiplier: Expected surge multiplier. Surge is active if surge_multiplier is greater than 1. Price estimate already factors in the surge multiplier. :resjsonarrtype surge_multiplier: number :statuscode 200: An array of price estimates by product :statuscode default: Unexpected error .. http:get:: /estimates/time **Time Estimates** The Time Estimates endpoint returns ETAs for all products offered at a given location, with the responses expressed as integers in seconds. We recommend that this endpoint be called every minute to provide the most accurate, up-to-date ETAs. :queryparam start_latitude: Latitude component of start location. :queryparamtype start_latitude: number:double, required :queryparam start_longitude: Longitude component of start location. :queryparamtype start_longitude: number:double, required :queryparam customer_uuid: Unique customer identifier to be used for experience customization. :queryparamtype customer_uuid: string:uuid :queryparam product_id: Unique identifier representing a specific product for a given latitude & longitude. :queryparamtype product_id: string :resjsonarr product_id: Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles. :resjsonarrtype product_id: string :resjsonarr description: Description of product. :resjsonarrtype description: string :resjsonarr display_name: Display name of product. :resjsonarrtype display_name: string :resjsonarr capacity: Capacity of product. For example, 4 people. :resjsonarrtype capacity: integer :resjsonarr image: Image URL representing the product. :resjsonarrtype image: string :statuscode 200: An array of products :statuscode default: Unexpected error .. http:get:: /me **User Profile** The User Profile endpoint returns information about the Uber user that has authorized with the application. :resjson first_name: First name of the Uber user. :resjsonobj first_name: string :resjson last_name: Last name of the Uber user. :resjsonobj last_name: string :resjson email: Email address of the Uber user :resjsonobj email: string :resjson picture: Image URL of the Uber user. :resjsonobj picture: string :resjson promo_code: Promo code of the Uber user. :resjsonobj promo_code: string :statuscode 200: Profile information for a user :statuscode default: Unexpected error .. http:get:: /history **User Activity** .. role:: raw-html-md(raw) :format: html The User Activity endpoint returns data about a user's lifetime activity with Uber. The response will include pickup locations and times, dropoff locations and times, the distance of past requests, and information about which products were requested.:raw-html-md:`
`:raw-html-md:`
`The history array in the response will have a maximum length based on the limit parameter. The response value count may exceed limit, therefore subsequent API requests may be necessary. :queryparam offset: Offset the list of returned results by this amount. Default is zero. :queryparamtype offset: integer:int32 :queryparam limit: Number of items to retrieve. Default is 5, maximum is 100. :queryparamtype limit: integer:int32 :resjson offset: Position in pagination. :resjsonobj offset: integer:int32 :resjson limit: Number of items to retrieve (100 max). :resjsonobj limit: integer:int32 :resjson count: Total number of items available. :resjsonobj count: integer:int32 :resjson history[]: :resjsonobj history[]: object :resjson history[].uuid: Unique identifier for the activity :resjsonobj history[].uuid: string :statuscode 200: History information for the given user :statuscode default: Unexpected error openapi-0.8.4/tests/renderers/httpdomain/rendered/v3.0/000077500000000000000000000000001456264404500227505ustar00rootroot00000000000000openapi-0.8.4/tests/renderers/httpdomain/rendered/v3.0/api-with-examples.yaml.rst000066400000000000000000000047061456264404500300100ustar00rootroot00000000000000.. http:get:: / **List API versions** :statuscode 200: 200 response .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "versions": [ { "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", "id": "v2.0", "links": [ { "href": "http://127.0.0.1:8774/v2/", "rel": "self" } ] }, { "status": "EXPERIMENTAL", "updated": "2013-07-23T11:33:21Z", "id": "v3.0", "links": [ { "href": "http://127.0.0.1:8774/v3/", "rel": "self" } ] } ] } :statuscode 300: 300 response .. http:get:: /v2 **Show API version details** :statuscode 200: 200 response .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "version": { "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", "media-types": [ { "base": "application/xml", "type": "application/vnd.openstack.compute+xml;version=2" }, { "base": "application/json", "type": "application/vnd.openstack.compute+json;version=2" } ], "id": "v2.0", "links": [ { "href": "http://127.0.0.1:8774/v2/", "rel": "self" }, { "href": "http://docs.openstack.org/api/openstack-compute/2/os-compute-devguide-2.pdf", "type": "application/pdf", "rel": "describedby" }, { "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", "type": "application/vnd.sun.wadl+xml", "rel": "describedby" }, { "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", "type": "application/vnd.sun.wadl+xml", "rel": "describedby" } ] } } :statuscode 203: 203 response openapi-0.8.4/tests/renderers/httpdomain/rendered/v3.0/api-with-recursion.yaml.rst000066400000000000000000000005601456264404500301750ustar00rootroot00000000000000.. http:get:: / **List API versions** :resjson name: :resjsonobj name: string, required :statuscode 200: 200 response :statuscode 300: 300 response .. http:get:: /v2 **Show API version details** :resjson name: :resjsonobj name: string, required :statuscode 200: 200 response :statuscode 203: 203 response openapi-0.8.4/tests/renderers/httpdomain/rendered/v3.0/petstore-expanded.yaml.rst000066400000000000000000000055461456264404500301100ustar00rootroot00000000000000.. http:get:: /pets Returns all pets from the system that the user has access to Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. :queryparam tags: tags to filter by :queryparamtype tags: array :queryparam limit: maximum number of results to return :queryparamtype limit: integer:int32 :statuscode 200: pet response :statuscode default: unexpected error .. http:post:: /pets Creates a new pet in the store. Duplicates are allowed :reqjson name: :reqjsonobj name: string, required :reqjson tag: :reqjsonobj tag: string :resjson name: :resjsonobj name: string :resjson tag: :resjsonobj tag: string :resjson id: :resjsonobj id: integer:int64, required :statuscode 200: pet response :statuscode default: unexpected error .. http:get:: /pets/{id} Returns a user based on a single ID, if the user does not have access to the pet :param id: ID of pet to fetch :paramtype id: integer:int64, required :resjson name: :resjsonobj name: string :resjson tag: :resjsonobj tag: string :resjson id: :resjsonobj id: integer:int64, required :statuscode 200: pet response :statuscode default: unexpected error .. http:delete:: /pets/{id} deletes a single pet based on the ID supplied :param id: ID of pet to delete :paramtype id: integer:int64, required :statuscode 204: pet deleted :statuscode default: unexpected error openapi-0.8.4/tests/renderers/httpdomain/rendered/v3.0/petstore.yaml.rst000066400000000000000000000021261456264404500263110ustar00rootroot00000000000000.. http:get:: /pets **List all pets** :queryparam limit: How many items to return at one time (max 100) :queryparamtype limit: integer:int32 :resjsonarr id: :resjsonarrtype id: integer:int64, required :resjsonarr name: :resjsonarrtype name: string, required :resjsonarr tag: :resjsonarrtype tag: string :statuscode 200: A paged array of pets :resheader x-next: A link to the next page of responses :resheadertype x-next: string :statuscode default: unexpected error .. http:post:: /pets **Create a pet** :statuscode 201: Null response :statuscode default: unexpected error .. http:get:: /pets/{petId} **Info for a specific pet** :param petId: The id of the pet to retrieve :paramtype petId: string, required :resjsonarr id: :resjsonarrtype id: integer:int64, required :resjsonarr name: :resjsonarrtype name: string, required :resjsonarr tag: :resjsonarrtype tag: string :statuscode 200: Expected response to a valid request :statuscode default: unexpected error openapi-0.8.4/tests/renderers/httpdomain/rendered/v3.0/uspto.yaml.rst000066400000000000000000000065711456264404500256260ustar00rootroot00000000000000.. http:get:: / **List available data sets** :resjson total: :resjsonobj total: integer :resjson apis[]: :resjsonobj apis[]: object :resjson apis[].apiKey: To be used as a dataset parameter value :resjsonobj apis[].apiKey: string :resjson apis[].apiVersionNumber: To be used as a version parameter value :resjsonobj apis[].apiVersionNumber: string :resjson apis[].apiUrl: The URL describing the dataset's fields :resjsonobj apis[].apiUrl: string:uriref :resjson apis[].apiDocumentationUrl: A URL to the API console for each API :resjsonobj apis[].apiDocumentationUrl: string:uriref :statuscode 200: Returns a list of data sets .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "total": 2, "apis": [ { "apiKey": "oa_citations", "apiVersionNumber": "v1", "apiUrl": "https://developer.uspto.gov/ds-api/oa_citations/v1/fields", "apiDocumentationUrl": "https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/oa_citations.json" }, { "apiKey": "cancer_moonshot", "apiVersionNumber": "v1", "apiUrl": "https://developer.uspto.gov/ds-api/cancer_moonshot/v1/fields", "apiDocumentationUrl": "https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/cancer_moonshot.json" } ] } .. http:get:: /{dataset}/{version}/fields **Provides the general information about the API and the list of fields that can be used to query the dataset.** This GET API returns the list of all the searchable field names that are in the oa_citations. Please see the 'fields' attribute which returns an array of field names. Each field or a combination of fields can be searched using the syntax options shown below. :param dataset: Name of the dataset. :paramtype dataset: string, required :param version: Version of the dataset. :paramtype version: string, required :statuscode 200: The dataset API for the given version is found and it is accessible to consume. :statuscode 404: The combination of dataset name and version is not found in the system or it is not published yet to be consumed by public. .. http:post:: /{dataset}/{version}/records **Provides search capability for the data set with the given search criteria.** This API is based on Solr/Lucense Search. The data is indexed using SOLR. This GET API returns the list of all the searchable field names that are in the Solr Index. Please see the 'fields' attribute which returns an array of field names. Each field or a combination of fields can be searched using the Solr/Lucene Syntax. Please refer https://lucene.apache.org/core/3_6_2/queryparsersyntax.html#Overview for the query syntax. List of field names that are searchable can be determined using above GET api. :param version: Version of the dataset. :paramtype version: string, required :param dataset: Name of the dataset. In this case, the default value is oa_citations :paramtype dataset: string, required :statuscode 200: successful operation :statuscode 404: No matching record found for the given criteria. openapi-0.8.4/tests/renderers/httpdomain/rendered/v3.1/000077500000000000000000000000001456264404500227515ustar00rootroot00000000000000openapi-0.8.4/tests/renderers/httpdomain/rendered/v3.1/issue-112.yaml.rst000066400000000000000000000020761456264404500261020ustar00rootroot00000000000000.. http:get:: /users **Get all users.** :queryparam role: :resjsonarr id: The user ID. :resjsonarrtype id: integer :resjsonarr username: The user name. :resjsonarrtype username: string :resjsonarr deleted: Whether the user account has been deleted. :resjsonarrtype deleted: boolean :statuscode 200: A list of all users. .. http:get:: /users/{userID} **Get a user by ID.** :param userID: :paramtype userID: string :resjson id: The user ID. :resjsonobj id: integer :resjson username: The user name. :resjsonobj username: string :resjson bio: A brief bio about the user. :resjsonobj bio: string, null :resjson deleted: Whether the user account has been deleted. :resjsonobj deleted: boolean :resjson created_at: The date the user account was created. :resjsonobj created_at: string:date :resjson deleted_at: The date the user account was deleted. :resjsonobj deleted_at: string:date :statuscode 200: The expected information about a user. openapi-0.8.4/tests/renderers/httpdomain/test_render.py000066400000000000000000000020421456264404500233600ustar00rootroot00000000000000import pathlib import os import pytest @pytest.fixture(scope="function") def rendered(): return pathlib.Path(os.path.dirname(__file__), "rendered") @pytest.mark.regenerate_rendered_specs def test_generate(testrenderer, testspec, rendered): testspec_name, testspec = testspec rendered_markup = "\n".join(testrenderer.render_restructuredtext_markup(testspec)) rendered.joinpath(os.path.dirname(testspec_name)).mkdir(parents=True, exist_ok=True) rendered.joinpath(testspec_name + ".rst").write_text(rendered_markup) def test_render(testrenderer, testspec, rendered): testspec_name, testspec = testspec rendered_markup = "\n".join(testrenderer.render_restructuredtext_markup(testspec)) # if this is our first time encountering the test, write the response if not os.path.exists(rendered.joinpath(testspec_name + ".rst")): with rendered.joinpath(testspec_name + ".rst").open("w") as fh: fh.write(rendered_markup) assert rendered_markup == rendered.joinpath(testspec_name + ".rst").read_text() openapi-0.8.4/tests/renderers/httpdomain/test_render_json_schema_description.py000066400000000000000000001140341456264404500303410ustar00rootroot00000000000000"""OpenAPI spec renderer: render_json_schema_description.""" import textwrap import pytest from sphinxcontrib.openapi import renderers def textify(generator): return "\n".join(generator) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_root_object( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for JSON object in root.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: prop_a: type: string prop_b: type: object properties: eggs: type: boolean prop_c: type: number """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} prop_a: :{typedirective} prop_a: string :{directive} prop_b: :{typedirective} prop_b: object :{directive} prop_b.eggs: :{typedirective} prop_b.eggs: boolean :{directive} prop_c: :{typedirective} prop_c: number """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjsonarr", "reqjsonarrtype", id="req"), pytest.param("res", "resjsonarr", "resjsonarrtype", id="res"), ], ) def test_render_json_schema_description_root_array( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for JSON array in root.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: array items: type: object properties: prop: type: string """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} prop: :{typedirective} prop: string """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) @pytest.mark.parametrize( ["schema_type"], [ pytest.param("null"), pytest.param("boolean"), pytest.param("number"), pytest.param("string"), pytest.param("integer"), ], ) def test_render_json_schema_description_root_unsupported( testrenderer, oas_fragment, schema_type, req_or_res, directive, typedirective ): """JSON schema description is not generated for unsupported type in root.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( f""" type: {schema_type} """ ), req_or_res, ) ) assert markup == textwrap.dedent( """\ """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_root_any_of_object( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for anyOf JSON object in root.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ anyOf: - type: object properties: prop_a: type: string prop_b: type: number - type: object properties: prop_c: type: object properties: eggs: type: boolean """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} prop_a: :{typedirective} prop_a: string :{directive} prop_b: :{typedirective} prop_b: number """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjsonarr", "reqjsonarrtype", id="req"), pytest.param("res", "resjsonarr", "resjsonarrtype", id="res"), ], ) def test_render_json_schema_description_root_any_of_array( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for anyOf JSON array in root.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ anyOf: - type: array items: type: object properties: prop: type: string - type: array items: type: object properties: prop: type: number """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} prop: :{typedirective} prop: string """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) @pytest.mark.parametrize( ["schema_type"], [ pytest.param("null"), pytest.param("boolean"), pytest.param("number"), pytest.param("string"), pytest.param("integer"), ], ) def test_render_json_schema_description_root_any_of_unsupported( testrenderer, oas_fragment, schema_type, req_or_res, directive, typedirective ): """JSON schema description is not generated for anyOf unsupported type in root.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( f""" anyOf: - type: {schema_type} - type: object """ ), req_or_res, ) ) assert markup == textwrap.dedent( """\ """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_root_one_of_object( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for oneOf JSON object in root.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ oneOf: - type: object properties: prop_a: type: string prop_b: type: number - type: object properties: prop_c: type: object properties: eggs: type: boolean """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} prop_a: :{typedirective} prop_a: string :{directive} prop_b: :{typedirective} prop_b: number """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjsonarr", "reqjsonarrtype", id="req"), pytest.param("res", "resjsonarr", "resjsonarrtype", id="res"), ], ) def test_render_json_schema_description_root_one_of_array( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for oneOf JSON array in root.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ oneOf: - type: array items: type: object properties: prop: type: string - type: array items: type: object properties: prop: type: number """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} prop: :{typedirective} prop: string """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) @pytest.mark.parametrize( ["schema_type"], [ pytest.param("null"), pytest.param("boolean"), pytest.param("number"), pytest.param("string"), pytest.param("integer"), ], ) def test_render_json_schema_description_root_one_of_unsupported( testrenderer, oas_fragment, schema_type, req_or_res, directive, typedirective ): """JSON schema description is not generated for oneOf unsupported type in root.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( f""" oneOf: - type: {schema_type} - type: object """ ), req_or_res, ) ) assert markup == textwrap.dedent( """\ """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_root_all_of_object( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for allOf in root.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ allOf: - properties: name: properties: first: type: string age: type: integer - properties: name: properties: last: type: string """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} name: :{typedirective} name: object :{directive} name.first: :{typedirective} name.first: string :{directive} name.last: :{typedirective} name.last: string :{directive} age: :{typedirective} age: integer """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) @pytest.mark.parametrize( ["schema_type"], [ pytest.param("null"), pytest.param("boolean"), pytest.param("number"), pytest.param("string"), pytest.param("integer"), ], ) def test_render_json_schema_description_primitive( testrenderer, oas_fragment, schema_type, req_or_res, directive, typedirective ): """JSON schema description is generated for primitive types.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( f""" type: object properties: some_key: type: "{schema_type}" """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} some_key: :{typedirective} some_key: {schema_type} """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_object( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for JSON object.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: root: type: object properties: prop_a: type: string prop_b: type: object properties: eggs: type: boolean prop_c: type: number """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} root: :{typedirective} root: object :{directive} root.prop_a: :{typedirective} root.prop_a: string :{directive} root.prop_b: :{typedirective} root.prop_b: object :{directive} root.prop_b.eggs: :{typedirective} root.prop_b.eggs: boolean :{directive} root.prop_c: :{typedirective} root.prop_c: number """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_object_implicit( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for implicit JSON object.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: root: properties: prop_a: type: string prop_b: properties: eggs: type: boolean prop_c: type: number """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} root: :{typedirective} root: object :{directive} root.prop_a: :{typedirective} root.prop_a: string :{directive} root.prop_b: :{typedirective} root.prop_b: object :{directive} root.prop_b.eggs: :{typedirective} root.prop_b.eggs: boolean :{directive} root.prop_c: :{typedirective} root.prop_c: number """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_array( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for JSON array.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: root: type: array items: type: object properties: prop_a: type: string prop_b: type: array items: type: number prop_c: type: number """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} root[]: :{typedirective} root[]: object :{directive} root[].prop_a: :{typedirective} root[].prop_a: string :{directive} root[].prop_b[]: :{typedirective} root[].prop_b[]: number :{directive} root[].prop_c: :{typedirective} root[].prop_c: number """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_array_implicit( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for implicit JSON array.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: root: items: type: object properties: prop_a: type: string prop_b: items: type: number prop_c: type: number """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} root[]: :{typedirective} root[]: object :{directive} root[].prop_a: :{typedirective} root[].prop_a: string :{directive} root[].prop_b[]: :{typedirective} root[].prop_b[]: number :{directive} root[].prop_c: :{typedirective} root[].prop_c: number """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_format( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for formatted types.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: created_at: type: string format: date-time """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} created_at: :{typedirective} created_at: string:date-time """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_deprecated( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated with deprecated marker.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: created_at: type: string deprecated: true """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} created_at: :{typedirective} created_at: string, deprecated """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_required( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for JSON object w/ required marker.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: root: type: object properties: prop_a: type: string prop_b: type: object properties: eggs: type: boolean required: [eggs] prop_c: type: number required: [prop_a] """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} root: :{typedirective} root: object :{directive} root.prop_a: :{typedirective} root.prop_a: string, required :{directive} root.prop_b: :{typedirective} root.prop_b: object :{directive} root.prop_b.eggs: :{typedirective} root.prop_b.eggs: boolean, required :{directive} root.prop_c: :{typedirective} root.prop_c: number """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_deprecated_and_required( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for JSON object w/ deprecated & required markers.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: root: type: object properties: prop_a: type: string prop_b: type: object properties: eggs: type: boolean deprecated: true required: [eggs] prop_c: type: number required: [prop_a] """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} root: :{typedirective} root: object :{directive} root.prop_a: :{typedirective} root.prop_a: string, required :{directive} root.prop_b: :{typedirective} root.prop_b: object :{directive} root.prop_b.eggs: :{typedirective} root.prop_b.eggs: boolean, deprecated, required :{directive} root.prop_c: :{typedirective} root.prop_c: number """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_description( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated with description.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object description: a resource representation properties: created_at: type: string description: a resource creation time """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} created_at: a resource creation time :{typedirective} created_at: string """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_description_commonmark_default( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated with CommonMark description by default.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object description: a resource representation properties: created_at: type: string description: a `resource` creation __time__ """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} created_at: a ``resource`` creation **time** :{typedirective} created_at: string """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_description_commonmark( fakestate, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated with CommonMark description.""" testrenderer = renderers.HttpdomainRenderer(fakestate, {"markup": "commonmark"}) markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object description: a resource representation properties: created_at: type: string description: a `resource` creation __time__ """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} created_at: a ``resource`` creation **time** :{typedirective} created_at: string """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_description_restructuredtext( fakestate, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated with reStructuredText description.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"markup": "restructuredtext"} ) markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object description: a resource representation properties: created_at: type: string description: a `resource` creation __time__ """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} created_at: a `resource` creation __time__ :{typedirective} created_at: string """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_any_of( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for anyOf.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( f""" type: object properties: some_key: anyOf: - type: integer - type: string """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} some_key: :{typedirective} some_key: integer """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_one_of( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for oneOf.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: some_key: oneOf: - type: integer - type: string """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} some_key: :{typedirective} some_key: integer """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_all_of( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for allOf.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: person: allOf: - properties: name: properties: first: type: string age: type: integer - properties: name: properties: last: type: string """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} person: :{typedirective} person: object :{directive} person.name: :{typedirective} person.name: object :{directive} person.name.first: :{typedirective} person.name.first: string :{directive} person.name.last: :{typedirective} person.name.last: string :{directive} person.age: :{typedirective} person.age: integer """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_all_of_logical_impossible( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for allOf that is logical impossible.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: some_key: allOf: - type: integer - type: string """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} some_key: :{typedirective} some_key: string """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_any_of_shared_type( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for anyOf w/ shared 'type'.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: some_key: type: string anyOf: - minLength: 3 - maxLength: 5 """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} some_key: :{typedirective} some_key: string """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_one_of_shared_type( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for oneOf w/ shared 'type'.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: some_key: type: string oneOf: - minLength: 3 - maxLength: 5 """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} some_key: :{typedirective} some_key: string """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_all_of_shared_type( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for allOf w/ shared 'type'.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: some_key: type: string alOf: - minLength: 3 - maxLength: 5 """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} some_key: :{typedirective} some_key: string """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_not( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for JSON *not*.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: root: not: type: boolean """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} root: """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_enum( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for JSON enum.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: root: type: string enum: - foo - bar """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} root: :{typedirective} root: string:enum """.rstrip() ) @pytest.mark.parametrize( ["req_or_res", "directive", "typedirective"], [ pytest.param("req", "reqjson", "reqjsonobj", id="req"), pytest.param("res", "resjson", "resjsonobj", id="res"), ], ) def test_render_json_schema_description_enum_wo_type( testrenderer, oas_fragment, req_or_res, directive, typedirective ): """JSON schema description is generated for JSON enum wo/ type.""" markup = textify( testrenderer.render_json_schema_description( oas_fragment( """ type: object properties: root: enum: - foo - bar """ ), req_or_res, ) ) assert markup == textwrap.dedent( f"""\ :{directive} root: :{typedirective} root: enum """.rstrip() ) openapi-0.8.4/tests/renderers/httpdomain/test_render_operation.py000066400000000000000000000231201456264404500254400ustar00rootroot00000000000000"""OpenAPI spec renderer: render_operation.""" import textwrap import pytest from sphinxcontrib.openapi import renderers def textify(generator): return "\n".join(generator) def test_render_operation(testrenderer, oas_fragment): """Usual operation definition is rendered.""" markup = textify( testrenderer.render_operation( "/evidences/{evidenceId}", "get", oas_fragment( """ summary: Retrieve an evidence by ID. description: More verbose description... parameters: - name: evidenceId in: path required: true description: A unique evidence identifier to query. schema: type: string - name: details in: query description: If true, information w/ details is returned. schema: type: boolean responses: '200': description: An evidence. '404': description: An evidence not found. """ ), ) ) assert markup == textwrap.dedent( """\ .. http:get:: /evidences/{evidenceId} **Retrieve an evidence by ID.** More verbose description... :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: string, required :queryparam details: If true, information w/ details is returned. :queryparamtype details: boolean :statuscode 200: An evidence. :statuscode 404: An evidence not found. """.rstrip() ) def test_render_operation_minimal(testrenderer, oas_fragment): """Operation minimal definition is rendered.""" markup = textify( testrenderer.render_operation( "/evidences", "post", oas_fragment( """ responses: '201': description: An evidence created. """ ), ) ) assert markup == textwrap.dedent( """\ .. http:post:: /evidences :statuscode 201: An evidence created. """.rstrip() ) def test_render_operation_summary(testrenderer, oas_fragment): """Operation's 'summary' is rendered in bold.""" markup = textify( testrenderer.render_operation( "/evidences", "post", oas_fragment( """ summary: Create an evidence. responses: '201': description: An evidence created. """ ), ) ) assert markup == textwrap.dedent( """\ .. http:post:: /evidences **Create an evidence.** :statuscode 201: An evidence created. """.rstrip() ) def test_render_operation_description(testrenderer, oas_fragment): """Operation's 'description' is rendered.""" markup = textify( testrenderer.render_operation( "/evidences", "post", oas_fragment( """ description: Create an evidence. responses: '201': description: An evidence created. """ ), ) ) assert markup == textwrap.dedent( """\ .. http:post:: /evidences Create an evidence. :statuscode 201: An evidence created. """.rstrip() ) def test_render_operation_description_multiline(testrenderer, oas_fragment): """Operation's multiline 'description' is rendered.""" markup = textify( testrenderer.render_operation( "/evidences", "post", oas_fragment( """ description: | Create an evidence. responses: '201': description: An evidence created. """ ), ) ) assert markup == textwrap.dedent( """\ .. http:post:: /evidences Create an evidence. :statuscode 201: An evidence created. """.rstrip() ) def test_render_operation_description_commonmark_default(testrenderer, oas_fragment): """Operation's 'description' must be in commonmark by default.""" markup = textify( testrenderer.render_operation( "/evidences", "post", oas_fragment( """ description: __Create__ an `evidence`. responses: '201': description: An evidence created. """ ), ) ) assert markup == textwrap.dedent( """\ .. http:post:: /evidences **Create** an ``evidence``. :statuscode 201: An evidence created. """.rstrip() ) def test_render_operation_description_commonmark(fakestate, oas_fragment): """Operation's 'description' can be in commonmark.""" testrenderer = renderers.HttpdomainRenderer(fakestate, {"markup": "commonmark"}) markup = textify( testrenderer.render_operation( "/evidences", "post", oas_fragment( """ description: __Create__ an `evidence`. responses: '201': description: An evidence created. """ ), ) ) assert markup == textwrap.dedent( """\ .. http:post:: /evidences **Create** an ``evidence``. :statuscode 201: An evidence created. """.rstrip() ) def test_render_operation_description_commonmark_restructuredtext( fakestate, oas_fragment ): """Operation's 'description' can be in restructuredtext.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"markup": "restructuredtext"} ) markup = textify( testrenderer.render_operation( "/evidences", "post", oas_fragment( """ description: __Create__ an `evidence`. responses: '201': description: An evidence created. """ ), ) ) assert markup == textwrap.dedent( """\ .. http:post:: /evidences __Create__ an `evidence`. :statuscode 201: An evidence created. """.rstrip() ) def test_render_operation_deprecated(testrenderer, oas_fragment): """Operation's 'deprecated' mark is rendered.""" markup = textify( testrenderer.render_operation( "/evidences", "post", oas_fragment( """ responses: '201': description: An evidence created. deprecated: true """ ), ) ) assert markup == textwrap.dedent( """\ .. http:post:: /evidences :deprecated: :statuscode 201: An evidence created. """.rstrip() ) def test_render_operation_w_requestbody(testrenderer, oas_fragment): """Operation's 'requestBody' is rendered.""" markup = textify( testrenderer.render_operation( "/evidences", "post", oas_fragment( """ requestBody: content: application/json: example: foo: bar baz: 42 responses: '201': description: An evidence created. """ ), ) ) assert markup == textwrap.dedent( """\ .. http:post:: /evidences .. sourcecode:: http POST /evidences HTTP/1.1 Content-Type: application/json { "foo": "bar", "baz": 42 } :statuscode 201: An evidence created. """.rstrip() ) @pytest.mark.parametrize( ["method"], [pytest.param("POST"), pytest.param("pOst"), pytest.param("post")] ) def test_render_operation_caseinsensitive_method(testrenderer, method, oas_fragment): """Operation's 'method' is case insensitive.""" markup = textify( testrenderer.render_operation( "/evidences", method, oas_fragment( """ requestBody: content: application/json: example: foo: bar baz: 42 responses: '201': description: An evidence created. """ ), ) ) assert markup == textwrap.dedent( f"""\ .. http:{method}:: /evidences .. sourcecode:: http {method.upper()} /evidences HTTP/1.1 Content-Type: application/json {{ "foo": "bar", "baz": 42 }} :statuscode 201: An evidence created. """.rstrip() ) openapi-0.8.4/tests/renderers/httpdomain/test_render_parameter.py000066400000000000000000000624501456264404500254310ustar00rootroot00000000000000"""OpenAPI spec renderer: render_parameter.""" import textwrap from sphinxcontrib.openapi import renderers def textify(generator): return "\n".join(generator) def test_render_parameter_path(testrenderer, oas_fragment): """Usual path parameter's definition is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: path required: true description: A unique evidence identifier to query. schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: string, required """.rstrip() ) def test_render_parameter_path_minimal(testrenderer, oas_fragment): """Path parameter's minimal definition is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: path required: true """ ) ) ) assert markup == textwrap.dedent( """\ :param evidenceId: :paramtype evidenceId: required """.rstrip() ) def test_render_parameter_path_description(testrenderer, oas_fragment): """Path parameter's 'description' is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: path required: true description: A unique evidence identifier to query. """ ) ) ) assert markup == textwrap.dedent( """\ :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: required """.rstrip() ) def test_render_parameter_path_multiline_description(testrenderer, oas_fragment): """Path parameter's multiline 'description' is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: path required: true description: | A unique evidence identifier to query. """ ) ) ) assert markup == textwrap.dedent( """\ :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: required """.rstrip() ) def test_render_parameter_path_description_commonmark_default( testrenderer, oas_fragment ): """Path parameter's 'description' must be in commonmark by default.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: path required: true description: | A unique evidence `identifier` to __query__. """ ) ) ) assert markup == textwrap.dedent( """\ :param evidenceId: A unique evidence ``identifier`` to **query**. :paramtype evidenceId: required """.rstrip() ) def test_render_parameter_path_description_commonmark(fakestate, oas_fragment): """Path parameter's 'description' can be in commonmark.""" testrenderer = renderers.HttpdomainRenderer(fakestate, {"markup": "commonmark"}) markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: path required: true description: | A unique evidence `identifier` to __query__. """ ) ) ) assert markup == textwrap.dedent( """\ :param evidenceId: A unique evidence ``identifier`` to **query**. :paramtype evidenceId: required """.rstrip() ) def test_render_parameter_path_description_restructuredtext(fakestate, oas_fragment): """Path parameter's 'description' can be in restructuredtext.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"markup": "restructuredtext"} ) markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: path required: true description: | A unique evidence `identifier` to __query__. """ ) ) ) assert markup == textwrap.dedent( """\ :param evidenceId: A unique evidence `identifier` to __query__. :paramtype evidenceId: required """.rstrip() ) def test_render_parameter_path_deprecated(testrenderer, oas_fragment): """Path parameter's 'deprecated' marker is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: path required: true deprecated: true """ ) ) ) assert markup == textwrap.dedent( """\ :param evidenceId: :paramtype evidenceId: required, deprecated """.rstrip() ) def test_render_parameter_path_deprecated_false(testrenderer, oas_fragment): """Path parameter's 'deprecated' marker is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: path required: true deprecated: false """ ) ) ) assert markup == textwrap.dedent( """\ :param evidenceId: :paramtype evidenceId: required """.rstrip() ) def test_render_parameter_path_type(testrenderer, oas_fragment): """Path parameter's type is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: path schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ :param evidenceId: :paramtype evidenceId: string """.rstrip() ) def test_render_parameter_path_type_with_format(testrenderer, oas_fragment): """Path parameter's type with format is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: path schema: type: string format: uuid4 """ ) ) ) assert markup == textwrap.dedent( """\ :param evidenceId: :paramtype evidenceId: string:uuid4 """.rstrip() ) def test_render_parameter_path_type_from_content(testrenderer, oas_fragment): """Path parameter's type from content is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: path content: text/plain: schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ :param evidenceId: :paramtype evidenceId: string """.rstrip() ) def test_render_parameter_query(testrenderer, oas_fragment): """Usual query parameter's definition is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: path description: A unique evidence identifier to query. schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: string """.rstrip() ) def test_render_parameter_query_minimal(testrenderer, oas_fragment): """Query parameter's minimal definition is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: path """ ) ) ) assert markup == textwrap.dedent( """\ :param evidenceId: """.rstrip() ) def test_render_parameter_query_description(testrenderer, oas_fragment): """Query parameter's 'description' is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: query description: A unique evidence identifier to query. """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam evidenceId: A unique evidence identifier to query. """.rstrip() ) def test_render_parameter_query_multiline_description(testrenderer, oas_fragment): """Query parameter's multiline 'description' is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: query description: | A unique evidence identifier to query. """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam evidenceId: A unique evidence identifier to query. """.rstrip() ) def test_render_parameter_query_description_commonmark_default( testrenderer, oas_fragment ): """Query parameter's 'description' must be in commonmark by default.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: query description: | A unique evidence `identifier` to __query__. """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam evidenceId: A unique evidence ``identifier`` to **query**. """.rstrip() ) def test_render_parameter_query_description_commonmark(fakestate, oas_fragment): """Query parameter's 'description' can be in commonmark.""" testrenderer = renderers.HttpdomainRenderer(fakestate, {"markup": "commonmark"}) markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: query description: | A unique evidence `identifier` to __query__. """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam evidenceId: A unique evidence ``identifier`` to **query**. """.rstrip() ) def test_render_parameter_query_description_restructuredtext(fakestate, oas_fragment): """Query parameter's 'description' can be in restructuredtext.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"markup": "restructuredtext"} ) markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: query description: | A unique evidence `identifier` to __query__. """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam evidenceId: A unique evidence `identifier` to __query__. """.rstrip() ) def test_render_parameter_query_required(testrenderer, oas_fragment): """Query parameter's 'required' marker is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: query required: true """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam evidenceId: :queryparamtype evidenceId: required """.rstrip() ) def test_render_parameter_query_required_false(testrenderer, oas_fragment): """Query parameter's 'required' marker is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: query required: false """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam evidenceId: """.rstrip() ) def test_render_parameter_query_deprecated(testrenderer, oas_fragment): """Query parameter's 'deprecated' marker is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: query deprecated: true """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam evidenceId: :queryparamtype evidenceId: deprecated """.rstrip() ) def test_render_parameter_query_deprecated_false(testrenderer, oas_fragment): """Query parameter's 'deprecated' marker is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: query deprecated: false """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam evidenceId: """.rstrip() ) def test_render_parameter_query_required_deprecated(testrenderer, oas_fragment): """Both query parameter's markers are rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: query required: true deprecated: true """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam evidenceId: :queryparamtype evidenceId: required, deprecated """.rstrip() ) def test_render_parameter_query_type(testrenderer, oas_fragment): """Query parameter's type is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: query schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam evidenceId: :queryparamtype evidenceId: string """.rstrip() ) def test_render_parameter_query_type_with_format(testrenderer, oas_fragment): """Query parameter's type with format is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: query schema: type: string format: uuid4 """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam evidenceId: :queryparamtype evidenceId: string:uuid4 """.rstrip() ) def test_render_parameter_query_type_from_content(testrenderer, oas_fragment): """Query parameter's type from content is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: query content: text/plain: schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam evidenceId: :queryparamtype evidenceId: string """.rstrip() ) def test_render_parameter_header(testrenderer, oas_fragment): """Usual header parameter's definition is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: X-Request-Id in: header description: A unique request identifier. schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader X-Request-Id: A unique request identifier. :reqheadertype X-Request-Id: string """.rstrip() ) def test_render_parameter_header_minimal(testrenderer, oas_fragment): """Header parameter's minimal definition is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: X-Request-Id in: header """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader X-Request-Id: """.rstrip() ) def test_render_parameter_header_description(testrenderer, oas_fragment): """Header parameter's 'description' is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: X-Request-Id in: header description: A unique request identifier. """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader X-Request-Id: A unique request identifier. """.rstrip() ) def test_render_parameter_header_multiline_description(testrenderer, oas_fragment): """Header parameter's multiline 'description' is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: X-Request-Id in: header description: | A unique request identifier. """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader X-Request-Id: A unique request identifier. """.rstrip() ) def test_render_parameter_header_description_commonmark_default( testrenderer, oas_fragment ): """Header parameter's 'description' must be in commonmark by default.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: header description: | A unique evidence `identifier` to __query__. """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader evidenceId: A unique evidence ``identifier`` to **query**. """.rstrip() ) def test_render_parameter_header_description_commonmark(fakestate, oas_fragment): """Header parameter's 'description' can be in commonmark.""" testrenderer = renderers.HttpdomainRenderer(fakestate, {"markup": "commonmark"}) markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: header description: | A unique evidence `identifier` to __query__. """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader evidenceId: A unique evidence ``identifier`` to **query**. """.rstrip() ) def test_render_parameter_header_description_restructuredtext(fakestate, oas_fragment): """Header parameter's 'description' can be in restructuredtext.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"markup": "restructuredtext"} ) markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: header description: | A unique evidence `identifier` to __query__. """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader evidenceId: A unique evidence `identifier` to __query__. """.rstrip() ) def test_render_parameter_header_required(testrenderer, oas_fragment): """Header parameter's 'required' marker is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: X-Request-Id in: header required: true """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader X-Request-Id: :reqheadertype X-Request-Id: required """.rstrip() ) def test_render_parameter_header_required_false(testrenderer, oas_fragment): """Header parameter's 'required' marker is not rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: X-Request-Id in: header required: false """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader X-Request-Id: """.rstrip() ) def test_render_parameter_header_deprecated(testrenderer, oas_fragment): """Header parameter's 'deprecated' marker is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: X-Request-Id in: header deprecated: true """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader X-Request-Id: :reqheadertype X-Request-Id: deprecated """.rstrip() ) def test_render_parameter_header_deprecated_false(testrenderer, oas_fragment): """Header parameter's 'deprecated' marker is not rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: X-Request-Id in: header deprecated: false """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader X-Request-Id: """.rstrip() ) def test_render_parameter_header_required_deprecated(testrenderer, oas_fragment): """Both header parameter's markers are rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: X-Request-Id in: header required: true deprecated: true """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader X-Request-Id: :reqheadertype X-Request-Id: required, deprecated """.rstrip() ) def test_render_parameter_header_type(testrenderer, oas_fragment): """Header parameter's type is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: header schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader evidenceId: :reqheadertype evidenceId: string """.rstrip() ) def test_render_parameter_header_type_with_format(testrenderer, oas_fragment): """Header parameter's type with format is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: header schema: type: string format: uuid4 """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader evidenceId: :reqheadertype evidenceId: string:uuid4 """.rstrip() ) def test_render_parameter_header_type_from_content(testrenderer, oas_fragment): """Header parameter's type from content is rendered.""" markup = textify( testrenderer.render_parameter( oas_fragment( """ name: evidenceId in: header content: text/plain: schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader evidenceId: :reqheadertype evidenceId: string """.rstrip() ) openapi-0.8.4/tests/renderers/httpdomain/test_render_parameters.py000066400000000000000000000334531456264404500256150ustar00rootroot00000000000000"""OpenAPI spec renderer: render_parameters.""" import itertools import textwrap import pytest from sphinxcontrib.openapi import renderers def textify(generator): return "\n".join(generator) def test_render_parameters_no_items(testrenderer, oas_fragment): """No parameter definitions are rendered.""" markup = textify( testrenderer.render_parameters( oas_fragment( """ [] """ ) ) ) assert markup == "" def test_render_parameters_one_item(testrenderer, oas_fragment): """One usual parameter definition is rendered.""" markup = textify( testrenderer.render_parameters( oas_fragment( """ - name: evidenceId in: path required: true description: A unique evidence identifier to query. schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: string, required """.rstrip() ) def test_render_parameters_many_items(testrenderer, oas_fragment): """Many parameter definitions are rendered.""" markup = textify( testrenderer.render_parameters( oas_fragment( """ - name: evidenceId in: path required: true description: A unique evidence identifier to query. schema: type: string - name: details in: query description: If true, information w/ details is returned. schema: type: boolean - name: Api-Version in: header default: '1' description: API version to use for the request. schema: type: integer """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader Api-Version: API version to use for the request. :reqheadertype Api-Version: integer :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: string, required :queryparam details: If true, information w/ details is returned. :queryparamtype details: boolean """.rstrip() ) @pytest.mark.parametrize("permutation_seq", itertools.permutations(range(3))) def test_render_parameters_many_items_ordered( testrenderer, oas_fragment, permutation_seq ): """Many parameter definitions are rendered and properly ordered.""" parameters = oas_fragment( """ - name: evidenceId in: path required: true description: A unique evidence identifier to query. schema: type: string - name: details in: query description: If true, information w/ details is returned. schema: type: boolean - name: Api-Version in: header required: false default: '1' description: API version to use for the request. schema: type: integer """ ) markup = textify( testrenderer.render_parameters( # Since the test receives a permutation sequence as input, # we need to ensure that parameters are shuffled according # to that sequence, because this is the essence of the test. [parameters[seq] for seq in permutation_seq] ) ) assert markup == textwrap.dedent( """\ :reqheader Api-Version: API version to use for the request. :reqheadertype Api-Version: integer :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: string, required :queryparam details: If true, information w/ details is returned. :queryparamtype details: boolean """.rstrip() ) def test_render_parameters_many_items_stable_order(testrenderer, oas_fragment): """Many parameter definitions are rendered w/ preserved order.""" markup = textify( testrenderer.render_parameters( oas_fragment( """ - name: kind in: path required: true description: An evidence kind. schema: type: string - name: Api-Version in: header default: '1' description: API version to use for the request. schema: type: integer - name: details in: query description: If true, information w/ details is returned. schema: type: boolean - name: evidenceId in: path required: true description: A unique evidence identifier to query. schema: type: string - name: related in: query description: If true, links to related evidences are returned. schema: type: boolean - name: Accept in: header default: application/json description: A desired Content-Type of HTTP response. schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ :reqheader Api-Version: API version to use for the request. :reqheadertype Api-Version: integer :reqheader Accept: A desired Content-Type of HTTP response. :reqheadertype Accept: string :param kind: An evidence kind. :paramtype kind: string, required :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: string, required :queryparam details: If true, information w/ details is returned. :queryparamtype details: boolean :queryparam related: If true, links to related evidences are returned. :queryparamtype related: boolean """.rstrip() ) def test_render_parameters_custom_order(fakestate, oas_fragment): """Many parameter definitions are rendered w/ preserved order.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"request-parameters-order": ["query", "path", "header"]} ) markup = textify( testrenderer.render_parameters( oas_fragment( """ - name: kind in: path required: true description: An evidence kind. schema: type: string - name: Api-Version in: header default: '1' description: API version to use for the request. schema: type: integer - name: details in: query description: If true, information w/ details is returned. schema: type: boolean - name: evidenceId in: path required: true description: A unique evidence identifier to query. schema: type: string - name: related in: query description: If true, links to related evidences are returned. schema: type: boolean - name: Accept in: header default: application/json description: A desired Content-Type of HTTP response. schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam details: If true, information w/ details is returned. :queryparamtype details: boolean :queryparam related: If true, links to related evidences are returned. :queryparamtype related: boolean :param kind: An evidence kind. :paramtype kind: string, required :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: string, required :reqheader Api-Version: API version to use for the request. :reqheadertype Api-Version: integer :reqheader Accept: A desired Content-Type of HTTP response. :reqheadertype Accept: string """.rstrip() ) def test_render_parameters_custom_order_partial(fakestate, oas_fragment): """Many parameter definitions are rendered w/ preserved order.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"request-parameters-order": ["query", "path"]} ) markup = textify( testrenderer.render_parameters( oas_fragment( """ - name: kind in: path required: true description: An evidence kind. schema: type: string - name: Api-Version in: header default: '1' description: API version to use for the request. schema: type: integer - name: details in: query description: If true, information w/ details is returned. schema: type: boolean - name: evidenceId in: path required: true description: A unique evidence identifier to query. schema: type: string - name: related in: query description: If true, links to related evidences are returned. schema: type: boolean - name: Accept in: header default: application/json description: A desired Content-Type of HTTP response. schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam details: If true, information w/ details is returned. :queryparamtype details: boolean :queryparam related: If true, links to related evidences are returned. :queryparamtype related: boolean :param kind: An evidence kind. :paramtype kind: string, required :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: string, required :reqheader Api-Version: API version to use for the request. :reqheadertype Api-Version: integer :reqheader Accept: A desired Content-Type of HTTP response. :reqheadertype Accept: string """.rstrip() ) def test_render_parameters_case_insensitive(fakestate, oas_fragment): """Many parameter definitions are rendered w/ preserved order.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"request-parameters-order": ["QUERY", "pAth", "Header"]} ) markup = textify( testrenderer.render_parameters( oas_fragment( """ - name: kind in: PATH required: true description: An evidence kind. schema: type: string - name: Api-Version in: header default: '1' description: API version to use for the request. schema: type: integer - name: details in: query description: If true, information w/ details is returned. schema: type: boolean - name: evidenceId in: Path required: true description: A unique evidence identifier to query. schema: type: string - name: related in: qUery description: If true, links to related evidences are returned. schema: type: boolean - name: Accept in: headeR default: application/json description: A desired Content-Type of HTTP response. schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ :queryparam details: If true, information w/ details is returned. :queryparamtype details: boolean :queryparam related: If true, links to related evidences are returned. :queryparamtype related: boolean :param kind: An evidence kind. :paramtype kind: string, required :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: string, required :reqheader Api-Version: API version to use for the request. :reqheadertype Api-Version: integer :reqheader Accept: A desired Content-Type of HTTP response. :reqheadertype Accept: string """.rstrip() ) openapi-0.8.4/tests/renderers/httpdomain/test_render_paths.py000066400000000000000000000323661456264404500245730ustar00rootroot00000000000000"""OpenAPI spec renderer: render_paths.""" import textwrap from sphinxcontrib.openapi import renderers def textify(generator): return "\n".join(generator) def test_render_paths(testrenderer, oas_fragment): """Usual paths definition is rendered.""" markup = textify( testrenderer.render_paths( oas_fragment( """ /evidences/{evidenceId}: summary: Ignored description: Ignored servers: url: https://example.com parameters: - name: evidenceId in: path required: true description: A unique evidence identifier to query. schema: type: string get: summary: Retrieve an evidence by ID. description: More verbose description... parameters: - name: details in: query description: If true, information w/ details is returned. schema: type: boolean responses: '200': description: An evidence. '404': description: An evidence not found. """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /evidences/{evidenceId} **Retrieve an evidence by ID.** More verbose description... :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: string, required :queryparam details: If true, information w/ details is returned. :queryparamtype details: boolean :statuscode 200: An evidence. :statuscode 404: An evidence not found. """ ) def test_render_paths_minimal(testrenderer, oas_fragment): """Minimal paths definition is rendered.""" markup = textify( testrenderer.render_paths( oas_fragment( """ /evidences: get: responses: '200': description: A list of evidences. """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /evidences :statuscode 200: A list of evidences. """ ) def test_render_paths_multiple(testrenderer, oas_fragment): """Paths definition with multiple paths is rendered.""" markup = textify( testrenderer.render_paths( oas_fragment( """ /evidences/{evidenceId}: get: summary: Retrieve an evidence by ID. description: More verbose description... parameters: - name: evidenceId in: path required: true description: A unique evidence identifier to query. schema: type: string - name: details in: query description: If true, information w/ details is returned. schema: type: boolean responses: '200': description: An evidence. '404': description: An evidence not found. /evidences: post: summary: Create an evidence. responses: '201': description: An evidence created. """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /evidences/{evidenceId} **Retrieve an evidence by ID.** More verbose description... :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: string, required :queryparam details: If true, information w/ details is returned. :queryparamtype details: boolean :statuscode 200: An evidence. :statuscode 404: An evidence not found. .. http:post:: /evidences **Create an evidence.** :statuscode 201: An evidence created. """ ) def test_render_paths_parameters_common(testrenderer, oas_fragment): """Paths definition with common parameters is rendered.""" markup = textify( testrenderer.render_paths( oas_fragment( """ /evidences/{evidenceId}: get: summary: Retrieve an evidence by ID. parameters: - name: details in: query description: If true, information w/ details is returned. schema: type: boolean responses: '200': description: An evidence. '404': description: An evidence not found. put: summary: Update an evidence by ID. responses: '200': description: An evidence. '404': description: An evidence not found. parameters: - name: evidenceId in: path required: true description: A unique evidence identifier to query. schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /evidences/{evidenceId} **Retrieve an evidence by ID.** :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: string, required :queryparam details: If true, information w/ details is returned. :queryparamtype details: boolean :statuscode 200: An evidence. :statuscode 404: An evidence not found. .. http:put:: /evidences/{evidenceId} **Update an evidence by ID.** :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: string, required :statuscode 200: An evidence. :statuscode 404: An evidence not found. """ ) def test_render_paths_parameters_common_prepend(testrenderer, oas_fragment): """Paths definition with common parameters is rendered.""" markup = textify( testrenderer.render_paths( oas_fragment( """ /evidences/{evidenceId}/{evidenceSection}: get: summary: Retrieve an evidence by ID. parameters: - name: evidenceSection in: path description: Query a section with a given name. schema: type: string responses: '200': description: An evidence. '404': description: An evidence not found. parameters: - name: evidenceId in: path required: true description: A unique evidence identifier to query. schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /evidences/{evidenceId}/{evidenceSection} **Retrieve an evidence by ID.** :param evidenceId: A unique evidence identifier to query. :paramtype evidenceId: string, required :param evidenceSection: Query a section with a given name. :paramtype evidenceSection: string :statuscode 200: An evidence. :statuscode 404: An evidence not found. """ ) def test_render_paths_parameters_common_overwritten(testrenderer, oas_fragment): """Paths definition with common parameters is rendered.""" markup = textify( testrenderer.render_paths( oas_fragment( """ /evidences/{evidenceId}: get: summary: Retrieve an evidence by ID. parameters: - name: evidenceId in: path description: Overwritten description. schema: type: string responses: '200': description: An evidence. parameters: - name: evidenceId in: path required: true description: A unique evidence identifier to query. schema: type: string """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /evidences/{evidenceId} **Retrieve an evidence by ID.** :param evidenceId: Overwritten description. :paramtype evidenceId: string :statuscode 200: An evidence. """ ) def test_render_paths_methods_order(testrenderer, oas_fragment): """Paths definition is rendered with HTTP methods ordered.""" markup = textify( testrenderer.render_paths( oas_fragment( """ /evidences: post: responses: '201': description: An evidence created. options: responses: '200': description: CORS preflight request. get: responses: '200': description: A list of evidences. """ ) ) ) assert markup == textwrap.dedent( """\ .. http:post:: /evidences :statuscode 201: An evidence created. .. http:options:: /evidences :statuscode 200: CORS preflight request. .. http:get:: /evidences :statuscode 200: A list of evidences. """ ) def test_render_paths_methods_order_custom(fakestate, oas_fragment): """Paths definition is rendered with HTTP methods ordered.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"http-methods-order": ["delete", "options", "get", "post"]} ) markup = textify( testrenderer.render_paths( oas_fragment( """ /evidences: post: responses: '201': description: An evidence created. options: responses: '200': description: CORS preflight request. get: responses: '200': description: A list of evidences. """ ) ) ) assert markup == textwrap.dedent( """\ .. http:options:: /evidences :statuscode 200: CORS preflight request. .. http:get:: /evidences :statuscode 200: A list of evidences. .. http:post:: /evidences :statuscode 201: An evidence created. """ ) def test_render_paths_methods_order_insensitive(fakestate, oas_fragment): """Paths definition is rendered with HTTP methods ordered.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"http-methods-order": ["gEt", "post"]} ) markup = textify( testrenderer.render_paths( oas_fragment( """ /evidences: post: responses: '201': description: An evidence created. get: responses: '200': description: A list of evidences. """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /evidences :statuscode 200: A list of evidences. .. http:post:: /evidences :statuscode 201: An evidence created. """ ) openapi-0.8.4/tests/renderers/httpdomain/test_render_request_body.py000066400000000000000000000050551456264404500261540ustar00rootroot00000000000000"""OpenAPI spec renderer: render_request_body.""" import textwrap import pytest from sphinxcontrib.openapi import renderers def textify(generator): return "\n".join(generator) @pytest.mark.parametrize( "content_type", ["application/json", "application/foobar+json"] ) def test_render_request_body_schema_description( testrenderer, oas_fragment, content_type ): """JSON schema description is rendered.""" markup = textify( testrenderer.render_request_body( oas_fragment( f""" content: {content_type}: schema: properties: foo: type: string bar: type: integer """ ), "/evidences/{evidenceId}", "POST", ) ) assert markup == textwrap.dedent( """\ :reqjson foo: :reqjsonobj foo: string :reqjson bar: :reqjsonobj bar: integer """ ) def test_render_request_body_schema_description_non_json(testrenderer, oas_fragment): """JSON schema is not rendered for non JSON mimetype.""" markup = textify( testrenderer.render_request_body( oas_fragment( """ content: text/csv: schema: properties: foo: type: string bar: type: integer """ ), "/evidences/{evidenceId}", "POST", ) ) assert markup == textwrap.dedent( """\ """ ) def test_render_request_body_schema_description_turned_off(fakestate, oas_fragment): """JSON schema description is not rendered b/c feature is off.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"no-json-schema-description": True}, ) markup = textify( testrenderer.render_request_body( oas_fragment( """ content: application/json: schema: properties: foo: type: string bar: type: integer """ ), "/evidences/{evidenceId}", "POST", ) ) assert markup == textwrap.dedent( """\ """ ) openapi-0.8.4/tests/renderers/httpdomain/test_render_request_body_example.py000066400000000000000000000351311456264404500276650ustar00rootroot00000000000000"""OpenAPI spec renderer: render_request_body_example.""" import textwrap import pytest import responses from sphinxcontrib.openapi import renderers def textify(generator): return "\n".join(generator) @pytest.mark.parametrize( ["content"], [ pytest.param( """ content: application/json: examples: test: value: foo: bar baz: 42 """, id="examples", ), pytest.param( """ content: application/json: example: foo: bar baz: 42 """, id="example", ), pytest.param( """ content: application/json: schema: example: foo: bar baz: 42 """, id="schema/example", ), pytest.param( """ content: application/json: examples: test: value: | { "foo": "bar", "baz": 42 } """, id="examples::str", ), pytest.param( """ content: application/json: example: | { "foo": "bar", "baz": 42 } """, id="example::str", ), pytest.param( """ content: application/json: schema: example: | { "foo": "bar", "baz": 42 } """, id="schema/example::str", ), pytest.param( """ content: application/json: schema: example: foobar: bazinga example: foo: bar baz: 42 """, id="example-beats-schema/example", ), pytest.param( """ content: application/json: schema: example: foobar: bazinga examples: test: value: foo: bar baz: 42 """, id="examples-beats-schema/example", ), ], ) def test_render_request_body_example(testrenderer, content, oas_fragment): """Request body is rendered.""" markup = textify( testrenderer.render_request_body_example( oas_fragment(content), "/evidences/{evidenceId}", "POST", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http POST /evidences/{evidenceId} HTTP/1.1 Content-Type: application/json { "foo": "bar", "baz": 42 } """.rstrip() ) def test_render_request_body_example_1st_from_examples(testrenderer, oas_fragment): """Request body's first example is rendered.""" markup = textify( testrenderer.render_request_body_example( oas_fragment( """ content: application/json: examples: foo: value: foo: bar baz: 42 bar: value: foobar: bazinga """ ), "/evidences/{evidenceId}", "POST", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http POST /evidences/{evidenceId} HTTP/1.1 Content-Type: application/json { "foo": "bar", "baz": 42 } """.rstrip() ) def test_render_request_body_example_1st_from_media_type(testrenderer, oas_fragment): """Request body's example from first media type is rendered.""" markup = textify( testrenderer.render_request_body_example( oas_fragment( """ content: text/plain: example: | foo = "bar" baz = 42 application/json: schema: type: object """ ), "/evidences/{evidenceId}", "POST", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http POST /evidences/{evidenceId} HTTP/1.1 Content-Type: text/plain foo = "bar" baz = 42 """.rstrip() ) @pytest.mark.parametrize( ["example_preference_key"], [pytest.param("request-example-preference"), pytest.param("example-preference")], ) def test_render_request_body_example_preference( fakestate, oas_fragment, example_preference_key ): """Request body's example from preferred media type is rendered.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {example_preference_key: ["text/plain"]} ) markup = textify( testrenderer.render_request_body_example( oas_fragment( """ content: application/json: example: foo: bar baz: 42 text/plain: example: | foo = "bar" baz = 42 """ ), "/evidences/{evidenceId}", "POST", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http POST /evidences/{evidenceId} HTTP/1.1 Content-Type: text/plain foo = "bar" baz = 42 """.rstrip() ) @pytest.mark.parametrize( ["example_preference_key"], [pytest.param("request-example-preference"), pytest.param("example-preference")], ) def test_render_request_body_example_preference_complex( fakestate, oas_fragment, example_preference_key ): """Request body's example from preferred media type is rendered.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {example_preference_key: ["application/json", "text/plain"]} ) markup = textify( testrenderer.render_request_body_example( oas_fragment( """ content: text/csv: example: | foo,baz bar,42 text/plain: example: | foo = "bar" baz = 42 application/json: schema: type: object """ ), "/evidences/{evidenceId}", "POST", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http POST /evidences/{evidenceId} HTTP/1.1 Content-Type: text/plain foo = "bar" baz = 42 """.rstrip() ) def test_render_request_body_example_preference_priority(fakestate, oas_fragment): """Request body's example from preferred media type is rendered.""" testrenderer = renderers.HttpdomainRenderer( fakestate, { "example-preference": ["application/json"], "request-example-preference": ["text/plain"], }, ) markup = textify( testrenderer.render_request_body_example( oas_fragment( """ content: application/json: example: foo: bar baz: 42 text/plain: example: | foo = "bar" baz = 42 """ ), "/evidences/{evidenceId}", "POST", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http POST /evidences/{evidenceId} HTTP/1.1 Content-Type: text/plain foo = "bar" baz = 42 """.rstrip() ) @responses.activate def test_render_request_body_example_external(testrenderer, oas_fragment): """Request body's example can be retrieved from external location.""" responses.add( responses.GET, "https://example.com/json/examples/test.json", json={"foo": "bar", "baz": 42}, status=200, ) markup = textify( testrenderer.render_request_body_example( oas_fragment( """ content: application/json: examples: test: externalValue: https://example.com/json/examples/test.json """ ), "/evidences/{evidenceId}", "POST", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http POST /evidences/{evidenceId} HTTP/1.1 Content-Type: application/json {"foo": "bar", "baz": 42} """.rstrip() ) @responses.activate def test_render_request_body_example_external_errored_next_example( testrenderer, oas_fragment, caplog ): """Request body's example fallbacks on next when external cannot be retrieved.""" responses.add( responses.GET, "https://example.com/json/examples/test.json", status=404, ) markup = textify( testrenderer.render_request_body_example( oas_fragment( """ content: application/json: examples: test: externalValue: https://example.com/json/examples/test.json fallback: value: '{"spam": 42}' """ ), "/evidences/{evidenceId}", "POST", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http POST /evidences/{evidenceId} HTTP/1.1 Content-Type: application/json {"spam": 42} """.rstrip() ) @responses.activate def test_render_request_body_example_external_errored_next_media_type( testrenderer, oas_fragment, caplog, ): """Request body's example fallbacks on next when external cannot be retrieved.""" responses.add( responses.GET, "https://example.com/json/examples/test.json", status=404, ) markup = textify( testrenderer.render_request_body_example( oas_fragment( """ content: application/json: examples: test: externalValue: https://example.com/json/examples/test.json text/csv: example: spam,42 """ ), "/evidences/{evidenceId}", "POST", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http POST /evidences/{evidenceId} HTTP/1.1 Content-Type: text/csv spam,42 """.rstrip() ) def test_render_request_body_example_content_type(testrenderer, oas_fragment): """Request body's example can render something other than application/json.""" markup = textify( testrenderer.render_request_body_example( oas_fragment( """ content: text/csv: example: | foo,baz bar,42 """ ), "/evidences/{evidenceId}", "POST", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http POST /evidences/{evidenceId} HTTP/1.1 Content-Type: text/csv foo,baz bar,42 """.rstrip() ) def test_render_request_body_example_noop(testrenderer, oas_fragment): """Request body's example is not rendered if there's nothing to render.""" markup = textify( testrenderer.render_request_body_example( oas_fragment( """ content: application/json: schema: type: object """ ), "/evidences/{evidenceId}", "POST", ) ) assert markup == "" @pytest.mark.parametrize( ["http_method"], [pytest.param("POST"), pytest.param("PUT"), pytest.param("PATCH")] ) def test_render_request_body_example_http_method( testrenderer, oas_fragment, http_method, ): """Request body's example shows proper HTTP method.""" markup = textify( testrenderer.render_request_body_example( oas_fragment( """ content: text/csv: example: | foo,baz bar,42 """ ), "/evidences/{evidenceId}", http_method, ) ) assert markup == textwrap.dedent( f"""\ .. sourcecode:: http {http_method} /evidences/{{evidenceId}} HTTP/1.1 Content-Type: text/csv foo,baz bar,42 """.rstrip() ) @pytest.mark.parametrize( ["http_endpoint"], [pytest.param("/evidences/{evidenceId}"), pytest.param("/heroes/{heroId}")], ) def test_render_request_body_example_http_endpoint( testrenderer, oas_fragment, http_endpoint, ): """Request body's example shows proper HTTP method.""" markup = textify( testrenderer.render_request_body_example( oas_fragment( """ content: text/csv: example: | foo,baz bar,42 """ ), http_endpoint, "POST", ) ) assert markup == textwrap.dedent( f"""\ .. sourcecode:: http POST {http_endpoint} HTTP/1.1 Content-Type: text/csv foo,baz bar,42 """.rstrip() ) openapi-0.8.4/tests/renderers/httpdomain/test_render_response.py000066400000000000000000000443611456264404500253100ustar00rootroot00000000000000"""OpenAPI spec renderer: render_response.""" import textwrap import pytest from sphinxcontrib.openapi import renderers def textify(generator): return "\n".join(generator) @pytest.mark.parametrize( ["statuscode"], [pytest.param("200"), pytest.param("4XX"), pytest.param("default")] ) def test_render_response_status_code(testrenderer, oas_fragment, statuscode): """Path response's definition is rendered for any status code.""" markup = textify( testrenderer.render_response( statuscode, oas_fragment( """ description: An evidence. """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode %s: An evidence. """.rstrip() % statuscode ) def test_render_response_minimal(testrenderer, oas_fragment): """Path response's minimal definition is rendered.""" markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. """.rstrip() ) def test_render_response_description_commonmark_default(testrenderer, oas_fragment): """Path response's 'description' must be in commonmark.""" markup = textify( testrenderer.render_response( "200", oas_fragment( """ description: | An __evidence__ that matches the `query`. """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An **evidence** that matches the ``query``. """.rstrip() ) def test_render_response_description_commonmark(fakestate, oas_fragment): """Path response's 'description' can be in commonmark.""" testrenderer = renderers.HttpdomainRenderer(fakestate, {"markup": "commonmark"}) markup = textify( testrenderer.render_response( "200", oas_fragment( """ description: | An __evidence__ that matches the `query`. """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An **evidence** that matches the ``query``. """.rstrip() ) def test_render_response_description_restructuredtext(fakestate, oas_fragment): """Path response's 'description' can be in restructuredtext.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"markup": "restructuredtext"} ) markup = textify( testrenderer.render_response( "200", oas_fragment( """ description: | An __evidence__ that matches the `query`. """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An __evidence__ that matches the `query`. """.rstrip() ) @pytest.mark.parametrize( ["status_code", "status"], [ pytest.param("200", "OK", id="200"), pytest.param("201", "Created", id="201"), pytest.param("202", "Accepted", id="202"), ], ) def test_render_response_content_2xx(testrenderer, oas_fragment, status_code, status): """Path response's 'content' definition is rendered.""" markup = textify( testrenderer.render_response( status_code, oas_fragment( """ description: An evidence. content: application/json: example: foo: bar baz: 42 """ ), ) ) assert markup == textwrap.dedent( f"""\ :statuscode {status_code}: An evidence. .. sourcecode:: http HTTP/1.1 {status_code} {status} Content-Type: application/json {{ "foo": "bar", "baz": 42 }} """.rstrip() ) @pytest.mark.parametrize( ["status_code"], [ pytest.param("301"), pytest.param("307"), pytest.param("401"), pytest.param("422"), pytest.param("502"), ], ) def test_render_response_content_non_2xx(testrenderer, oas_fragment, status_code): """Path response's 'content' definition is NOT rendered.""" markup = textify( testrenderer.render_response( status_code, oas_fragment( """ description: An evidence. content: application/json: example: foo: bar baz: 42 """ ), ) ) assert markup == textwrap.dedent( f"""\ :statuscode {status_code}: An evidence. """.rstrip() ) @pytest.mark.parametrize( ["status_code", "status"], [ pytest.param("301", "Moved Permanently", id="301"), pytest.param("307", "Temporary Redirect", id="307"), pytest.param("401", "Unauthorized", id="401"), pytest.param("422", "Unprocessable Entity", id="422"), ], ) def test_render_response_content_custom(fakestate, oas_fragment, status_code, status): """Path response's 'content' definition is rendered.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"response-examples-for": ["301", "307", "401", "422"]} ) markup = textify( testrenderer.render_response( status_code, oas_fragment( """ description: An evidence. content: application/json: example: foo: bar baz: 42 """ ), ) ) assert markup == textwrap.dedent( f"""\ :statuscode {status_code}: An evidence. .. sourcecode:: http HTTP/1.1 {status_code} {status} Content-Type: application/json {{ "foo": "bar", "baz": 42 }} """.rstrip() ) def test_render_response_content_custom_mismatch(fakestate, oas_fragment): """Path response's 'content' definition is NOT rendered.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"response-examples-for": ["301", "307", "401", "422"]} ) markup = textify( testrenderer.render_response( "200", oas_fragment( """ description: An evidence. content: application/json: example: foo: bar baz: 42 """ ), ) ) assert markup == textwrap.dedent( f"""\ :statuscode 200: An evidence. """.rstrip() ) def test_render_response_header(testrenderer, oas_fragment): """Path response's 'header' definition is rendered.""" markup = textify( testrenderer.render_response( "200", oas_fragment( """ description: An evidence. headers: X-Request-Id: description: A unique request identifier. schema: type: string """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :resheader X-Request-Id: A unique request identifier. :resheadertype X-Request-Id: string """.rstrip() ) def test_render_response_header_minimal(testrenderer, oas_fragment): """Path response's 'header' minimal definition is rendered.""" markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. headers: X-Request-Id: {} """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :resheader X-Request-Id: """.rstrip() ) def test_render_response_header_description(testrenderer, oas_fragment): """Path response's 'header' description is rendered.""" markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. headers: X-Request-Id: description: A unique request identifier. """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :resheader X-Request-Id: A unique request identifier. """.rstrip() ) def test_render_response_header_multiline_description(testrenderer, oas_fragment): """Path response's 'header' multiline description is rendered.""" markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. headers: X-Request-Id: description: | A unique request identifier. """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :resheader X-Request-Id: A unique request identifier. """.rstrip() ) def test_render_response_header_description_commonmark_default( testrenderer, oas_fragment ): """Path response's 'header' description must be in commonmark by default.""" markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. headers: X-Request-Id: description: | A unique __request__ `identifier`. """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :resheader X-Request-Id: A unique **request** ``identifier``. """.rstrip() ) def test_render_response_header_description_commonmark(fakestate, oas_fragment): """Path response's 'header' description can be in commonmark.""" testrenderer = renderers.HttpdomainRenderer(fakestate, {"markup": "commonmark"}) markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. headers: X-Request-Id: description: | A unique __request__ `identifier`. """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :resheader X-Request-Id: A unique **request** ``identifier``. """.rstrip() ) def test_render_response_header_description_restructuredtext(fakestate, oas_fragment): """Path response's 'header' description can be in restructuredtext.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"markup": "restructuredtext"} ) markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. headers: X-Request-Id: description: | A unique __request__ `identifier`. """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :resheader X-Request-Id: A unique __request__ `identifier`. """.rstrip() ) def test_render_response_header_content_type(testrenderer, oas_fragment): """Path response's 'Content-Type' header is ignored.""" markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. headers: Content-Type: {} """ ), ) ) # There's an extra newline at the end of markup if there's at least one # response header defined. assert markup.rstrip() == textwrap.dedent( """\ :statuscode 200: An evidence. """.rstrip() ) def test_render_response_header_required(testrenderer, oas_fragment): """Path response's header 'required' marker is rendered.""" markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. headers: X-Request-Id: required: true """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :resheader X-Request-Id: :resheadertype X-Request-Id: required """.rstrip() ) def test_render_response_header_required_false(testrenderer, oas_fragment): """Path response's header 'required' marker is not rendered.""" markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. headers: X-Request-Id: required: false """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :resheader X-Request-Id: """.rstrip() ) def test_render_response_header_deprecated(testrenderer, oas_fragment): """Path response's header 'deprecated' marker is rendered.""" markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. headers: X-Request-Id: deprecated: true """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :resheader X-Request-Id: :resheadertype X-Request-Id: deprecated """.rstrip() ) def test_render_response_header_deprecated_false(testrenderer, oas_fragment): """Path response's header 'deprecated' marker is not rendered.""" markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. headers: X-Request-Id: deprecated: false """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :resheader X-Request-Id: """.rstrip() ) def test_render_response_header_required_deprecated(testrenderer, oas_fragment): """Path response's header markers are rendered.""" markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. headers: X-Request-Id: required: true deprecated: true """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :resheader X-Request-Id: :resheadertype X-Request-Id: required, deprecated """.rstrip() ) def test_render_response_header_type(testrenderer, oas_fragment): """Path response's header type is rendered.""" markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. headers: X-Request-Id: schema: type: string """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :resheader X-Request-Id: :resheadertype X-Request-Id: string """.rstrip() ) def test_render_response_header_type_with_format(testrenderer, oas_fragment): """Path response's header type with format is rendered.""" markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. headers: X-Request-Id: schema: type: string format: uuid4 """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :resheader X-Request-Id: :resheadertype X-Request-Id: string:uuid4 """.rstrip() ) def test_render_response_header_type_from_content(testrenderer, oas_fragment): """Path response's header type from content is rendered.""" markup = textify( testrenderer.render_response( 200, oas_fragment( """ description: An evidence. headers: X-Request-Id: content: text/plain: schema: type: string """ ), ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :resheader X-Request-Id: :resheadertype X-Request-Id: string """.rstrip() ) openapi-0.8.4/tests/renderers/httpdomain/test_render_response_example.py000066400000000000000000000357031456264404500270230ustar00rootroot00000000000000"""OpenAPI spec renderer: render_response_example.""" import textwrap import pytest import responses from sphinxcontrib.openapi import renderers def textify(generator): return "\n".join(generator) @pytest.mark.parametrize( ["media_type"], [ pytest.param( """ application/json: examples: test: value: foo: bar baz: 42 """, id="examples", ), pytest.param( """ application/json: example: foo: bar baz: 42 """, id="example", ), pytest.param( """ application/json: schema: example: foo: bar baz: 42 """, id="schema/example", ), pytest.param( """ application/json: examples: test: value: | { "foo": "bar", "baz": 42 } """, id="examples::str", ), pytest.param( """ application/json: example: | { "foo": "bar", "baz": 42 } """, id="example::str", ), pytest.param( """ application/json: schema: example: | { "foo": "bar", "baz": 42 } """, id="schema/example::str", ), pytest.param( """ application/json: schema: example: foobar: bazinga example: foo: bar baz: 42 """, id="example-beats-schema/example", ), pytest.param( """ application/json: schema: example: foobar: bazinga examples: test: value: foo: bar baz: 42 """, id="examples-beats-schema/example", ), ], ) def test_render_response_example(testrenderer, oas_fragment, media_type): """Path response's example is rendered.""" markup = textify( testrenderer.render_response_example(oas_fragment(media_type), "200") ) assert markup == textwrap.dedent( """\ .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "foo": "bar", "baz": 42 } """.rstrip() ) def test_render_response_example_1st_from_examples(testrenderer, oas_fragment): """Path response's first example is rendered.""" markup = textify( testrenderer.render_response_example( oas_fragment( """ application/json: examples: foo: value: foo: bar baz: 42 bar: value: foobar: bazinga """ ), "200", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "foo": "bar", "baz": 42 } """.rstrip() ) def test_render_response_example_1st_from_media_type(testrenderer, oas_fragment): """Path response's example from first media type is rendered.""" markup = textify( testrenderer.render_response_example( oas_fragment( """ text/plain: example: | foo = "bar" baz = 42 application/json: schema: type: object """ ), "200", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http HTTP/1.1 200 OK Content-Type: text/plain foo = "bar" baz = 42 """.rstrip() ) @pytest.mark.parametrize( ["example_preference_key"], [pytest.param("response-example-preference"), pytest.param("example-preference")], ) def test_render_response_example_preference( fakestate, example_preference_key, oas_fragment ): """Path response's example from preferred media type is rendered.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {example_preference_key: ["text/plain"]} ) markup = textify( testrenderer.render_response_example( oas_fragment( """ application/json: example: foo: bar baz: 42 text/plain: example: | foo = "bar" baz = 42 """ ), "200", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http HTTP/1.1 200 OK Content-Type: text/plain foo = "bar" baz = 42 """.rstrip() ) @pytest.mark.parametrize( ["example_preference_key"], [pytest.param("response-example-preference"), pytest.param("example-preference")], ) def test_render_response_example_preference_complex( fakestate, example_preference_key, oas_fragment ): """Path response's example from preferred media type is rendered.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {example_preference_key: ["application/json", "text/plain"]} ) markup = textify( testrenderer.render_response_example( oas_fragment( """ text/csv: example: | foo,baz bar,42 text/plain: example: | foo = "bar" baz = 42 application/json: schema: type: object """ ), "200", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http HTTP/1.1 200 OK Content-Type: text/plain foo = "bar" baz = 42 """.rstrip() ) def test_render_response_example_preference_priority(fakestate, oas_fragment): """Path response's example from preferred media type is rendered.""" testrenderer = renderers.HttpdomainRenderer( fakestate, { "example-preference": ["application/json"], "response-example-preference": ["text/plain"], }, ) markup = textify( testrenderer.render_response_example( oas_fragment( """ application/json: example: foo: bar baz: 42 text/plain: example: | foo = "bar" baz = 42 """ ), "200", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http HTTP/1.1 200 OK Content-Type: text/plain foo = "bar" baz = 42 """.rstrip() ) @responses.activate def test_render_response_example_external(testrenderer, oas_fragment): """Path response's example can be retrieved from external location.""" responses.add( responses.GET, "https://example.com/json/examples/test.json", json={"foo": "bar", "baz": 42}, status=200, ) markup = textify( testrenderer.render_response_example( oas_fragment( """ application/json: examples: test: externalValue: https://example.com/json/examples/test.json """ ), "200", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json {"foo": "bar", "baz": 42} """.rstrip() ) @responses.activate def test_render_response_example_external_errored_next_example( testrenderer, caplog, oas_fragment ): """Path response's example fallbacks on next when external cannot be retrieved.""" responses.add( responses.GET, "https://example.com/json/examples/test.json", status=404, ) markup = textify( testrenderer.render_response_example( oas_fragment( """ application/json: examples: test: externalValue: https://example.com/json/examples/test.json fallback: value: '{"spam": 42}' """ ), "200", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json {"spam": 42} """.rstrip() ) @responses.activate def test_render_response_example_external_errored_next_media_type( testrenderer, oas_fragment, caplog ): """Path response's example fallbacks on next when external cannot be retrieved.""" responses.add( responses.GET, "https://example.com/json/examples/test.json", status=404, ) markup = textify( testrenderer.render_response_example( oas_fragment( """ application/json: examples: test: externalValue: https://example.com/json/examples/test.json text/csv: example: spam,42 """ ), "200", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http HTTP/1.1 200 OK Content-Type: text/csv spam,42 """.rstrip() ) def test_render_response_example_content_type(testrenderer, oas_fragment): """Path response's example can render something other than application/json.""" markup = textify( testrenderer.render_response_example( oas_fragment( """ text/csv: example: | foo,baz bar,42 """ ), "200", ) ) assert markup == textwrap.dedent( """\ .. sourcecode:: http HTTP/1.1 200 OK Content-Type: text/csv foo,baz bar,42 """.rstrip() ) def test_render_response_example_noop(testrenderer, oas_fragment): """Path response's example is not rendered if there's nothing to render.""" markup = textify( testrenderer.render_response_example( oas_fragment( """ application/json: schema: type: object """ ), "200", ) ) assert markup == "" @pytest.mark.parametrize( ["status_code", "status_text"], [ pytest.param("201", "Created", id="201"), pytest.param("307", "Temporary Redirect", id="307"), pytest.param("422", "Unprocessable Entity", id="422"), ], ) def test_render_response_status_code( testrenderer, oas_fragment, status_code, status_text ): """Path response's example is rendered with proper status code.""" markup = textify( testrenderer.render_response_example( oas_fragment( """ text/csv: example: | foo,baz bar,42 """ ), status_code, ) ) assert markup == textwrap.dedent( f"""\ .. sourcecode:: http HTTP/1.1 {status_code} {status_text} Content-Type: text/csv foo,baz bar,42 """.rstrip() ) @pytest.mark.parametrize( ["status_range", "status_code", "status_text"], [ pytest.param("2XX", "200", "OK", id="2XX"), pytest.param("3XX", "300", "Multiple Choices", id="3XX"), pytest.param("4XX", "400", "Bad Request", id="4XX"), ], ) def test_render_response_status_code_range( testrenderer, oas_fragment, status_range, status_code, status_text ): """Path response's example is rendered with proper status range.""" markup = textify( testrenderer.render_response_example( oas_fragment( """ text/csv: example: | foo,baz bar,42 """ ), status_range, ) ) assert markup == textwrap.dedent( f"""\ .. sourcecode:: http HTTP/1.1 {status_code} {status_text} Content-Type: text/csv foo,baz bar,42 """.rstrip() ) @pytest.mark.parametrize( ["status_code", "status_text"], [ pytest.param("201", "Created", id="201"), pytest.param("307", "Temporary Redirect", id="307"), pytest.param("422", "Unprocessable Entity", id="422"), ], ) def test_render_response_status_code_int( testrenderer, oas_fragment, status_code, status_text ): """Path response's example is rendered with proper status code.""" markup = textify( testrenderer.render_response_example( oas_fragment( """ text/csv: example: | foo,baz bar,42 """ ), status_code, ) ) assert markup == textwrap.dedent( f"""\ .. sourcecode:: http HTTP/1.1 {status_code} {status_text} Content-Type: text/csv foo,baz bar,42 """.rstrip() ) def test_render_response_status_code_default(testrenderer, oas_fragment): """Path response's example is rendered when default is passed.""" markup = textify( testrenderer.render_response_example( oas_fragment( """ text/csv: example: | foo,baz bar,42 """ ), "default", ) ) assert markup == textwrap.dedent( f"""\ .. sourcecode:: http HTTP/1.1 000 Reason-Phrase Content-Type: text/csv foo,baz bar,42 """.rstrip() ) openapi-0.8.4/tests/renderers/httpdomain/test_render_responses.py000066400000000000000000000137331456264404500254720ustar00rootroot00000000000000"""OpenAPI spec renderer: render_responses.""" import textwrap from sphinxcontrib.openapi import renderers def textify(generator): return "\n".join(generator) def test_render_responses_no_items(testrenderer, oas_fragment): """No response definitions are rendered.""" markup = textify( testrenderer.render_responses( oas_fragment( """ {} """ ) ) ) assert markup == "" def test_render_responses_one_item(testrenderer, oas_fragment): """One usual response definition is rendered.""" markup = textify( testrenderer.render_responses( oas_fragment( """ '200': description: An evidence. """ ) ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. """.rstrip() ) def test_render_responses_one_item_status_code_int(testrenderer, oas_fragment): """One usual response definition is rendered even if status code is integer.""" markup = textify( testrenderer.render_responses( oas_fragment( """ 200: description: An evidence. """ ) ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. """.rstrip() ) def test_render_responses_many_items(testrenderer, oas_fragment): """Many response definitions are rendered.""" markup = textify( testrenderer.render_responses( oas_fragment( """ '200': description: An evidence. '404': description: An evidence not found. """ ) ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. :statuscode 404: An evidence not found. """.rstrip() ) def test_render_responses_json_schema_description(testrenderer, oas_fragment): """JSON schema description is rendered.""" markup = textify( testrenderer.render_responses( oas_fragment( """ '200': description: An evidence. content: application/json: schema: properties: foo: type: string bar: type: integer """ ) ) ) assert markup == textwrap.dedent( """\ :resjson foo: :resjsonobj foo: string :resjson bar: :resjsonobj bar: integer :statuscode 200: An evidence. """ ) def test_render_responses_json_schema_description_4xx(testrenderer, oas_fragment): """JSON schema description is rendered.""" markup = textify( testrenderer.render_responses( oas_fragment( """ '400': description: An evidence. content: application/json: schema: properties: foo: type: string bar: type: integer """ ) ) ) assert markup == textwrap.dedent( """\ :statuscode 400: An evidence. """.rstrip() ) def test_render_responses_json_schema_description_first_2xx(testrenderer, oas_fragment): """JSON schema description is rendered.""" markup = textify( testrenderer.render_responses( oas_fragment( """ '400': description: An error. content: application/json: schema: properties: aaa: type: string '200': description: An evidence. content: application/json: schema: properties: foo: type: string bar: type: integer '201': description: An evidence created. content: application/json: schema: properties: bbb: type: string """ ) ) ) assert markup == textwrap.dedent( """\ :resjson foo: :resjsonobj foo: string :resjson bar: :resjsonobj bar: integer :statuscode 400: An error. :statuscode 200: An evidence. :statuscode 201: An evidence created. """ ) def test_render_responses_json_schema_description_turned_off(fakestate, oas_fragment): """JSON schema description is not rendered b/c feature is off.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"no-json-schema-description": True}, ) markup = textify( testrenderer.render_responses( oas_fragment( """ '200': description: An evidence. content: application/json: schema: properties: foo: type: string bar: type: integer """ ) ) ) assert markup == textwrap.dedent( """\ :statuscode 200: An evidence. """ ) openapi-0.8.4/tests/renderers/httpdomain/test_render_restructuredtext_markup.py000066400000000000000000000372731456264404500304750ustar00rootroot00000000000000"""OpenAPI spec renderer: render_restructuredtext_markup.""" import textwrap from sphinxcontrib.openapi import renderers def textify(generator): return "\n".join(generator) def test_oas2_minimal(testrenderer, oas_fragment): """Minimal OAS 2 can be rendered.""" markup = textify( testrenderer.render_restructuredtext_markup( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 paths: /test: get: responses: '200': description: a response description """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /test :statuscode 200: a response description """ ) def test_oas2_complete(testrenderer, oas_fragment): """Feature rich OAS 2 can be rendered.""" markup = textify( testrenderer.render_restructuredtext_markup( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 paths: /test: get: responses: '200': description: a response description /{username}: parameters: - in: path name: username required: true type: string get: tags: - tag_a - tag_b summary: an operation summary description: an operation description externalDocs: https://docs.example.com/ operationId: myOperation produces: - application/json parameters: - in: header name: token type: string - in: query name: id type: string responses: '200': schema: items: format: int32 type: integer type: array description: a response description '404': description: a username not found """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /test :statuscode 200: a response description .. http:get:: /{username} **an operation summary** an operation description :reqheader token: :reqheadertype token: string :param username: :paramtype username: string, required :queryparam id: :queryparamtype id: string :statuscode 200: a response description :statuscode 404: a username not found """ ) def test_oas2_schema_example(testrenderer, oas_fragment): """Schema's 'example' property can be used in example snippets.""" markup = textify( testrenderer.render_restructuredtext_markup( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 paths: /test: get: description: an operation description produces: - application/json responses: '200': schema: example: | [ 19, 84 ] items: format: int32 type: integer type: array description: a response description """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /test an operation description :statuscode 200: a response description .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ 19, 84 ] """ ) def test_oas2_complete_generate_examples_from_schema(fakestate, oas_fragment): """Schema can be used to generate example snippets.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"generate-examples-from-schemas": True} ) markup = textify( testrenderer.render_restructuredtext_markup( oas_fragment( """ swagger: "2.0" info: title: An example spec version: 1.0 paths: /test: get: description: an operation description produces: - application/json responses: '200': schema: items: format: int32 type: integer type: array description: a response description """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /test an operation description :statuscode 200: a response description .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ 1, 1 ] """ ) def test_oas3_minimal(testrenderer, oas_fragment): """Minimal OAS 3 can be rendered.""" markup = textify( testrenderer.render_restructuredtext_markup( oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 paths: /test: get: responses: '200': description: a response description """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /test :statuscode 200: a response description """ ) def test_oas3_complete(testrenderer, oas_fragment): """Feature rich OAS 3 can be rendered.""" markup = textify( testrenderer.render_restructuredtext_markup( oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 paths: /test: get: responses: '200': description: a response description /{username}: parameters: - in: path name: username schema: type: string required: true get: tags: - tag_a - tag_b summary: an operation summary description: an operation description externalDocs: https://docs.example.com/ operationId: myOperation parameters: - in: header name: token schema: type: string - in: query name: id schema: type: string responses: '200': content: application/json: schema: items: format: int32 type: integer type: array description: a response description '404': description: a username not found """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /test :statuscode 200: a response description .. http:get:: /{username} **an operation summary** an operation description :reqheader token: :reqheadertype token: string :param username: :paramtype username: string, required :queryparam id: :queryparamtype id: string :statuscode 200: a response description :statuscode 404: a username not found """ ) def test_oas3_schema_example(testrenderer, oas_fragment): """Schema's 'example' property can be used in example snippets.""" markup = textify( testrenderer.render_restructuredtext_markup( oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 paths: /test: get: description: an operation description responses: '200': content: application/json: schema: example: | [ 19, 84 ] items: format: int32 type: integer type: array description: a response description """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /test an operation description :statuscode 200: a response description .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ 19, 84 ] """ ) def test_oas3_generate_examples_from_schema(fakestate, oas_fragment): """Schema can be used to generate example snippets.""" testrenderer = renderers.HttpdomainRenderer( fakestate, {"generate-examples-from-schemas": True} ) markup = textify( testrenderer.render_restructuredtext_markup( oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 paths: /test: get: description: an operation description responses: '200': content: application/json: schema: items: format: int32 type: integer type: array description: a response description """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /test an operation description :statuscode 200: a response description .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ 1, 1 ] """ ) def test_oas3_request_body(testrenderer, oas_fragment): """Request body example is rendered.""" markup = textify( testrenderer.render_restructuredtext_markup( oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 paths: /test: get: description: an operation description requestBody: content: application/json: examples: test: value: foo: bar baz: 42 responses: '200': description: a response description """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /test an operation description .. sourcecode:: http GET /test HTTP/1.1 Content-Type: application/json { "foo": "bar", "baz": 42 } :statuscode 200: a response description """ ) def test_oas3_response_example_2xx(testrenderer, oas_fragment): """Response examples are rendered for 2XX status codes.""" markup = textify( testrenderer.render_restructuredtext_markup( oas_fragment( """ openapi: 3.0.3 info: title: An example spec version: 1.0 paths: /test: get: description: an operation description responses: '200': content: application/json: example: | [ 19, 84 ] description: a response description '404': content: application/json: example: | { "message": "an error message" } description: resource not found """ ) ) ) assert markup == textwrap.dedent( """\ .. http:get:: /test an operation description :statuscode 200: a response description .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ 19, 84 ] :statuscode 404: resource not found """ ) openapi-0.8.4/tests/test_openapi.py000066400000000000000000002020521456264404500173770ustar00rootroot00000000000000""" tests.test_openapi ------------------ Tests some stuff of ``sphinxcontrib.openapi`` module. :copyright: (c) 2016, Ihor Kalnytskyi. :license: BSD, see LICENSE for details. """ import json import os import textwrap import collections from unittest import mock import py import pytest from sphinxcontrib.openapi import renderers from sphinxcontrib.openapi import openapi20 from sphinxcontrib.openapi import utils class TestOpenApi2HttpDomain(object): def test_basic(self): renderer = renderers.HttpdomainOldRenderer(None, {}) text = '\n'.join(renderer.render_restructuredtext_markup({ 'paths': { '/resources/{kind}': { 'get': { 'summary': 'List Resources', 'description': '~ some useful description ~', 'parameters': [ { 'name': 'kind', 'in': 'path', 'type': 'string', 'description': 'Kind of resource to list.', }, { 'name': 'limit', 'in': 'query', 'type': 'integer', 'description': 'Show up to `limit` entries.', }, { 'name': 'If-None-Match', 'in': 'header', 'type': 'string', 'description': 'Last known resource ETag.' }, ], 'responses': { '200': { 'description': 'An array of resources.', 'headers': { 'ETag': { 'description': 'Resource ETag.', 'type': 'string' }, }, }, }, }, }, }, })) assert text == textwrap.dedent(''' .. http:get:: /resources/{kind} :synopsis: List Resources **List Resources** ~ some useful description ~ :param string kind: Kind of resource to list. :query integer limit: Show up to `limit` entries. :status 200: An array of resources. :reqheader If-None-Match: Last known resource ETag. :resheader ETag: Resource ETag. ''').lstrip() def test_groups(self): renderer = renderers.HttpdomainOldRenderer(None, {'group': True}) text = '\n'.join(renderer.render_restructuredtext_markup({ 'tags': [ {'name': 'tags'}, {'name': 'pets'}, ], 'paths': collections.OrderedDict([ ('/', { 'get': { 'summary': 'Index', 'description': '~ some useful description ~', 'responses': { '200': { 'description': 'Index', 'content': { 'application/json': { 'pets': 'https://example.com/api/pets', 'tags': 'https://example.com/api/tags', } } }, }, }, }), ('/pets', { 'get': { 'summary': 'List Pets', 'description': '~ some useful description ~', 'responses': { '200': { 'description': 'Pets', 'content': { 'application/json': [ { 'example': '{"foo": "bar"}' }, ], }, }, }, 'tags': [ 'pets', ], }, }), ('/pets/{name}', { 'get': { 'summary': 'Show Pet', 'description': '~ some useful description ~', 'parameters': [ { 'name': 'name', 'in': 'path', 'type': 'string', 'description': 'Name of pet.', }, ], 'responses': { '200': { 'description': 'A Pet', 'content': { 'application/json': { 'example': '{"foo": "bar"}' } } }, }, 'tags': [ 'pets', ], }, }), ('/tags', { 'get': { 'summary': 'List Tags', 'description': '~ some useful description ~', 'responses': { '200': { 'description': 'Tags', 'content': { 'application/json': [ { 'example': '{"foo": "bar"}' }, ], } }, }, 'tags': [ 'tags', 'pets', ], }, }), ]), })) assert text == textwrap.dedent(''' tags ==== .. http:get:: /tags :synopsis: List Tags **List Tags** ~ some useful description ~ :status 200: Tags pets ==== .. http:get:: /pets :synopsis: List Pets **List Pets** ~ some useful description ~ :status 200: Pets .. http:get:: /pets/{name} :synopsis: Show Pet **Show Pet** ~ some useful description ~ :param string name: Name of pet. :status 200: A Pet default ======= .. http:get:: / :synopsis: Index **Index** ~ some useful description ~ :status 200: Index ''').lstrip() def test_two_resources(self): spec = collections.defaultdict(collections.OrderedDict) spec['paths']['/resource_a'] = { 'get': { 'description': 'resource a', 'responses': { '200': {'description': 'ok'}, } } } spec['paths']['/resource_b'] = { 'post': { 'description': 'resource b', 'responses': { '404': {'description': 'error'}, } } } renderer = renderers.HttpdomainOldRenderer(None, {}) text = '\n'.join(renderer.render_restructuredtext_markup(spec)) assert text == textwrap.dedent(''' .. http:get:: /resource_a :synopsis: null resource a :status 200: ok .. http:post:: /resource_b :synopsis: null resource b :status 404: error ''').lstrip() def test_path_option(self): spec = collections.defaultdict(collections.OrderedDict) spec['paths']['/resource_a'] = { 'get': { 'description': 'resource a', 'responses': { '200': {'description': 'ok'}, } } } spec['paths']['/resource_b'] = { 'post': { 'description': 'resource b', 'responses': { '404': {'description': 'error'}, } } } renderer = renderers.HttpdomainOldRenderer(None, {'paths': [ '/resource_a', ]}) text = '\n'.join(renderer.render_restructuredtext_markup(spec)) assert text == textwrap.dedent(''' .. http:get:: /resource_a :synopsis: null resource a :status 200: ok ''').lstrip() def test_include_option(self): spec = collections.defaultdict(collections.OrderedDict) spec['paths']['/resource_a'] = { 'get': { 'description': 'resource a', 'responses': { '200': {'description': 'ok'}, } } } spec['paths']['/resource_b'] = { 'post': { 'description': 'resource b', 'responses': { '404': {'description': 'error'}, } } } renderer = renderers.HttpdomainOldRenderer(None, {'include': [ '/resource', ]}) text = '\n'.join(renderer.render_restructuredtext_markup(spec)) assert text == textwrap.dedent(''' .. http:get:: /resource_a :synopsis: null resource a :status 200: ok .. http:post:: /resource_b :synopsis: null resource b :status 404: error ''').lstrip() def test_exclude_option(self): spec = collections.defaultdict(collections.OrderedDict) spec['paths']['/resource_a'] = { 'get': { 'description': 'resource a', 'responses': { '200': {'description': 'ok'}, } } } spec['paths']['/resource_b'] = { 'post': { 'description': 'resource b', 'responses': { '404': {'description': 'error'}, } } } renderer = renderers.HttpdomainOldRenderer(None, {'exclude': [ '/.*_a', ]}) text = '\n'.join(renderer.render_restructuredtext_markup(spec)) assert text == textwrap.dedent(''' .. http:post:: /resource_b :synopsis: null resource b :status 404: error ''').lstrip() def test_method_option(self): spec = collections.defaultdict(collections.OrderedDict) spec['paths']['/resource_a'] = { 'get': { 'description': 'resource a', 'responses': { '200': {'description': 'ok'}, } }, 'post': { 'description': 'resource a', 'responses': { '201': {'description': 'ok'}, } }, 'put': { 'description': 'resource a', 'responses': { '404': {'description': 'error'}, } } } renderer = renderers.HttpdomainOldRenderer( None, { 'methods': ['post'], 'paths': ['/resource_a'], }, ) text = '\n'.join(renderer.render_restructuredtext_markup(spec)) assert text == textwrap.dedent(''' .. http:post:: /resource_a :synopsis: null resource a :status 201: ok ''').lstrip() def test_root_parameters(self): spec = {'paths': {}} spec['paths']['/resources/{name}'] = collections.OrderedDict() spec['paths']['/resources/{name}']['parameters'] = [ { 'name': 'name', 'in': 'path', 'type': 'string', 'description': 'The name of the resource.', } ] spec['paths']['/resources/{name}']['get'] = { 'summary': 'Fetch a Resource', 'description': '~ some useful description ~', 'responses': { '200': { 'description': 'The fetched resource.', }, }, } spec['paths']['/resources/{name}']['put'] = { 'summary': 'Modify a Resource', 'description': '~ some useful description ~', 'responses': { '200': { 'description': 'The modified resource.', }, }, } renderer = renderers.HttpdomainOldRenderer(None, {}) text = '\n'.join(renderer.render_restructuredtext_markup(spec)) assert text == textwrap.dedent(''' .. http:get:: /resources/{name} :synopsis: Fetch a Resource **Fetch a Resource** ~ some useful description ~ :param string name: The name of the resource. :status 200: The fetched resource. .. http:put:: /resources/{name} :synopsis: Modify a Resource **Modify a Resource** ~ some useful description ~ :param string name: The name of the resource. :status 200: The modified resource. ''').lstrip() def test_path_invalid(self): spec = collections.defaultdict(collections.OrderedDict) spec['paths']['/resource_a'] = { 'get': { 'description': 'resource a', 'responses': { '200': {'description': 'ok'}, } } } spec['paths']['/resource_b'] = { 'post': { 'description': 'resource b', 'responses': { '404': {'description': 'error'}, } } } renderer = renderers.HttpdomainOldRenderer(None, {'paths': [ '/resource_a', '/resource_invalid_name', ]}) with pytest.raises(ValueError) as exc: '\n'.join(renderer.render_restructuredtext_markup(spec)) assert str(exc.value) == ( 'One or more paths are not defined in the spec: ' '/resource_invalid_name.' ) def test_unicode_is_allowed(self): spec = { 'paths': { '/resource_a': { 'get': { 'description': '\u041f', 'responses': { '200': {'description': 'ok'} } } } } } renderer = renderers.HttpdomainOldRenderer(None, {}) text = '\n'.join(renderer.render_restructuredtext_markup(spec)) assert text == textwrap.dedent(''' .. http:get:: /resource_a :synopsis: null \u041f :status 200: ok ''').lstrip() def test_json_in_out(self): renderer = renderers.HttpdomainOldRenderer(None, {}) text = '\n'.join(renderer.render_restructuredtext_markup({ 'definitions': { 'CreateResourceSchema': { 'additionalProperties': False, 'properties': { 'string_field': { 'type': 'string', 'description': 'some input string' }, 'int_field': { 'default': 1, 'type': 'integer', }, }, 'required': [ 'string_field' ], 'title': 'CreateResourceSchema', 'type': 'object' }, 'ResourceSchema': { 'properties': { 'string_field': { 'type': 'string', 'description': 'some output string' }, 'int_field': { 'type': 'integer', }, }, 'required': [ 'string_field' ], 'title': 'ResourceSchema', 'type': 'object' }, 'Error': { 'properties': { 'errors': { 'type': 'object' }, 'message': { 'type': 'string' } }, 'required': [ 'message' ], 'title': 'Error', 'type': 'object' }, }, 'paths': { '/resources': { 'post': { 'description': '~ some useful description ~', 'parameters': [ { 'in': 'body', 'name': 'CreateResourceSchema', 'required': True, 'schema': { '$ref': '#/definitions/CreateResourceSchema' } }, ], 'responses': { '201': { 'description': '~ some useful description ~', 'schema': { '$ref': '#/definitions/ResourceSchema' } }, 'default': { 'description': '~ some useful description ~', 'schema': { '$ref': '#/definitions/Error' } } }, }, }, }, })) text2 = textwrap.dedent(''' .. http:post:: /resources :synopsis: null ~ some useful description ~ :json integer int_field: :>json string string_field: some output string (required) ''').lstrip() assert text == text2 class TestOpenApi3HttpDomain(object): def test_basic(self): renderer = renderers.HttpdomainOldRenderer(None, {}) text = '\n'.join(renderer.render_restructuredtext_markup({ 'openapi': '3.0.0', 'paths': { '/resources/{kind}': { 'get': { 'summary': 'List Resources', 'description': '~ some useful description ~', 'parameters': [ { 'name': 'kind', 'in': 'path', 'schema': {'type': 'string'}, 'description': 'Kind of resource to list.', }, { 'name': 'limit', 'in': 'query', 'schema': {'type': 'integer'}, 'description': 'Show up to `limit` entries.', }, { 'name': 'If-None-Match', 'in': 'header', 'schema': {'type': 'string'}, 'description': 'Last known resource ETag.' }, ], 'requestBody': { 'content': { 'application/json': { 'example': '{"foo2": "bar2"}' } } }, 'responses': { '200': { 'description': 'An array of resources.', 'content': { 'application/json': { 'example': '{"foo": "bar"}' } } }, }, }, }, }, })) assert text == textwrap.dedent(''' .. http:get:: /resources/{kind} :synopsis: List Resources **List Resources** ~ some useful description ~ :param string kind: Kind of resource to list. :query integer limit: Show up to `limit` entries. :status 200: An array of resources. :reqheader If-None-Match: Last known resource ETag. ''').lstrip() def test_rfc7807(self): # Fix order to have a reliable test pb_example = collections.OrderedDict() pb_example["type"] = "string" pb_example["title"] = "string" pb_example["status"] = 1 pb_example["detail"] = "string" pb_example["instance"] = "string" renderer = renderers.HttpdomainOldRenderer(None, {'examples': True}) text = '\n'.join(renderer.render_restructuredtext_markup({ 'openapi': '3.0.0', 'paths': { '/problem': { 'post': { 'summary': 'Problem', 'description': '~ some useful description ~', 'requestBody': { 'content': { 'application/problem+json': { 'example': pb_example } } }, 'responses': { '200': { 'description': 'An array of resources.', 'content': { 'application/json': { 'example': '{"foo": "bar"}' } } }, }, }, }, }, })) assert text == textwrap.dedent(''' .. http:post:: /problem :synopsis: Problem **Problem** ~ some useful description ~ **Example request:** .. sourcecode:: http POST /problem HTTP/1.1 Host: example.com Content-Type: application/problem+json { "type": "string", "title": "string", "status": 1, "detail": "string", "instance": "string" } :status 200: An array of resources. **Example response:** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json {"foo": "bar"} ''').lstrip() def test_groups(self): renderer = renderers.HttpdomainOldRenderer(None, {'group': True}) text = '\n'.join(renderer.render_restructuredtext_markup({ 'openapi': '3.0.0', 'tags': [ {'name': 'tags'}, {'name': 'pets'}, ], 'paths': collections.OrderedDict([ ('/', { 'get': { 'summary': 'Index', 'description': '~ some useful description ~', 'responses': { '200': { 'description': 'Index', 'content': { 'application/json': { 'pets': 'https://example.com/api/pets', 'tags': 'https://example.com/api/tags', } } }, }, }, }), ('/pets', { 'get': { 'summary': 'List Pets', 'description': '~ some useful description ~', 'responses': { '200': { 'description': 'Pets', 'content': { 'application/json': [ { 'example': '{"foo": "bar"}' }, ], }, }, }, 'tags': [ 'pets', ], }, }), ('/pets/{name}', { 'get': { 'summary': 'Show Pet', 'description': '~ some useful description ~', 'parameters': [ { 'name': 'name', 'in': 'path', 'schema': {'type': 'string'}, 'description': 'Name of pet.', }, ], 'responses': { '200': { 'description': 'A Pet', 'content': { 'application/json': { 'example': '{"foo": "bar"}' } } }, }, 'tags': [ 'pets', ], }, }), ('/tags', { 'get': { 'summary': 'List Tags', 'description': '~ some useful description ~', 'responses': { '200': { 'description': 'Tags', 'content': { 'application/json': [ { 'example': '{"foo": "bar"}' }, ], } }, }, 'tags': [ 'tags', 'pets', ], }, }), ]), })) assert text == textwrap.dedent(''' tags ==== .. http:get:: /tags :synopsis: List Tags **List Tags** ~ some useful description ~ :status 200: Tags pets ==== .. http:get:: /pets :synopsis: List Pets **List Pets** ~ some useful description ~ :status 200: Pets .. http:get:: /pets/{name} :synopsis: Show Pet **Show Pet** ~ some useful description ~ :param string name: Name of pet. :status 200: A Pet default ======= .. http:get:: / :synopsis: Index **Index** ~ some useful description ~ :status 200: Index ''').lstrip() def test_required_parameters(self): renderer = renderers.HttpdomainOldRenderer(None, {}) text = '\n'.join(renderer.render_restructuredtext_markup({ 'openapi': '3.0.0', 'paths': { '/resources/{kind}': { 'get': { 'summary': 'List Resources', 'description': '~ some useful description ~', 'parameters': [ { 'name': 'kind', 'in': 'path', 'schema': {'type': 'string'}, 'description': 'Kind of resource to list.', }, { 'name': 'limit', 'in': 'query', 'required': True, 'schema': {'type': 'integer'}, 'description': 'Show up to `limit` entries.', }, { 'name': 'If-None-Match', 'in': 'header', 'required': True, 'schema': {'type': 'string'}, 'description': 'Last known resource ETag.' }, ], 'requestBody': { 'content': { 'application/json': { 'example': '{"foo2": "bar2"}' } } }, 'responses': { '200': { 'description': 'An array of resources.', 'content': { 'application/json': { 'example': '{"foo": "bar"}' } } }, }, }, }, }, })) assert text == textwrap.dedent(''' .. http:get:: /resources/{kind} :synopsis: List Resources **List Resources** ~ some useful description ~ :param string kind: Kind of resource to list. :query integer limit: Show up to `limit` entries. (Required) :status 200: An array of resources. :reqheader If-None-Match: Last known resource ETag. (Required) ''').lstrip() def test_example_generation(self): renderer = renderers.HttpdomainOldRenderer(None, {'examples': True}) text = '\n'.join(renderer.render_restructuredtext_markup({ 'openapi': '3.0.0', 'paths': collections.OrderedDict([ ('/resources/', collections.OrderedDict([ ('get', { 'summary': 'List Resources', 'description': '~ some useful description ~', 'parameters': [ { 'name': 'kind', 'in': 'path', 'schema': {'type': 'string'}, 'description': 'Kind of resource to list.', }, { 'name': 'limit', 'in': 'query', 'required': True, 'schema': {'type': 'integer'}, 'description': 'Show up to `limit` entries.', }, { 'name': 'If-None-Match', 'in': 'header', 'schema': {'type': 'string'}, 'description': 'Last known resource ETag.' }, ], 'responses': { '200': { 'description': 'An array of resources.', 'content': { 'application/json': { 'schema': { 'type': 'array', 'items': { '$ref': '#/components/schemas/Resource', # noqa }, }, } } }, }, }), ('post', { 'summary': 'Create Resource', 'description': '~ some useful description ~', 'parameters': [], 'requestBody': { 'content': { 'application/json': { 'schema': { '$ref': '#/components/schemas/Resource', # noqa }, } } }, 'responses': { '200': { 'description': 'The created resource.', 'content': { 'application/json': { 'schema': { '$ref': '#/components/schemas/Resource', # noqa }, } } }, }, }), ])), ('/resources/{kind}', collections.OrderedDict([ ('get', { 'summary': 'Show Resource', 'description': '~ some useful description ~', 'parameters': [ { 'name': 'kind', 'in': 'path', 'schema': {'type': 'string'}, 'description': 'Kind of resource to list.', }, ], 'responses': { '200': { 'description': 'The created resource.', 'content': { 'application/json': { 'schema': { '$ref': '#/components/schemas/Resource', # noqa }, } } }, }, }), ('patch', { 'summary': 'Update Resource (partial)', 'description': '~ some useful description ~', 'parameters': [ { 'name': 'kind', 'in': 'path', 'schema': {'type': 'string'}, 'description': 'Kind of resource to list.', }, ], 'requestBody': { 'content': { 'application/json': { 'schema': { '$ref': '#/components/schemas/Resource', # noqa }, } } }, 'responses': { '200': { 'description': 'The created resource.', 'content': { 'application/json': { 'schema': { '$ref': '#/components/schemas/Resource', # noqa }, } } }, }, }), ])), ]), 'components': { 'schemas': { 'Resource': { 'type': 'object', 'properties': collections.OrderedDict([ ('kind', { 'title': 'Kind', 'type': 'string', 'readOnly': True, }), ('description', { 'title': 'Description', 'type': 'string', }), ('data', { 'title': 'Data', 'type': 'string', 'format': 'byte', }), ]), }, }, }, })) assert text == textwrap.dedent(''' .. http:get:: /resources/ :synopsis: List Resources **List Resources** ~ some useful description ~ :param string kind: Kind of resource to list. :query integer limit: Show up to `limit` entries. (Required) **Example request:** .. sourcecode:: http GET /resources/?limit=1 HTTP/1.1 Host: example.com :status 200: An array of resources. **Example response:** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "kind": "string", "description": "string", "data": "c3RyaW5n" } ] :reqheader If-None-Match: Last known resource ETag. .. http:post:: /resources/ :synopsis: Create Resource **Create Resource** ~ some useful description ~ **Example request:** .. sourcecode:: http POST /resources/ HTTP/1.1 Host: example.com Content-Type: application/json { "description": "string", "data": "c3RyaW5n" } :status 200: The created resource. **Example response:** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "kind": "string", "description": "string", "data": "c3RyaW5n" } .. http:get:: /resources/{kind} :synopsis: Show Resource **Show Resource** ~ some useful description ~ :param string kind: Kind of resource to list. **Example request:** .. sourcecode:: http GET /resources/{kind} HTTP/1.1 Host: example.com :status 200: The created resource. **Example response:** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "kind": "string", "description": "string", "data": "c3RyaW5n" } .. http:patch:: /resources/{kind} :synopsis: Update Resource (partial) **Update Resource (partial)** ~ some useful description ~ :param string kind: Kind of resource to list. **Example request:** .. sourcecode:: http PATCH /resources/{kind} HTTP/1.1 Host: example.com Content-Type: application/json { "description": "string", "data": "c3RyaW5n" } :status 200: The created resource. **Example response:** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "kind": "string", "description": "string", "data": "c3RyaW5n" } ''').lstrip() def test_get_example_with_explode(self): renderer = renderers.HttpdomainOldRenderer(None, {'examples': True}) text = '\n'.join(renderer.render_restructuredtext_markup({ 'openapi': '3.0.0', 'paths': collections.OrderedDict([ ('/resources/', collections.OrderedDict([ ('get', { 'summary': 'List Resources', 'description': '~ some useful description ~', 'parameters': [ { 'name': 'params', 'in': 'query', 'required': True, 'schema': { 'type': 'array', 'items': { 'type': 'string' } }, 'style': 'form', 'explode': True, 'example': [ 'p1', 'p2', ], 'description': 'List with explode set to True' }, { 'name': 'values', 'in': 'query', 'required': True, 'schema': { 'type': 'object', 'additionalProperties': True }, 'style': 'form', 'explode': True, 'example': collections.OrderedDict([ ('v1', 'V1'), ('v2', 'V2'), ]), 'description': 'Dict with explode set to True' }, ], 'responses': { '200': { 'description': 'OK' }, }, }), ])), ]), })) assert text == textwrap.dedent(''' .. http:get:: /resources/ :synopsis: List Resources **List Resources** ~ some useful description ~ :query array params: List with explode set to True (Required) :query object values: Dict with explode set to True (Required) **Example request:** .. sourcecode:: http GET /resources/?params=p1¶ms=p2&v1=V1&v2=V2 HTTP/1.1 Host: example.com :status 200: OK ''').lstrip() def test_callback(self): renderer = renderers.HttpdomainOldRenderer(None, {}) text = '\n'.join(renderer.render_restructuredtext_markup({ 'openapi': '3.0.0', 'paths': { '/resources/{kind}': { 'post': { 'summary': 'List Resources', 'description': '~ some useful description ~', 'parameters': [ { 'name': 'kind', 'in': 'path', 'schema': {'type': 'string'}, 'description': 'Kind of resource to list.', }, { 'name': 'callback', 'in': 'query', 'description': 'the callback address', 'required': False, 'schema': { 'type': 'string', 'format': 'uri' }, 'example': 'http://client.com/callback' } ], 'requestBody': { 'content': { 'application/json': { 'example': '{"foo2": "bar2"}' } } }, 'responses': { '202': { 'description': 'Something', 'content': { 'application/json': { 'example': '{"foo": "bar"}' } } }, }, 'callbacks': { 'callback': { '${request.query.callback}': { 'post': { 'summary': 'Response callback', 'operationId': 'sampleCB', 'requestBody': { 'required': True, 'description': 'Result', 'content': { 'application/json': { 'schema': { 'type': 'object', 'required': ['status'], 'properties': { 'status': { 'type': 'string', 'enum': [ 'OK', 'ERROR' ] } } } } } }, 'responses': { '200': { 'description': 'Success' } } } } } } }, }, }, })) assert text == textwrap.dedent(''' .. http:post:: /resources/{kind} :synopsis: List Resources **List Resources** ~ some useful description ~ :param string kind: Kind of resource to list. :query string callback: the callback address :status 202: Something .. admonition:: Callback: callback .. http:post:: ${request.query.callback} :synopsis: Response callback **Response callback** :status 200: Success ''').lstrip() def test_string_example(self): renderer = renderers.HttpdomainOldRenderer(None, {'examples': True}) text = '\n'.join(renderer.render_restructuredtext_markup({ 'openapi': '3.0.0', 'paths': { '/resources': { 'get': { 'summary': 'Get resources', 'responses': { '200': { 'description': 'Something', 'content': { 'application/json': { 'schema': { 'type': 'string', 'example': '"A sample"', } } } }, }, }, }, }, })) assert text == textwrap.dedent(''' .. http:get:: /resources :synopsis: Get resources **Get resources** **Example request:** .. sourcecode:: http GET /resources HTTP/1.1 Host: example.com :status 200: Something **Example response:** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json "A sample" ''').lstrip() def test_ref_example(self): renderer = renderers.HttpdomainOldRenderer(None, {'examples': True}) text = '\n'.join(renderer.render_restructuredtext_markup({ 'openapi': '3.0.0', 'paths': { '/resources': { 'get': { 'summary': 'Get resources', 'responses': { '200': { 'description': 'Something', 'content': { 'application/json': { 'schema': { '$ref': '#/components/schemas/Data', } } } }, }, }, }, }, 'components': { 'schemas': { 'Data': { 'type': 'object', 'additionalProperties': True, 'example': { 'prop1': "Sample 1", } } } } })) assert text == textwrap.dedent(''' .. http:get:: /resources :synopsis: Get resources **Get resources** **Example request:** .. sourcecode:: http GET /resources HTTP/1.1 Host: example.com :status 200: Something **Example response:** .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "prop1": "Sample 1" } ''').lstrip() def test_method_option(self): spec = collections.defaultdict(collections.OrderedDict) spec['paths']['/resource_a'] = { 'get': { 'description': 'resource a', 'responses': { '200': {'description': 'ok'}, } }, 'post': { 'description': 'resource a', 'responses': { '201': {'description': 'ok'}, } }, 'put': { 'description': 'resource a', 'responses': { '404': {'description': 'error'}, } } } renderer = renderers.HttpdomainOldRenderer( None, { 'methods': ['post'], 'paths': ['/resource_a'], }, ) text = '\n'.join(renderer.render_restructuredtext_markup(spec)) assert text == textwrap.dedent(''' .. http:post:: /resource_a :synopsis: null resource a :status 201: ok ''').lstrip() class TestResolveRefs(object): def test_ref_resolving(self): data = { 'foo': { 'a': 13, 'b': { 'c': True, } }, 'bar': { '$ref': '#/foo/b' }, 'baz': [ {'$ref': '#/foo/a'}, {'$ref': '#/foo/b'}, 'batman', ] } assert utils._resolve_refs('', data) == { 'foo': { 'a': 13, 'b': { 'c': True, } }, 'bar': { 'c': True, }, 'baz': [ 13, {'c': True}, 'batman', ] } def test_relative_ref_resolving_on_fs(self): baseuri = 'file://%s' % os.path.abspath(__file__) data = { 'bar': { '$ref': 'testdata/foo.json#/foo/b', }, # check also JSON to YAML references: 'baz': { '$ref': 'testdata/foo.yaml#/foo', } } # import pdb # pdb.set_trace() assert utils._resolve_refs(baseuri, data) == { 'bar': { 'c': True, }, 'baz': { 'a': 17, 'b': 13, }, } @mock.patch('requests.get') def test_relative_ref_resolving_remote(self, mock_get): baseuri = os.path.abspath(__file__) with open( os.path.join(os.path.dirname(baseuri), 'testdata', 'foo.json'), 'r', encoding='utf-8' ) as file: json_content = json.loads(file.read()) with open(os.path.join(os.path.dirname(baseuri), 'testdata', 'foo.yaml'), 'rb') as file: yaml_content = file.read() def get_side_effect(path): nonlocal json_content if path.endswith('.json'): return mock.Mock(json=mock.Mock(return_value=json_content)) return mock.Mock(content=yaml_content, read=mock.Mock(side_effect=Exception)) mock_get.side_effect = get_side_effect data = { 'bar': { '$ref': 'testdata/foo.json#/foo/b', }, # check also JSON to YAML references: 'baz': { '$ref': 'testdata/foo.yaml#/foo', } } assert utils._resolve_refs('https://some/remote/file', data) == { 'bar': { 'c': True, }, 'baz': { 'a': 17, 'b': 13, }, } def test_noproperties(self): renderer = renderers.HttpdomainOldRenderer(None, {'examples': True}) text = '\n'.join(renderer.render_restructuredtext_markup({ 'openapi': '3.0.0', 'paths': { '/resources': { 'post': { 'summary': 'Create Resources', 'description': '~ some useful description ~', 'requestBody': { 'content': { 'application/json': { 'schema': { '$ref': '#/components/schemas/Resource', # noqa } } } }, 'responses': { '200': { 'description': 'Something', }, }, }, }, }, 'components': { 'schemas': { 'Resource': { 'type': 'object', 'additionalProperties': True, }, }, }, })) assert text == textwrap.dedent(''' .. http:post:: /resources :synopsis: Create Resources **Create Resources** ~ some useful description ~ **Example request:** .. sourcecode:: http POST /resources HTTP/1.1 Host: example.com Content-Type: application/json {} :status 200: Something ''').lstrip() def test_openapi2_examples(tmpdir, run_sphinx): spec = os.path.join( os.path.abspath(os.path.dirname(__file__)), 'OpenAPI-Specification', 'examples', 'v2.0', 'json', 'uber.json') py.path.local(spec).copy(tmpdir.join('src', 'test-spec.yml')) with pytest.raises(ValueError) as excinfo: run_sphinx('test-spec.yml', options={'examples': True}) assert str(excinfo.value) == ( 'Rendering examples is not supported for OpenAPI v2.x specs.') @pytest.mark.parametrize('render_examples', [False, True]) def test_openapi3_examples(tmpdir, run_sphinx, render_examples): spec = os.path.join( os.path.abspath(os.path.dirname(__file__)), 'OpenAPI-Specification', 'examples', 'v3.0', 'petstore.yaml') py.path.local(spec).copy(tmpdir.join('src', 'test-spec.yml')) run_sphinx('test-spec.yml', options={'examples': render_examples}) rendered_html = tmpdir.join('out', 'index.html').read_text('utf-8') assert ('Example response:' in rendered_html) \ == render_examples class TestConvertJsonSchema(object): schema = { 'type': 'object', 'required': ['name', 'surprise'], 'properties': { 'name': { 'type': 'string', 'description': 'The name of user'}, 'alias': { 'type': 'array', 'items': { 'type': 'string'}, 'description': 'The list of user alias'}, 'id': { 'type': 'integer', 'description': 'the id of user', 'readOnly': True}, 'surprise': { 'type': 'string'}, 'secret': { 'type': 'string', 'readOnly': True}}} result = list(openapi20.convert_json_schema(schema)) def test_required_field_with_description(self): assert ':
The response also includes low and high estimates, and the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code for situations requiring currency conversion. When surge is active for a particular product, its surge_multiplier will be greater than 1, but the price estimate already factors in this multiplier.", "parameters": [ { "name": "start_latitude", "in": "query", "description": "Latitude component of start location.", "required": true, "type": "number", "format": "double" }, { "name": "start_longitude", "in": "query", "description": "Longitude component of start location.", "required": true, "type": "number", "format": "double" }, { "name": "end_latitude", "in": "query", "description": "Latitude component of end location.", "required": true, "type": "number", "format": "double" }, { "name": "end_longitude", "in": "query", "description": "Longitude component of end location.", "required": true, "type": "number", "format": "double" } ], "tags": [ "Estimates" ], "responses": { "200": { "description": "An array of price estimates by product", "schema": { "type": "array", "items": { "$ref": "#/definitions/PriceEstimate" } } }, "default": { "description": "Unexpected error", "schema": { "$ref": "#/definitions/Error" } } } } }, "/estimates/time": { "get": { "summary": "Time Estimates", "description": "The Time Estimates endpoint returns ETAs for all products offered at a given location, with the responses expressed as integers in seconds. We recommend that this endpoint be called every minute to provide the most accurate, up-to-date ETAs.", "parameters": [ { "name": "start_latitude", "in": "query", "description": "Latitude component of start location.", "required": true, "type": "number", "format": "double" }, { "name": "start_longitude", "in": "query", "description": "Longitude component of start location.", "required": true, "type": "number", "format": "double" }, { "name": "customer_uuid", "in": "query", "type": "string", "format": "uuid", "description": "Unique customer identifier to be used for experience customization." }, { "name": "product_id", "in": "query", "type": "string", "description": "Unique identifier representing a specific product for a given latitude & longitude." } ], "tags": [ "Estimates" ], "responses": { "200": { "description": "An array of products", "schema": { "type": "array", "items": { "$ref": "#/definitions/Product" } } }, "default": { "description": "Unexpected error", "schema": { "$ref": "#/definitions/Error" } } } } }, "/me": { "get": { "summary": "User Profile", "description": "The User Profile endpoint returns information about the Uber user that has authorized with the application.", "tags": [ "User" ], "responses": { "200": { "description": "Profile information for a user", "schema": { "$ref": "#/definitions/Profile" } }, "default": { "description": "Unexpected error", "schema": { "$ref": "#/definitions/Error" } } } } }, "/history": { "get": { "summary": "User Activity", "description": "The User Activity endpoint returns data about a user's lifetime activity with Uber. The response will include pickup locations and times, dropoff locations and times, the distance of past requests, and information about which products were requested.

The history array in the response will have a maximum length based on the limit parameter. The response value count may exceed limit, therefore subsequent API requests may be necessary.", "parameters": [ { "name": "offset", "in": "query", "type": "integer", "format": "int32", "description": "Offset the list of returned results by this amount. Default is zero." }, { "name": "limit", "in": "query", "type": "integer", "format": "int32", "description": "Number of items to retrieve. Default is 5, maximum is 100." } ], "tags": [ "User" ], "responses": { "200": { "description": "History information for the given user", "schema": { "$ref": "#/definitions/Activities" } }, "default": { "description": "Unexpected error", "schema": { "$ref": "#/definitions/Error" } } } } } }, "definitions": { "Product": { "properties": { "product_id": { "type": "string", "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles." }, "description": { "type": "string", "description": "Description of product." }, "display_name": { "type": "string", "description": "Display name of product." }, "capacity": { "type": "string", "description": "Capacity of product. For example, 4 people." }, "image": { "type": "string", "description": "Image URL representing the product." } } }, "PriceEstimate": { "properties": { "product_id": { "type": "string", "description": "Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles" }, "currency_code": { "type": "string", "description": "[ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code." }, "display_name": { "type": "string", "description": "Display name of product." }, "estimate": { "type": "string", "description": "Formatted string of estimate in local currency of the start location. Estimate could be a range, a single number (flat rate) or \"Metered\" for TAXI." }, "low_estimate": { "type": "number", "description": "Lower bound of the estimated price." }, "high_estimate": { "type": "number", "description": "Upper bound of the estimated price." }, "surge_multiplier": { "type": "number", "description": "Expected surge multiplier. Surge is active if surge_multiplier is greater than 1. Price estimate already factors in the surge multiplier." } } }, "Profile": { "properties": { "first_name": { "type": "string", "description": "First name of the Uber user." }, "last_name": { "type": "string", "description": "Last name of the Uber user." }, "email": { "type": "string", "description": "Email address of the Uber user" }, "picture": { "type": "string", "description": "Image URL of the Uber user." }, "promo_code": { "type": "string", "description": "Promo code of the Uber user." } } }, "Activity": { "properties": { "uuid": { "type": "string", "description": "Unique identifier for the activity" } } }, "Activities": { "properties": { "offset": { "type": "integer", "format": "int32", "description": "Position in pagination." }, "limit": { "type": "integer", "format": "int32", "description": "Number of items to retrieve (100 max)." }, "count": { "type": "integer", "format": "int32", "description": "Total number of items available." }, "history": { "type": "array", "items": { "$ref": "#/definitions/Activity" } } } }, "Error": { "properties": { "code": { "type": "integer", "format": "int32" }, "message": { "type": "string" }, "fields": { "type": "string" } } } } } openapi-0.8.4/tests/testspecs/v2.0/uber.yaml000066400000000000000000000222511456264404500206570ustar00rootroot00000000000000# this is an example of the Uber API # as a demonstration of an API spec in YAML swagger: "2.0" info: title: Uber API description: Move your app forward with the Uber API version: "1.0.0" # the domain of the service host: api.uber.com # array of all schemes that your API supports schemes: - https # will be prefixed to all paths basePath: /v1 securityDefinitions: apikey: type: apiKey name: server_token in: query produces: - application/json paths: /products: get: summary: Product Types description: The Products endpoint returns information about the Uber products offered at a given location. The response includes the display name and other details about each product, and lists the products in the proper display order. parameters: - name: latitude in: query description: Latitude component of location. required: true type: number format: double - name: longitude in: query description: Longitude component of location. required: true type: number format: double security: - apikey: [] tags: - Products responses: "200": description: An array of products schema: type: array items: $ref: '#/definitions/Product' default: description: Unexpected error schema: $ref: '#/definitions/Error' /estimates/price: get: summary: Price Estimates description: The Price Estimates endpoint returns an estimated price range for each product offered at a given location. The price estimate is provided as a formatted string with the full price range and the localized currency symbol.

The response also includes low and high estimates, and the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code for situations requiring currency conversion. When surge is active for a particular product, its surge_multiplier will be greater than 1, but the price estimate already factors in this multiplier. parameters: - name: start_latitude in: query description: Latitude component of start location. required: true type: number format: double - name: start_longitude in: query description: Longitude component of start location. required: true type: number format: double - name: end_latitude in: query description: Latitude component of end location. required: true type: number format: double - name: end_longitude in: query description: Longitude component of end location. required: true type: number format: double tags: - Estimates responses: "200": description: An array of price estimates by product schema: type: array items: $ref: '#/definitions/PriceEstimate' default: description: Unexpected error schema: $ref: '#/definitions/Error' /estimates/time: get: summary: Time Estimates description: The Time Estimates endpoint returns ETAs for all products offered at a given location, with the responses expressed as integers in seconds. We recommend that this endpoint be called every minute to provide the most accurate, up-to-date ETAs. parameters: - name: start_latitude in: query description: Latitude component of start location. required: true type: number format: double - name: start_longitude in: query description: Longitude component of start location. required: true type: number format: double - name: customer_uuid in: query type: string format: uuid description: Unique customer identifier to be used for experience customization. - name: product_id in: query type: string description: Unique identifier representing a specific product for a given latitude & longitude. tags: - Estimates responses: "200": description: An array of products schema: type: array items: $ref: '#/definitions/Product' default: description: Unexpected error schema: $ref: '#/definitions/Error' /me: get: summary: User Profile description: The User Profile endpoint returns information about the Uber user that has authorized with the application. tags: - User responses: "200": description: Profile information for a user schema: $ref: '#/definitions/Profile' default: description: Unexpected error schema: $ref: '#/definitions/Error' /history: get: summary: User Activity description: The User Activity endpoint returns data about a user's lifetime activity with Uber. The response will include pickup locations and times, dropoff locations and times, the distance of past requests, and information about which products were requested.

The history array in the response will have a maximum length based on the limit parameter. The response value count may exceed limit, therefore subsequent API requests may be necessary. parameters: - name: offset in: query type: integer format: int32 description: Offset the list of returned results by this amount. Default is zero. - name: limit in: query type: integer format: int32 description: Number of items to retrieve. Default is 5, maximum is 100. tags: - User responses: "200": description: History information for the given user schema: $ref: '#/definitions/Activities' default: description: Unexpected error schema: $ref: '#/definitions/Error' definitions: Product: properties: product_id: type: string description: Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles. description: type: string description: Description of product. display_name: type: string description: Display name of product. capacity: type: integer description: Capacity of product. For example, 4 people. image: type: string description: Image URL representing the product. ProductList: properties: products: description: Contains the list of products type: array items: $ref: "#/definitions/Product" PriceEstimate: properties: product_id: type: string description: Unique identifier representing a specific product for a given latitude & longitude. For example, uberX in San Francisco will have a different product_id than uberX in Los Angeles currency_code: type: string description: "[ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code." display_name: type: string description: Display name of product. estimate: type: string description: Formatted string of estimate in local currency of the start location. Estimate could be a range, a single number (flat rate) or "Metered" for TAXI. low_estimate: type: number description: Lower bound of the estimated price. high_estimate: type: number description: Upper bound of the estimated price. surge_multiplier: type: number description: Expected surge multiplier. Surge is active if surge_multiplier is greater than 1. Price estimate already factors in the surge multiplier. Profile: properties: first_name: type: string description: First name of the Uber user. last_name: type: string description: Last name of the Uber user. email: type: string description: Email address of the Uber user picture: type: string description: Image URL of the Uber user. promo_code: type: string description: Promo code of the Uber user. Activity: properties: uuid: type: string description: Unique identifier for the activity Activities: properties: offset: type: integer format: int32 description: Position in pagination. limit: type: integer format: int32 description: Number of items to retrieve (100 max). count: type: integer format: int32 description: Total number of items available. history: type: array items: $ref: '#/definitions/Activity' Error: properties: code: type: integer format: int32 message: type: string fields: type: string openapi-0.8.4/tests/testspecs/v3.0/000077500000000000000000000000001456264404500170355ustar00rootroot00000000000000openapi-0.8.4/tests/testspecs/v3.0/api-with-examples.yaml000066400000000000000000000142741456264404500232670ustar00rootroot00000000000000openapi: "3.0.0" info: title: Simple API overview version: 2.0.0 paths: /: get: operationId: listVersionsv2 summary: List API versions responses: '200': description: |- 200 response content: application/json: examples: foo: value: { "versions": [ { "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", "id": "v2.0", "links": [ { "href": "http://127.0.0.1:8774/v2/", "rel": "self" } ] }, { "status": "EXPERIMENTAL", "updated": "2013-07-23T11:33:21Z", "id": "v3.0", "links": [ { "href": "http://127.0.0.1:8774/v3/", "rel": "self" } ] } ] } '300': description: |- 300 response content: application/json: examples: foo: value: | { "versions": [ { "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", "id": "v2.0", "links": [ { "href": "http://127.0.0.1:8774/v2/", "rel": "self" } ] }, { "status": "EXPERIMENTAL", "updated": "2013-07-23T11:33:21Z", "id": "v3.0", "links": [ { "href": "http://127.0.0.1:8774/v3/", "rel": "self" } ] } ] } /v2: get: operationId: getVersionDetailsv2 summary: Show API version details responses: '200': description: |- 200 response content: application/json: examples: foo: value: { "version": { "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", "media-types": [ { "base": "application/xml", "type": "application/vnd.openstack.compute+xml;version=2" }, { "base": "application/json", "type": "application/vnd.openstack.compute+json;version=2" } ], "id": "v2.0", "links": [ { "href": "http://127.0.0.1:8774/v2/", "rel": "self" }, { "href": "http://docs.openstack.org/api/openstack-compute/2/os-compute-devguide-2.pdf", "type": "application/pdf", "rel": "describedby" }, { "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", "type": "application/vnd.sun.wadl+xml", "rel": "describedby" }, { "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", "type": "application/vnd.sun.wadl+xml", "rel": "describedby" } ] } } '203': description: |- 203 response content: application/json: examples: foo: value: { "version": { "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", "media-types": [ { "base": "application/xml", "type": "application/vnd.openstack.compute+xml;version=2" }, { "base": "application/json", "type": "application/vnd.openstack.compute+json;version=2" } ], "id": "v2.0", "links": [ { "href": "http://23.253.228.211:8774/v2/", "rel": "self" }, { "href": "http://docs.openstack.org/api/openstack-compute/2/os-compute-devguide-2.pdf", "type": "application/pdf", "rel": "describedby" }, { "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", "type": "application/vnd.sun.wadl+xml", "rel": "describedby" } ] } } openapi-0.8.4/tests/testspecs/v3.0/api-with-recursion.yaml000066400000000000000000000023641456264404500234570ustar00rootroot00000000000000openapi: "3.0.0" info: title: Simple API overview version: 2.0.0 paths: /: get: operationId: listVersionsv2 summary: List API versions responses: '200': description: |- 200 response content: application/json: schema: $ref: '#/components/schemas/Object' '300': description: |- 300 response content: application/json: schema: $ref: '#/components/schemas/Object' /v2: get: operationId: getVersionDetailsv2 summary: Show API version details responses: '200': description: |- 200 response content: application/json: schema: $ref: '#/components/schemas/Object' '203': description: |- 203 response content: application/json: schema: $ref: '#/components/schemas/Object' components: schemas: Object: required: - name properties: name: type: string children: type: array items: $ref: '#/components/schemas/Object' openapi-0.8.4/tests/testspecs/v3.0/petstore-expanded.yaml000066400000000000000000000124451456264404500233620ustar00rootroot00000000000000openapi: "3.0.0" info: version: 1.0.0 title: Swagger Petstore description: A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification termsOfService: http://swagger.io/terms/ contact: name: Swagger API Team email: apiteam@swagger.io url: http://swagger.io license: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0.html servers: - url: http://petstore.swagger.io/api paths: /pets: get: description: | Returns all pets from the system that the user has access to Nam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia. Sed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien. operationId: findPets parameters: - name: tags in: query description: tags to filter by required: false style: form schema: type: array items: type: string - name: limit in: query description: maximum number of results to return required: false schema: type: integer format: int32 responses: '200': description: pet response content: application/json: schema: type: array items: $ref: '#/components/schemas/Pet' default: description: unexpected error content: application/json: schema: $ref: '#/components/schemas/Error' post: description: Creates a new pet in the store. Duplicates are allowed operationId: addPet requestBody: description: Pet to add to the store required: true content: application/json: schema: $ref: '#/components/schemas/NewPet' responses: '200': description: pet response content: application/json: schema: $ref: '#/components/schemas/Pet' default: description: unexpected error content: application/json: schema: $ref: '#/components/schemas/Error' /pets/{id}: get: description: Returns a user based on a single ID, if the user does not have access to the pet operationId: find pet by id parameters: - name: id in: path description: ID of pet to fetch required: true schema: type: integer format: int64 responses: '200': description: pet response content: application/json: schema: $ref: '#/components/schemas/Pet' default: description: unexpected error content: application/json: schema: $ref: '#/components/schemas/Error' delete: description: deletes a single pet based on the ID supplied operationId: deletePet parameters: - name: id in: path description: ID of pet to delete required: true schema: type: integer format: int64 responses: '204': description: pet deleted default: description: unexpected error content: application/json: schema: $ref: '#/components/schemas/Error' components: schemas: Pet: allOf: - $ref: '#/components/schemas/NewPet' - required: - id properties: id: type: integer format: int64 NewPet: required: - name properties: name: type: string tag: type: string Error: required: - code - message properties: code: type: integer format: int32 message: type: string openapi-0.8.4/tests/testspecs/v3.0/petstore.yaml000066400000000000000000000047441456264404500215770ustar00rootroot00000000000000openapi: "3.0.0" info: version: 1.0.0 title: Swagger Petstore license: name: MIT servers: - url: http://petstore.swagger.io/v1 paths: /pets: get: summary: List all pets operationId: listPets tags: - pets parameters: - name: limit in: query description: How many items to return at one time (max 100) required: false schema: type: integer format: int32 responses: '200': description: A paged array of pets headers: x-next: description: A link to the next page of responses schema: type: string content: application/json: schema: $ref: "#/components/schemas/Pets" default: description: unexpected error content: application/json: schema: $ref: "#/components/schemas/Error" post: summary: Create a pet operationId: createPets tags: - pets responses: '201': description: Null response default: description: unexpected error content: application/json: schema: $ref: "#/components/schemas/Error" /pets/{petId}: get: summary: Info for a specific pet operationId: showPetById tags: - pets parameters: - name: petId in: path required: true description: The id of the pet to retrieve schema: type: string responses: '200': description: Expected response to a valid request content: application/json: schema: $ref: "#/components/schemas/Pets" default: description: unexpected error content: application/json: schema: $ref: "#/components/schemas/Error" components: schemas: Pet: required: - id - name properties: id: type: integer format: int64 name: type: string tag: type: string Pets: type: array items: $ref: "#/components/schemas/Pet" Error: required: - code - message properties: code: type: integer format: int32 message: type: string openapi-0.8.4/tests/testspecs/v3.0/uspto.yaml000066400000000000000000000171001456264404500210720ustar00rootroot00000000000000openapi: 3.0.1 servers: - url: '{scheme}://developer.uspto.gov/ds-api' variables: scheme: description: 'The Data Set API is accessible via https and http' enum: - 'https' - 'http' default: 'https' info: description: >- The Data Set API (DSAPI) allows the public users to discover and search USPTO exported data sets. This is a generic API that allows USPTO users to make any CSV based data files searchable through API. With the help of GET call, it returns the list of data fields that are searchable. With the help of POST call, data can be fetched based on the filters on the field names. Please note that POST call is used to search the actual data. The reason for the POST call is that it allows users to specify any complex search criteria without worry about the GET size limitations as well as encoding of the input parameters. version: 1.0.0 title: USPTO Data Set API contact: name: Open Data Portal url: 'https://developer.uspto.gov' email: developer@uspto.gov tags: - name: metadata description: Find out about the data sets - name: search description: Search a data set paths: /: get: tags: - metadata operationId: list-data-sets summary: List available data sets responses: '200': description: Returns a list of data sets content: application/json: schema: $ref: '#/components/schemas/dataSetList' example: { "total": 2, "apis": [ { "apiKey": "oa_citations", "apiVersionNumber": "v1", "apiUrl": "https://developer.uspto.gov/ds-api/oa_citations/v1/fields", "apiDocumentationUrl": "https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/oa_citations.json" }, { "apiKey": "cancer_moonshot", "apiVersionNumber": "v1", "apiUrl": "https://developer.uspto.gov/ds-api/cancer_moonshot/v1/fields", "apiDocumentationUrl": "https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/cancer_moonshot.json" } ] } /{dataset}/{version}/fields: get: tags: - metadata summary: >- Provides the general information about the API and the list of fields that can be used to query the dataset. description: >- This GET API returns the list of all the searchable field names that are in the oa_citations. Please see the 'fields' attribute which returns an array of field names. Each field or a combination of fields can be searched using the syntax options shown below. operationId: list-searchable-fields parameters: - name: dataset in: path description: 'Name of the dataset.' required: true example: "oa_citations" schema: type: string - name: version in: path description: Version of the dataset. required: true example: "v1" schema: type: string responses: '200': description: >- The dataset API for the given version is found and it is accessible to consume. content: application/json: schema: type: string '404': description: >- The combination of dataset name and version is not found in the system or it is not published yet to be consumed by public. content: application/json: schema: type: string /{dataset}/{version}/records: post: tags: - search summary: >- Provides search capability for the data set with the given search criteria. description: >- This API is based on Solr/Lucense Search. The data is indexed using SOLR. This GET API returns the list of all the searchable field names that are in the Solr Index. Please see the 'fields' attribute which returns an array of field names. Each field or a combination of fields can be searched using the Solr/Lucene Syntax. Please refer https://lucene.apache.org/core/3_6_2/queryparsersyntax.html#Overview for the query syntax. List of field names that are searchable can be determined using above GET api. operationId: perform-search parameters: - name: version in: path description: Version of the dataset. required: true schema: type: string default: v1 - name: dataset in: path description: 'Name of the dataset. In this case, the default value is oa_citations' required: true schema: type: string default: oa_citations responses: '200': description: successful operation content: application/json: schema: type: array items: type: object additionalProperties: type: object '404': description: No matching record found for the given criteria. requestBody: content: application/x-www-form-urlencoded: schema: type: object properties: criteria: description: >- Uses Lucene Query Syntax in the format of propertyName:value, propertyName:[num1 TO num2] and date range format: propertyName:[yyyyMMdd TO yyyyMMdd]. In the response please see the 'docs' element which has the list of record objects. Each record structure would consist of all the fields and their corresponding values. type: string default: '*:*' start: description: Starting record number. Default value is 0. type: integer default: 0 rows: description: >- Specify number of rows to be returned. If you run the search with default values, in the response you will see 'numFound' attribute which will tell the number of records available in the dataset. type: integer default: 100 required: - criteria components: schemas: dataSetList: type: object properties: total: type: integer apis: type: array items: type: object properties: apiKey: type: string description: To be used as a dataset parameter value apiVersionNumber: type: string description: To be used as a version parameter value apiUrl: type: string format: uriref description: "The URL describing the dataset's fields" apiDocumentationUrl: type: string format: uriref description: A URL to the API console for each API openapi-0.8.4/tests/testspecs/v3.1/000077500000000000000000000000001456264404500170365ustar00rootroot00000000000000openapi-0.8.4/tests/testspecs/v3.1/issue-112.yaml000066400000000000000000000053231456264404500213560ustar00rootroot00000000000000--- openapi: "3.1.0" info: title: "Reproducer for issue #112" version: 2.0.0 paths: /users: get: summary: Get all users. parameters: - in: query name: role required: false schema: # this is one way to represent nullable types in OpenAPI oneOf: - type: "string" enum: ["admin", "member", "reader"] - type: "null" responses: "200": description: A list of all users. content: application/json: schema: type: array items: type: object properties: id: description: The user ID. type: integer username: description: The user name. type: string deleted: description: Whether the user account has been deleted. type: boolean default: false /users/{userID}: get: summary: Get a user by ID. parameters: - in: path name: userID schema: type: "string" responses: "200": description: The expected information about a user. content: application/json: schema: type: object properties: id: description: The user ID. type: integer username: description: The user name. type: string bio: description: A brief bio about the user. # this is another way to represent nullable types in OpenAPI that also demonstrates that assertions are # ignored for different primitive types # https://github.com/OAI/OpenAPI-Specification/issues/3148 type: ["string", "null"] maxLength: 255 deleted: description: Whether the user account has been deleted. type: boolean default: false created_at: description: The date the user account was created. type: string format: date deleted_at: description: The date the user account was deleted. # this is yet another slightly different way anyOf: - type: string format: date - type: null openapi-0.8.4/tox.ini000066400000000000000000000015701456264404500145060ustar00rootroot00000000000000[tox] envlist = py, pre-commit, docs [testenv] deps = flake8 pytest responses commands = {envpython} -m pytest --strict-markers {posargs:tests/} [testenv:pre-commit] skip_install = true deps = pre-commit commands = pre-commit run --all-files --show-diff-on-failure [testenv:docs] deps = sphinx_rtd_theme commands = sphinx-build -b html -d {envtmpdir}/doctrees docs docs/_build/ [pytest] markers = regenerate_rendered_specs [flake8] # The following are ignored since they conflict with black # # E123 closing bracket does not match indentation of opening bracket's line # E241 multiple spaces after ':' # E226 missing whitespace around arithmetic operator # W503 line break before binary operator # # The following are ignored since we disagree with them # # F541 f-string is missing placeholders ignore = E123, E241, E226, W503, F541 max-line-length = 99