├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.rst ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile ├── conf.py ├── index.rst └── quickstart_guide.rst ├── openapi_type ├── __init__.py ├── cli │ ├── __init__.py │ └── check.py ├── custom_types.py ├── info.py └── py.typed ├── pytest.ini ├── requirements ├── extras │ └── third_party.txt ├── minimal.txt └── test.txt ├── setup.cfg ├── setup.py ├── shell.nix └── tests ├── __init__.py ├── custom_examples ├── one.json └── petstore.json ├── paths.py ├── test_spec_parsing.py └── utils.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [avanov] 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | on: 3 | push: 4 | branches: [ master, develop ] 5 | pull_request: 6 | branches: [ master, develop ] 7 | 8 | jobs: 9 | tests: 10 | strategy: 11 | matrix: 12 | python-version: [ 310 ] 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2.3.4 16 | with: 17 | submodules: recursive 18 | - uses: cachix/install-nix-action@v12 19 | with: 20 | install_url: https://releases.nixos.org/nix/nix-2.3.10/install 21 | extra_nix_config: "system-features = benchmark kvm" 22 | - name: Run tests on Python${{ matrix.python-version }} 23 | run: | 24 | nix-shell --argstr pyVersion ${{ matrix.python-version }} --run \ 25 | "pip install -e . && pip install -r requirements/test.txt && pip install -r requirements/extras/third_party.txt && make test" 26 | 27 | - name: Coveralls [upload] 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | COVERALLS_FLAG_NAME: python${{ matrix.python-version }} 31 | COVERALLS_PARALLEL: true 32 | run: | 33 | nix-shell --argstr pyVersion ${{ matrix.python-version }} --arg isDevEnv false --run "coveralls --service=github" 34 | 35 | coveralls: 36 | name: Coveralls [finalize] 37 | needs: tests 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v2.3.4 41 | with: 42 | submodules: recursive 43 | - uses: cachix/install-nix-action@v12 44 | with: 45 | install_url: https://releases.nixos.org/nix/nix-2.3.10/install 46 | extra_nix_config: "system-features = benchmark kvm" 47 | - name: Coveralls [finalize] 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | run: | 51 | nix-shell --arg isDevEnv false --run "coveralls --service=github --finish" 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .[oa] 2 | *~ 3 | .py[co] 4 | __pycache__/ 5 | .doit.db 6 | .idea/ 7 | .cache/ 8 | .pytest_cache/ 9 | .DS_Store 10 | 11 | .venv*/ 12 | .local/ 13 | build/ 14 | dist/ 15 | *.egg-info/ 16 | 17 | docs/_build 18 | 19 | *nose* 20 | *node_modules* 21 | # Ignore all test-related files (see setup.py -> setup(tests_require=[])) 22 | *coverage* 23 | # but keep the .coveragerc (for TravisCI/Coveralls services) 24 | !.coveragerc 25 | 26 | # Ignore vagrant box meta files 27 | .vagrant/ 28 | *-console.log 29 | 30 | # Compass/Sass cache 31 | .sass-cache/ 32 | 33 | # compiled pacific configs 34 | *.pconf 35 | 36 | # do not track contents of private directories 37 | *_priv/ 38 | 39 | # ansible temporary files 40 | deploy/*.retry 41 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "specification"] 2 | path = specification 3 | url = https://github.com/OAI/OpenAPI-Specification.git 4 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | CHANGELOG 3 | ========= 4 | 5 | 0.1.0 6 | ====== 7 | 8 | * ``ContentTypeFormat`` is redefined as a new type of ``str`` (used to be a strict Enum) 9 | for allowing non-common yet valid type representations. 10 | 11 | 0.0.19 12 | ====== 13 | 14 | * Support for ``#/components/responses`` 15 | * Support for ``#/components/headers`` 16 | * Support for ``#/components/parameters`` 17 | * ``ObjectWithAdditionalProperties`` supports boolean value 18 | * Support for header schemas referenced through ``$ref`` 19 | 20 | 0.0.6 21 | ===== 22 | 23 | * Support for Content Media Type with charset. 24 | 25 | 0.0.2 26 | ===== 27 | 28 | * Attributes use pythonic ``snake_case``. 29 | 30 | 0.0.1 31 | ===== 32 | 33 | * Initial Release 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Maxim Avanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # https://docs.python.org/3/distutils/sourcedist.html#manifest 2 | include README.rst CHANGELOG.rst LICENSE.txt 3 | recursive-include requirements *.txt 4 | recursive-include openapi_type 5 | recursive-include openapi_type py.typed 6 | exclude requirements/test.txt 7 | prune tests -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # https://www.gnu.org/software/make/manual/html_node/Special-Variables.html 2 | # https://ftp.gnu.org/old-gnu/Manuals/make-3.80/html_node/make_17.html 3 | PROJECT_MKFILE_PATH := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) 4 | PROJECT_MKFILE_DIR := $(shell cd $(shell dirname $(PROJECT_MKFILE_PATH)); pwd) 5 | 6 | PROJECT_NAME := openapi_type 7 | PROJECT_ROOT := $(PROJECT_MKFILE_DIR) 8 | 9 | BUILD_DIR := $(PROJECT_ROOT)/build 10 | DIST_DIR := $(PROJECT_ROOT)/dist 11 | 12 | PROJECT=openapi_type 13 | 14 | test: typecheck 15 | pytest -s --cov=openapi_type --cov-report xml $(PROJECT_ROOT)/tests 16 | 17 | typecheck: 18 | mypy --config-file setup.cfg --package $(PROJECT_NAME) 19 | 20 | publish: test 21 | rm -rf $(BUILD_DIR) $(DIST_DIR) 22 | python $(PROJECT_ROOT)/setup.py sdist bdist_wheel 23 | twine upload $(DIST_DIR)/* 24 | 25 | shell: 26 | # pyopenssl on m1 issue https://github.com/NixOS/nixpkgs/issues/175875 27 | NIXPKGS_ALLOW_BROKEN=1 nix-shell $(PROJECT_ROOT)/shell.nix 28 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. _badges: 2 | 3 | .. image:: https://github.com/avanov/openapi-type/workflows/CI/badge.svg?branch=develop 4 | :target: https://github.com/avanov/openapi-type/actions?query=branch%3Adevelop 5 | 6 | .. image:: https://coveralls.io/repos/github/avanov/openapi-type/badge.svg?branch=develop 7 | :target: https://coveralls.io/github/avanov/openapi-type?branch=develop 8 | 9 | .. image:: https://requires.io/github/avanov/openapi-type/requirements.svg?branch=master 10 | :target: https://requires.io/github/avanov/openapi-type/requirements/?branch=master 11 | :alt: Requirements Status 12 | 13 | .. image:: https://readthedocs.org/projects/openapi-type/badge/?version=latest 14 | :target: https://openapi-type.readthedocs.io/en/latest/ 15 | :alt: Documentation Status 16 | 17 | .. image:: http://img.shields.io/pypi/v/openapi-type.svg 18 | :target: https://pypi.python.org/pypi/openapi-type 19 | :alt: Latest PyPI Release 20 | 21 | 22 | OpenAPI Type 23 | ============ 24 | 25 | OpenAPI specification represented as a Python type. Use it to parse specifications written in JSON and YAML formats. 26 | 27 | .. code:: bash 28 | 29 | pip install openapi-type 30 | 31 | 32 | .. code:: python 33 | 34 | from openapi_type import OpenAPI, parse_spec, serialize_spec 35 | 36 | 37 | spec: OpenAPI = parse_spec({ 38 | "your OpenAPI Spec as Python dictionary": "will be parsed into a proper Python type" 39 | }) 40 | assert parse_spec(serialize_spec(spec)) == spec 41 | 42 | .. code:: bash 43 | 44 | $ curl -s https://petstore3.swagger.io/api/v3/openapi.json | openapi-type check 45 | Successfully parsed. 46 | 47 | 48 | Codegen 49 | ------- 50 | 51 | If you are looking for a complete client code generator, consider `openapi-client-generator `_ 52 | that uses this library under the hood. 53 | 54 | Cloning this repo 55 | ----------------- 56 | 57 | The proper way to clone this repo is: 58 | 59 | .. code-block:: bash 60 | 61 | git clone --recurse-submodules 62 | cd 63 | 64 | # for showing submodule status with `git status` 65 | git config status.submodulesummary 1 66 | 67 | # for logging submodule diff with `git diff` 68 | git config diff.submodule log 69 | 70 | 71 | Documentation 72 | ------------- 73 | 74 | Documentation is hosted on ReadTheDocs: https://openapi-type.readthedocs.io/en/develop/ 75 | 76 | 77 | Test framework 78 | -------------- 79 | 80 | The project uses `Nix `_ for bootstrapping its dev environment. 81 | 82 | You can run existing test suite with 83 | 84 | .. code:: bash 85 | 86 | nix-shell --run "make test" 87 | 88 | 89 | Changelog 90 | --------- 91 | 92 | See `CHANGELOG `_. 93 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " epub3 to make an epub3" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | @echo " dummy to check syntax errors of document sources" 51 | 52 | .PHONY: clean 53 | clean: 54 | rm -rf $(BUILDDIR)/* 55 | 56 | .PHONY: html 57 | html: 58 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 61 | 62 | .PHONY: dirhtml 63 | dirhtml: 64 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 65 | @echo 66 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 67 | 68 | .PHONY: singlehtml 69 | singlehtml: 70 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 71 | @echo 72 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 73 | 74 | .PHONY: pickle 75 | pickle: 76 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 77 | @echo 78 | @echo "Build finished; now you can process the pickle files." 79 | 80 | .PHONY: json 81 | json: 82 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 83 | @echo 84 | @echo "Build finished; now you can process the JSON files." 85 | 86 | .PHONY: htmlhelp 87 | htmlhelp: 88 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 89 | @echo 90 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 91 | ".hhp project file in $(BUILDDIR)/htmlhelp." 92 | 93 | .PHONY: qthelp 94 | qthelp: 95 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 96 | @echo 97 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 98 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 99 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/openapi-type.qhcp" 100 | @echo "To view the help file:" 101 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/openapi-type.qhc" 102 | 103 | .PHONY: applehelp 104 | applehelp: 105 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 106 | @echo 107 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 108 | @echo "N.B. You won't be able to view it unless you put it in" \ 109 | "~/Library/Documentation/Help or install it in your application" \ 110 | "bundle." 111 | 112 | .PHONY: devhelp 113 | devhelp: 114 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 115 | @echo 116 | @echo "Build finished." 117 | @echo "To view the help file:" 118 | @echo "# mkdir -p $$HOME/.local/share/devhelp/openapi-type" 119 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/openapi-type" 120 | @echo "# devhelp" 121 | 122 | .PHONY: epub 123 | epub: 124 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 125 | @echo 126 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 127 | 128 | .PHONY: epub3 129 | epub3: 130 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 131 | @echo 132 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 133 | 134 | .PHONY: latex 135 | latex: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo 138 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 139 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 140 | "(use \`make latexpdf' here to do that automatically)." 141 | 142 | .PHONY: latexpdf 143 | latexpdf: 144 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 145 | @echo "Running LaTeX files through pdflatex..." 146 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 147 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 148 | 149 | .PHONY: latexpdfja 150 | latexpdfja: 151 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 152 | @echo "Running LaTeX files through platex and dvipdfmx..." 153 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 154 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 155 | 156 | .PHONY: text 157 | text: 158 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 159 | @echo 160 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 161 | 162 | .PHONY: man 163 | man: 164 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 165 | @echo 166 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 167 | 168 | .PHONY: texinfo 169 | texinfo: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo 172 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 173 | @echo "Run \`make' in that directory to run these through makeinfo" \ 174 | "(use \`make info' here to do that automatically)." 175 | 176 | .PHONY: info 177 | info: 178 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 179 | @echo "Running Texinfo files through makeinfo..." 180 | make -C $(BUILDDIR)/texinfo info 181 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 182 | 183 | .PHONY: gettext 184 | gettext: 185 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 186 | @echo 187 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 188 | 189 | .PHONY: changes 190 | changes: 191 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 192 | @echo 193 | @echo "The overview file is in $(BUILDDIR)/changes." 194 | 195 | .PHONY: linkcheck 196 | linkcheck: 197 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 198 | @echo 199 | @echo "Link check complete; look for any errors in the above output " \ 200 | "or in $(BUILDDIR)/linkcheck/output.txt." 201 | 202 | .PHONY: doctest 203 | doctest: 204 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 205 | @echo "Testing of doctests in the sources finished, look at the " \ 206 | "results in $(BUILDDIR)/doctest/output.txt." 207 | 208 | .PHONY: coverage 209 | coverage: 210 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 211 | @echo "Testing of coverage in the sources finished, look at the " \ 212 | "results in $(BUILDDIR)/coverage/python.txt." 213 | 214 | .PHONY: xml 215 | xml: 216 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 217 | @echo 218 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 219 | 220 | .PHONY: pseudoxml 221 | pseudoxml: 222 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 223 | @echo 224 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 225 | 226 | .PHONY: dummy 227 | dummy: 228 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 229 | @echo 230 | @echo "Build finished. Dummy builder generates no files." 231 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | import os 6 | 7 | # If extensions (or modules to document with autodoc) are in another directory, 8 | # add these directories to sys.path here. If the directory is relative to the 9 | # documentation root, use os.path.abspath to make it absolute, like shown here. 10 | #sys.path.insert(0, os.path.abspath('.')) 11 | 12 | # -- General configuration ------------------------------------------------ 13 | 14 | # If your documentation needs a minimal Sphinx version, state it here. 15 | #needs_sphinx = '1.0' 16 | 17 | # Add any Sphinx extension module names here, as strings. They can be 18 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 19 | # ones. 20 | extensions = [ 21 | 'sphinx.ext.autodoc', 22 | 'sphinx.ext.doctest', 23 | 'sphinx.ext.intersphinx', 24 | 'sphinx.ext.todo', 25 | 'sphinx.ext.coverage', 26 | 'sphinx.ext.ifconfig', 27 | 'sphinx.ext.viewcode', 28 | ] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix(es) of source filenames. 34 | # You can specify multiple suffix as a list of string: 35 | # source_suffix = ['.rst', '.md'] 36 | source_suffix = '.rst' 37 | 38 | # The encoding of source files. 39 | #source_encoding = 'utf-8-sig' 40 | 41 | # The master toctree document. 42 | master_doc = 'index' 43 | 44 | # General information about the project. 45 | project = 'openapi-type' 46 | copyright = '2021, Maxim Avanov' 47 | author = 'Maxim Avanov' 48 | 49 | # The version info for the project you're documenting, acts as replacement for 50 | # |version| and |release|, also used in various other places throughout the 51 | # built documents. 52 | # 53 | # The short X.Y version. 54 | version = '0.1' 55 | # The full version, including alpha/beta/rc tags. 56 | release = '0.1.0' 57 | 58 | # The language for content autogenerated by Sphinx. Refer to documentation 59 | # for a list of supported languages. 60 | # 61 | # This is also used if you do content translation via gettext catalogs. 62 | # Usually you set "language" from the command line for these cases. 63 | language = None 64 | 65 | # There are two options for replacing |today|: either, you set today to some 66 | # non-false value, then it is used: 67 | #today = '' 68 | # Else, today_fmt is used as the format for a strftime call. 69 | #today_fmt = '%B %d, %Y' 70 | 71 | # List of patterns, relative to source directory, that match files and 72 | # directories to ignore when looking for source files. 73 | # This patterns also effect to html_static_path and html_extra_path 74 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 75 | 76 | # The reST default role (used for this markup: `text`) to use for all 77 | # documents. 78 | #default_role = None 79 | 80 | # If true, '()' will be appended to :func: etc. cross-reference text. 81 | #add_function_parentheses = True 82 | 83 | # If true, the current module name will be prepended to all description 84 | # unit titles (such as .. function::). 85 | #add_module_names = True 86 | 87 | # If true, sectionauthor and moduleauthor directives will be shown in the 88 | # output. They are ignored by default. 89 | #show_authors = False 90 | 91 | # The name of the Pygments (syntax highlighting) style to use. 92 | pygments_style = 'sphinx' 93 | 94 | # A list of ignored prefixes for module index sorting. 95 | #modindex_common_prefix = [] 96 | 97 | # If true, keep warnings as "system message" paragraphs in the built documents. 98 | #keep_warnings = False 99 | 100 | # If true, `todo` and `todoList` produce output, else they produce nothing. 101 | todo_include_todos = True 102 | 103 | 104 | # -- Options for HTML output ---------------------------------------------- 105 | 106 | # The theme to use for HTML and HTML Help pages. See the documentation for 107 | # a list of builtin themes. 108 | html_theme = 'alabaster' 109 | 110 | # Theme options are theme-specific and customize the look and feel of a theme 111 | # further. For a list of options available for each theme, see the 112 | # documentation. 113 | #html_theme_options = {} 114 | 115 | # Add any paths that contain custom themes here, relative to this directory. 116 | #html_theme_path = [] 117 | 118 | # The name for this set of Sphinx documents. 119 | # " v documentation" by default. 120 | #html_title = 'openapi-type v0.5.0' 121 | 122 | # A shorter title for the navigation bar. Default is the same as html_title. 123 | #html_short_title = None 124 | 125 | # The name of an image file (relative to this directory) to place at the top 126 | # of the sidebar. 127 | #html_logo = None 128 | 129 | # The name of an image file (relative to this directory) to use as a favicon of 130 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 131 | # pixels large. 132 | #html_favicon = None 133 | 134 | # Add any paths that contain custom static files (such as style sheets) here, 135 | # relative to this directory. They are copied after the builtin static files, 136 | # so a file named "default.css" will overwrite the builtin "default.css". 137 | html_static_path = ['_static'] 138 | 139 | # Add any extra paths that contain custom files (such as robots.txt or 140 | # .htaccess) here, relative to this directory. These files are copied 141 | # directly to the root of the documentation. 142 | #html_extra_path = [] 143 | 144 | # If not None, a 'Last updated on:' timestamp is inserted at every page 145 | # bottom, using the given strftime format. 146 | # The empty string is equivalent to '%b %d, %Y'. 147 | #html_last_updated_fmt = None 148 | 149 | # If true, SmartyPants will be used to convert quotes and dashes to 150 | # typographically correct entities. 151 | #html_use_smartypants = True 152 | 153 | # Custom sidebar templates, maps document names to template names. 154 | #html_sidebars = {} 155 | 156 | # Additional templates that should be rendered to pages, maps page names to 157 | # template names. 158 | #html_additional_pages = {} 159 | 160 | # If false, no module index is generated. 161 | #html_domain_indices = True 162 | 163 | # If false, no index is generated. 164 | #html_use_index = True 165 | 166 | # If true, the index is split into individual pages for each letter. 167 | #html_split_index = False 168 | 169 | # If true, links to the reST sources are added to the pages. 170 | #html_show_sourcelink = True 171 | 172 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 173 | #html_show_sphinx = True 174 | 175 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 176 | #html_show_copyright = True 177 | 178 | # If true, an OpenSearch description file will be output, and all pages will 179 | # contain a tag referring to it. The value of this option must be the 180 | # base URL from which the finished HTML is served. 181 | #html_use_opensearch = '' 182 | 183 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 184 | #html_file_suffix = None 185 | 186 | # Language to be used for generating the HTML full-text search index. 187 | # Sphinx supports the following languages: 188 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 189 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' 190 | #html_search_language = 'en' 191 | 192 | # A dictionary with options for the search language support, empty by default. 193 | # 'ja' uses this config value. 194 | # 'zh' user can custom change `jieba` dictionary path. 195 | #html_search_options = {'type': 'default'} 196 | 197 | # The name of a javascript file (relative to the configuration directory) that 198 | # implements a search results scorer. If empty, the default will be used. 199 | #html_search_scorer = 'scorer.js' 200 | 201 | # Output file base name for HTML help builder. 202 | htmlhelp_basename = 'openapi-type-doc' 203 | 204 | # -- Options for LaTeX output --------------------------------------------- 205 | 206 | latex_elements = { 207 | # The paper size ('letterpaper' or 'a4paper'). 208 | #'papersize': 'letterpaper', 209 | 210 | # The font size ('10pt', '11pt' or '12pt'). 211 | #'pointsize': '10pt', 212 | 213 | # Additional stuff for the LaTeX preamble. 214 | #'preamble': '', 215 | 216 | # Latex figure (float) alignment 217 | #'figure_align': 'htbp', 218 | } 219 | 220 | # Grouping the document tree into LaTeX files. List of tuples 221 | # (source start file, target name, title, 222 | # author, documentclass [howto, manual, or own class]). 223 | latex_documents = [ 224 | (master_doc, 'openapi-type.tex', 'openapi-type documentation', 225 | 'Maxim Avanov', 'manual'), 226 | ] 227 | 228 | # The name of an image file (relative to this directory) to place at the top of 229 | # the title page. 230 | #latex_logo = None 231 | 232 | # For "manual" documents, if this is true, then toplevel headings are parts, 233 | # not chapters. 234 | #latex_use_parts = False 235 | 236 | # If true, show page references after internal links. 237 | #latex_show_pagerefs = False 238 | 239 | # If true, show URL addresses after external links. 240 | #latex_show_urls = False 241 | 242 | # Documents to append as an appendix to all manuals. 243 | #latex_appendices = [] 244 | 245 | # If false, no module index is generated. 246 | #latex_domain_indices = True 247 | 248 | 249 | # -- Options for manual page output --------------------------------------- 250 | 251 | # One entry per manual page. List of tuples 252 | # (source start file, name, description, authors, manual section). 253 | man_pages = [ 254 | (master_doc, 'openapi-type', 'openapi-type documentation', 255 | [author], 1) 256 | ] 257 | 258 | # If true, show URL addresses after external links. 259 | #man_show_urls = False 260 | 261 | 262 | # -- Options for Texinfo output ------------------------------------------- 263 | 264 | # Grouping the document tree into Texinfo files. List of tuples 265 | # (source start file, target name, title, author, 266 | # dir menu entry, description, category) 267 | texinfo_documents = [ 268 | (master_doc, 'openapi-type', 'openapi-type documentation', 269 | author, 'openapi-type', 'One line description of project.', 270 | 'Miscellaneous'), 271 | ] 272 | 273 | # Documents to append as an appendix to all manuals. 274 | #texinfo_appendices = [] 275 | 276 | # If false, no module index is generated. 277 | #texinfo_domain_indices = True 278 | 279 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 280 | #texinfo_show_urls = 'footnote' 281 | 282 | # If true, do not generate a @detailmenu in the "Top" node's menu. 283 | #texinfo_no_detailmenu = False 284 | 285 | 286 | # -- Options for Epub output ---------------------------------------------- 287 | 288 | # Bibliographic Dublin Core info. 289 | epub_title = project 290 | epub_author = author 291 | epub_publisher = author 292 | epub_copyright = copyright 293 | 294 | # The basename for the epub file. It defaults to the project name. 295 | #epub_basename = project 296 | 297 | # The HTML theme for the epub output. Since the default themes are not 298 | # optimized for small screen space, using the same theme for HTML and epub 299 | # output is usually not wise. This defaults to 'epub', a theme designed to save 300 | # visual space. 301 | #epub_theme = 'epub' 302 | 303 | # The language of the text. It defaults to the language option 304 | # or 'en' if the language is not set. 305 | #epub_language = '' 306 | 307 | # The scheme of the identifier. Typical schemes are ISBN or URL. 308 | #epub_scheme = '' 309 | 310 | # The unique identifier of the text. This can be a ISBN number 311 | # or the project homepage. 312 | #epub_identifier = '' 313 | 314 | # A unique identification for the text. 315 | #epub_uid = '' 316 | 317 | # A tuple containing the cover image and cover page html template filenames. 318 | #epub_cover = () 319 | 320 | # A sequence of (type, uri, title) tuples for the guide element of content.opf. 321 | #epub_guide = () 322 | 323 | # HTML files that should be inserted before the pages created by sphinx. 324 | # The format is a list of tuples containing the path and title. 325 | #epub_pre_files = [] 326 | 327 | # HTML files that should be inserted after the pages created by sphinx. 328 | # The format is a list of tuples containing the path and title. 329 | #epub_post_files = [] 330 | 331 | # A list of files that should not be packed into the epub file. 332 | epub_exclude_files = ['search.html'] 333 | 334 | # The depth of the table of contents in toc.ncx. 335 | #epub_tocdepth = 3 336 | 337 | # Allow duplicate toc entries. 338 | #epub_tocdup = True 339 | 340 | # Choose between 'default' and 'includehidden'. 341 | #epub_tocscope = 'default' 342 | 343 | # Fix unsupported image types using the Pillow. 344 | #epub_fix_images = False 345 | 346 | # Scale large images. 347 | #epub_max_image_width = 0 348 | 349 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 350 | #epub_show_urls = 'inline' 351 | 352 | # If false, no index is generated. 353 | #epub_use_index = True 354 | 355 | 356 | # Example configuration for intersphinx: refer to the Python standard library. 357 | intersphinx_mapping = {'https://docs.python.org/': None} 358 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. openapi-type documentation master file, created by 2 | sphinx-quickstart. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | OpenAPI Type for Python 7 | ======================= 8 | 9 | **openapi-type** provides the `OpenAPI Specification `_ as a Python type. 10 | 11 | .. code-block:: bash 12 | 13 | pip install openapi-type 14 | 15 | 16 | .. code-block:: python 17 | 18 | from openapi_type import OpenAPI, parse_spec, serialize_spec 19 | 20 | 21 | spec: OpenAPI = parse_spec({ 22 | "your OpenAPI Spec as Python dictionary": "will be parsed into a proper Python type" 23 | }) 24 | assert parse_spec(serialize_spec(spec)) == spec 25 | 26 | 27 | Quickstart Guide 28 | ================ 29 | 30 | .. toctree:: 31 | :maxdepth: 2 32 | 33 | quickstart_guide.rst 34 | 35 | 36 | Indices and tables 37 | ================== 38 | 39 | * :ref:`genindex` 40 | * :ref:`modindex` 41 | * :ref:`search` 42 | 43 | -------------------------------------------------------------------------------- /docs/quickstart_guide.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ------------ 3 | 4 | .. code-block:: bash 5 | 6 | $ pip install openapi-type 7 | 8 | 9 | Using CLI tool 10 | -------------- 11 | 12 | Once installed, ``openapi-type`` provides you with a CLI tool that allows you to generate a 13 | Python client derived from a JSON/YAML specification. 14 | 15 | For example, try the following snippet in your shell: 16 | 17 | .. code-block:: bash 18 | 19 | $ curl -s https:// | openapi-type gen -o 20 | -------------------------------------------------------------------------------- /openapi_type/__init__.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from typing import Literal, NewType 4 | from typing import Mapping 5 | from typing import Any, NamedTuple, Optional, Sequence, FrozenSet, Union 6 | 7 | from pyrsistent import pmap, pvector 8 | from pyrsistent.typing import PVector, PMap 9 | 10 | from .custom_types import * 11 | 12 | 13 | __all__ = ('parse_spec', 'serialize_spec', 'OpenAPI') 14 | 15 | 16 | class IntegerValue(NamedTuple): 17 | type: Literal['integer'] 18 | format: str = '' 19 | example: Optional[int] = None 20 | default: Optional[int] = None 21 | minimum: Optional[int] = None 22 | maximum: Optional[int] = None 23 | 24 | 25 | class FloatValue(NamedTuple): 26 | type: Literal['number'] 27 | format: str = '' 28 | example: Optional[float] = None 29 | default: Optional[float] = None 30 | 31 | 32 | class StringValue(NamedTuple): 33 | type: Literal['string'] 34 | format: str = '' 35 | description: str = '' 36 | enum: PVector[str] = pvector() 37 | default: Optional[str] = None 38 | pattern: Optional[str] = None 39 | example: str = '' 40 | 41 | 42 | class BooleanValue(NamedTuple): 43 | type: Literal['boolean'] 44 | default: Optional[bool] = None 45 | 46 | 47 | class Reference(NamedTuple): 48 | ref: Ref 49 | 50 | 51 | RecursiveAttrs = Mapping[str, 'SchemaType'] # type: ignore 52 | 53 | 54 | class ObjectWithAdditionalProperties(NamedTuple): 55 | """ Represents a free-form object 56 | https://swagger.io/docs/specification/data-models/dictionaries/#free-form 57 | """ 58 | type: Literal['object'] 59 | additional_properties: Union[None, bool, 'SchemaType'] = None # type: ignore 60 | 61 | 62 | class ArrayValue(NamedTuple): 63 | type: Literal['array'] 64 | items: 'SchemaType' # type: ignore 65 | description: str = '' 66 | 67 | 68 | class ObjectValue(NamedTuple): 69 | type: Literal['object'] 70 | properties: RecursiveAttrs 71 | required: FrozenSet[str] = frozenset() 72 | description: str = '' 73 | xml: Mapping[str, Any] = pmap() 74 | 75 | 76 | class InlinedObjectValue(NamedTuple): 77 | properties: RecursiveAttrs 78 | required: FrozenSet[str] 79 | description: str = '' 80 | 81 | 82 | class ResponseRef(NamedTuple): 83 | """ Values that are referenced as $response.body#/some/path 84 | """ 85 | operation_id: str 86 | parameters: Mapping[str, str] 87 | 88 | 89 | class ObjectRef(NamedTuple): 90 | """ Values that are referenced as #/components/schemas/ 91 | """ 92 | ref: str 93 | 94 | 95 | class ProductSchemaType(NamedTuple): 96 | all_of: Sequence['SchemaType'] # type: ignore 97 | 98 | 99 | class UnionSchemaTypeAny(NamedTuple): 100 | any_of: Sequence['SchemaType'] # type: ignore 101 | 102 | 103 | class UnionSchemaTypeOne(NamedTuple): 104 | one_of: Sequence['SchemaType'] # type: ignore 105 | 106 | 107 | SchemaType = Union[ StringValue # type: ignore 108 | , IntegerValue 109 | , FloatValue 110 | , BooleanValue 111 | , ObjectValue 112 | , ArrayValue 113 | , ResponseRef 114 | , Reference 115 | , ProductSchemaType 116 | , UnionSchemaTypeAny 117 | , UnionSchemaTypeOne 118 | , ObjectWithAdditionalProperties 119 | , InlinedObjectValue 120 | , EmptyValue 121 | ] 122 | 123 | 124 | class ParamLocation(Enum): 125 | QUERY = 'query' 126 | HEADER = 'header' 127 | PATH = 'path' 128 | COOKIE = 'cookie' 129 | 130 | 131 | class ParamStyle(Enum): 132 | """ 133 | * https://swagger.io/specification/#style-values 134 | * https://swagger.io/specification/#style-examples 135 | """ 136 | FORM = 'form' 137 | SIMPLE = 'simple' 138 | MATRIX = 'matrix' 139 | LABEL = 'label' 140 | SPACE_DELIMITED = 'spaceDelimited' 141 | PIPE_DELIMITED = 'pipeDelimited' 142 | DEEP_OBJECT = 'deepObject' 143 | 144 | 145 | class OperationParameter(NamedTuple): 146 | name: str 147 | in_: ParamLocation 148 | schema: SchemaType 149 | required: bool = False 150 | description: str = '' 151 | style: Optional[ParamStyle] = None 152 | explode: Optional[bool] = None 153 | 154 | 155 | class Header(NamedTuple): 156 | """ response header 157 | """ 158 | schema: SchemaType 159 | description: str = '' 160 | 161 | 162 | HTTPCode = NewType('HTTPCode', str) 163 | HeaderName = NewType('HeaderName', str) 164 | 165 | 166 | class MediaType(NamedTuple): 167 | """ https://swagger.io/specification/#media-type-object 168 | """ 169 | schema: Optional[SchemaType] = None 170 | example: Union[None, str, PMap[str, Any]] = None 171 | examples: Mapping[str, Any] = pmap() 172 | encoding: Mapping[str, Any] = pmap() 173 | 174 | 175 | class Response(NamedTuple): 176 | """ Response of an endpoint 177 | """ 178 | content: PMap[ContentTypeTag, MediaType] = pmap() 179 | headers: PMap[HeaderName, Union[Header, Reference]] = pmap() 180 | description: str = '' 181 | 182 | 183 | HeaderTypeName = NewType('HeaderTypeName', str) 184 | ParamTypeName = NewType('ParamTypeName', str) 185 | ResponseTypeName = NewType('ResponseTypeName', str) 186 | 187 | 188 | class Components(NamedTuple): 189 | schemas: Mapping[str, SchemaType] 190 | links: Mapping[str, SchemaType] = pmap() 191 | parameters: Mapping[ParamTypeName, OperationParameter] = pmap() 192 | responses: Mapping[ResponseTypeName, Response] = pmap() 193 | headers: Mapping[HeaderTypeName, Header] = pmap() 194 | request_bodies: Mapping[str, Any] = pmap() 195 | security_schemes: Mapping[str, Any] = pmap() 196 | 197 | 198 | class ServerVar(NamedTuple): 199 | default: str 200 | enum: Sequence[str] 201 | description: str = '' 202 | 203 | 204 | class Server(NamedTuple): 205 | url: str 206 | description: str = '' 207 | variables: Mapping[str, ServerVar] = pmap() 208 | 209 | 210 | class InfoLicense(NamedTuple): 211 | name: str 212 | url: str = '' 213 | 214 | 215 | class InfoContact(NamedTuple): 216 | name: Optional[str] 217 | email: Optional[str] 218 | url: Optional[str] 219 | 220 | 221 | class Info(NamedTuple): 222 | version: str 223 | """ API version 224 | """ 225 | title: str 226 | license: Optional[InfoLicense] 227 | contact: Optional[InfoContact] 228 | terms_of_service: str = '' 229 | description: str = '' 230 | 231 | 232 | class SpecFormat(Enum): 233 | V3_0_0 = '3.0.0' 234 | V3_0_1 = '3.0.1' 235 | V3_0_2 = '3.0.2' 236 | 237 | 238 | class ExternalDoc(NamedTuple): 239 | url: str 240 | description: str = '' 241 | 242 | 243 | class RequestBodySchema(NamedTuple): 244 | schema: SchemaType 245 | 246 | 247 | class RequestBody(NamedTuple): 248 | """ https://swagger.io/specification/#request-body-object 249 | """ 250 | content: Mapping[ContentTypeTag, RequestBodySchema] 251 | description: str = '' 252 | required: bool = False 253 | 254 | 255 | class Operation(NamedTuple): 256 | """ https://swagger.io/specification/#operation-object 257 | """ 258 | responses: Mapping[HTTPCode, Union[Reference, Response]] # union order matters 259 | external_docs: Optional[ExternalDoc] 260 | summary: str = '' 261 | operation_id: str = '' 262 | parameters: FrozenSet[Union[OperationParameter, Reference]] = frozenset() 263 | request_body: Union[None, RequestBody, Reference] = None 264 | description: str = '' 265 | tags: FrozenSet[str] = frozenset() 266 | callbacks: Mapping[str, Mapping[str, Any]] = pmap() 267 | security: Optional[Any] = None 268 | 269 | 270 | class PathItem(NamedTuple): 271 | """ Describes endpoint methods 272 | """ 273 | head: Optional[Operation] 274 | get: Optional[Operation] 275 | post: Optional[Operation] 276 | put: Optional[Operation] 277 | patch: Optional[Operation] 278 | delete: Optional[Operation] 279 | trace: Optional[Operation] 280 | servers: Sequence[Server] = pvector() 281 | ref: Optional[Ref] = None 282 | summary: str = '' 283 | description: str = '' 284 | 285 | 286 | SecurityName = NewType('SecurityName', str) 287 | 288 | 289 | class SpecTag(NamedTuple): 290 | name: str 291 | external_docs: Optional[ExternalDoc] 292 | description: str = '' 293 | 294 | 295 | class OpenAPI(NamedTuple): 296 | openapi: SpecFormat 297 | """ Spec format version 298 | """ 299 | info: Info 300 | """ Various metadata 301 | """ 302 | paths: Mapping[str, PathItem] 303 | components: Components = Components(schemas=pmap(), links=pmap()) 304 | servers: Sequence[Server] = pvector() 305 | security: Sequence[Mapping[SecurityName, Sequence[str]]] = pvector() 306 | tags: Sequence[SpecTag] = pvector() 307 | external_docs: Optional[ExternalDoc] = None 308 | 309 | 310 | overrides = { 311 | OperationParameter.in_: 'in', 312 | Reference.ref: '$ref', 313 | PathItem.ref: '$ref', 314 | } 315 | 316 | 317 | parse_spec, serialize_spec = TypeGenerator & overrides ^ OpenAPI 318 | -------------------------------------------------------------------------------- /openapi_type/cli/__init__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | 4 | from pkg_resources import get_distribution 5 | 6 | from . import check 7 | from ..info import DISTRIBUTION_NAME 8 | 9 | 10 | def main(args=None, in_channel=sys.stdin, out_channel=sys.stdout): 11 | parser = argparse.ArgumentParser(description='OpenAPI Type') 12 | parser.add_argument('-V', '--version', action='version', 13 | version=f'{DISTRIBUTION_NAME} {get_distribution(DISTRIBUTION_NAME).version}') 14 | subparsers = parser.add_subparsers(title='sub-commands', 15 | description='valid sub-commands', 16 | help='additional help', 17 | dest='sub-command') 18 | # make subparsers required (see http://stackoverflow.com/a/23354355/458106) 19 | subparsers.required = True 20 | 21 | # $ gen 22 | # --------------------------- 23 | check.setup(subparsers) 24 | 25 | # Parse arguments and config 26 | # -------------------------- 27 | if args is None: 28 | args = sys.argv[1:] 29 | args = parser.parse_args(args) 30 | 31 | # Set up and run 32 | # -------------- 33 | args.run_cmd(args, in_channel=in_channel, out_channel=out_channel) 34 | -------------------------------------------------------------------------------- /openapi_type/cli/check.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import sys 4 | from pathlib import Path 5 | from typing import Mapping 6 | from itertools import islice 7 | 8 | from openapi_type import parse_spec 9 | 10 | 11 | def is_empty_dir(p: Path) -> bool: 12 | return p.is_dir() and not bool(list(islice(p.iterdir(), 1))) 13 | 14 | 15 | def setup(subparsers: argparse._SubParsersAction) -> argparse.ArgumentParser: 16 | sub = subparsers.add_parser('check', help='Check whether a provided schema (JSON, YAML) can be parsed.') 17 | sub.add_argument('-s', '--source', help="Path to a spec (JSON, YAML). " 18 | "If not specified, then the data will be read from stdin.") 19 | sub.set_defaults(run_cmd=main) 20 | return sub 21 | 22 | 23 | def main(args: argparse.Namespace, in_channel=sys.stdin, out_channel=sys.stdout) -> None: 24 | """ $ gen 25 | """ 26 | try: 27 | with Path(args.source).open('r') as f: 28 | python_data = _read_data(f) 29 | except TypeError: 30 | # source is None, read from stdin 31 | python_data = _read_data(in_channel) 32 | 33 | _spec = parse_spec(python_data) 34 | 35 | out_channel.write('Successfully parsed.\n') 36 | 37 | 38 | def _read_data(fd) -> Mapping: 39 | buf = fd.read() # because stdin does not support seek and we want to try both json and yaml parsing 40 | try: 41 | struct = json.loads(buf) 42 | except ValueError: 43 | try: 44 | import yaml 45 | except ImportError: 46 | raise RuntimeError( 47 | "Could not parse data as JSON, and could not locate PyYAML library " 48 | "to try to parse the data as YAML. You can either install PyYAML as a separate " 49 | "dependency, or use the `third_party` extra tag:\n\n" 50 | "$ pip install openapi-client-generator[third_party]" 51 | ) 52 | struct = yaml.full_load(buf) 53 | return struct 54 | -------------------------------------------------------------------------------- /openapi_type/custom_types.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import NewType, NamedTuple, Optional, Mapping, Sequence, Any, List 3 | 4 | import typeit 5 | from inflection import camelize 6 | from typeit.schema import Invalid 7 | 8 | 9 | __all__ = ( 10 | 'TypeGenerator', 11 | 'ContentTypeTag', 12 | 'Ref', 13 | 'EmptyValue', 14 | ) 15 | 16 | 17 | ContentTypeFormat = NewType('ContentTypeFormat', str) 18 | MediaTypeCharset = NewType('MediaTypeCharset', str) 19 | 20 | 21 | class ContentTypeTag(NamedTuple): 22 | format: ContentTypeFormat 23 | charset: Optional[MediaTypeCharset] 24 | 25 | 26 | class ContentTypeTagSchema(typeit.schema.primitives.Str): 27 | def deserialize(self, node, cstruct: str) -> ContentTypeTag: 28 | """ Converts input string value ``cstruct`` to ``ContentTypeTag`` 29 | """ 30 | try: 31 | tag_str = super().deserialize(node, cstruct) 32 | except Invalid as e: 33 | error = Invalid(node, "Media Type should be a string", cstruct) 34 | error.add(e) 35 | raise error 36 | 37 | media_format, *param = tag_str.split(';') 38 | typed_format = ContentTypeFormat(media_format) 39 | 40 | if param: 41 | param = [x for x in param[0].split('charset=') if x.strip()] 42 | if param: 43 | charset = param[0] 44 | else: 45 | charset = None 46 | else: 47 | charset = None 48 | return ContentTypeTag( 49 | format=typed_format, 50 | charset=charset 51 | ) 52 | 53 | def serialize(self, node, appstruct: ContentTypeTag) -> str: 54 | """ Converts ``ContentTypeTag`` back to string value suitable for JSON/YAML 55 | """ 56 | rv: List = [appstruct.format] 57 | if appstruct.charset: 58 | rv.extend([';', "charset=", appstruct.charset]) 59 | 60 | return super().serialize(node, ''.join(rv)) 61 | 62 | 63 | class RefTo(Enum): 64 | SCHEMAS = '#/components/schemas/' 65 | LINKS = '#/components/links/' 66 | PARAMS = '#/components/parameters/' 67 | RESPONSES = '#/components/responses/' 68 | HEADERS = '#/components/headers/' 69 | EXAMPLES = '#/components/examples/' 70 | REQUEST_BODIES = '#/components/requestBodies/' 71 | SECURITY_SCHEMES = '#/components/securitySchemes/' 72 | CALLBACKS = '#/components/callbacks/' 73 | 74 | 75 | class Ref(NamedTuple): 76 | location: RefTo 77 | name: str 78 | 79 | 80 | class RefSchema(typeit.schema.primitives.Str): 81 | REF_PREFIX: Sequence[str] = ['#', 'components'] 82 | REF_LOCATIONS: Mapping[str, RefTo] = { 83 | 'schemas': RefTo.SCHEMAS, 84 | 'links': RefTo.LINKS, 85 | 'parameters': RefTo.PARAMS, 86 | 'responses': RefTo.RESPONSES, 87 | 'headers': RefTo.HEADERS, 88 | 'examples': RefTo.EXAMPLES, 89 | 'requestBodies': RefTo.REQUEST_BODIES, 90 | 'securitySchemes': RefTo.SECURITY_SCHEMES, 91 | 'callbacks': RefTo.CALLBACKS, 92 | } 93 | 94 | def deserialize(self, node, cstruct: str) -> Ref: 95 | """ Converts input string value ``cstruct`` to ``Ref`` 96 | """ 97 | try: 98 | ref_str = super().deserialize(node, cstruct) 99 | except Invalid as e: 100 | error = Invalid(node, "Reference should be a string", cstruct) 101 | error.add(e) 102 | raise error 103 | 104 | try: 105 | *prefix, location, ref_name = ref_str.split('/') 106 | except (ValueError, AttributeError): 107 | raise Invalid(node, "Invalid reference format", ref_str) 108 | 109 | if prefix != self.REF_PREFIX: 110 | raise Invalid(node, f"Reference is not prefixed with {'/'.join(self.REF_PREFIX)}", ref_str) 111 | 112 | try: 113 | ref_location = self.REF_LOCATIONS[location] 114 | except KeyError: 115 | raise Invalid(node, f"Unrecognised reference location '{location}'", ref_str) 116 | 117 | return Ref(ref_location, ref_name) 118 | 119 | def serialize(self, node, appstruct: Ref) -> str: 120 | """ Converts ``Ref`` back to string value suitable for JSON/YAML 121 | """ 122 | rv = [appstruct.location.value, appstruct.name] 123 | return super().serialize(node, ''.join(rv)) 124 | 125 | 126 | class EmptyValue(NamedTuple): 127 | """ Sometimes spec contains schemas like: 128 | { 129 | "type": "array", 130 | "items": {} 131 | } 132 | 133 | In that case we need a strict type that would check that its serialized representation 134 | exactly matches the empty schema value {}. This object serves that purpose. 135 | """ 136 | pass 137 | 138 | 139 | _empty = EmptyValue() 140 | 141 | 142 | class EmptyValueSchema(typeit.schema.meta.SchemaType): 143 | def deserialize(self, node, cstruct: Any) -> EmptyValue: 144 | """ Converts input value ``cstruct`` to ``EmptyValue`` 145 | """ 146 | if cstruct != {}: 147 | error = Invalid(node, "Not an empty type", cstruct) 148 | raise error 149 | return _empty 150 | 151 | def serialize(self, node, appstruct: EmptyValue) -> Mapping[Any, Any]: 152 | """ Converts ``EmptyValue`` back to a value suitable for JSON/YAML 153 | """ 154 | return {} 155 | 156 | 157 | TypeGenerator = (typeit.TypeConstructor 158 | & ContentTypeTagSchema[ContentTypeTag] # type: ignore 159 | & RefSchema[Ref] # type: ignore 160 | & EmptyValueSchema[EmptyValue] # type: ignore 161 | & typeit.flags.GlobalNameOverride(lambda x: camelize(x, uppercase_first_letter=False)) 162 | ) 163 | -------------------------------------------------------------------------------- /openapi_type/info.py: -------------------------------------------------------------------------------- 1 | DISTRIBUTION_NAME = 'openapi-type' 2 | PACKAGE_NAME = 'openapi_type' -------------------------------------------------------------------------------- /openapi_type/py.typed: -------------------------------------------------------------------------------- 1 | # https://www.python.org/dev/peps/pep-0561/#packaging-type-information -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | # Can also be defined in setup.cfg or tox.ini files, but 2 | # searching stops when the first [pytest] section is found in any of these files. 3 | # There is no merging of configuration values from multiple files. 4 | # Read more on https://pytest.org/latest/customize.html 5 | # ------------------------------------------------------------------------------- 6 | [pytest] 7 | # This would tell py.test to not recurse into typical sphinx-build directories or 8 | # into any tmp prefixed directory. 9 | #https://docs.pytest.org/en/latest/reference.html#confval-norecursedirs 10 | norecursedirs = _build build dist tmp* *.egg* frontend* docs* deploy* 11 | 12 | # One or more Glob-style file patterns determining which python files are considered 13 | # as test modules. 14 | python_files = test_*.py *_test.py *_tests.py *_t.py 15 | 16 | # https://docs.pytest.org/en/latest/reference.html#confval-python_classes 17 | python_classes = 18 | Test* 19 | *Tests 20 | 21 | # https://docs.pytest.org/en/latest/reference.html#confval-testpaths 22 | testpaths = 23 | tests 24 | 25 | # use coverage plugin 26 | addopts = -s 27 | -------------------------------------------------------------------------------- /requirements/extras/third_party.txt: -------------------------------------------------------------------------------- 1 | # Third-party requirements that the library uses in CLI 2 | pyyaml -------------------------------------------------------------------------------- /requirements/minimal.txt: -------------------------------------------------------------------------------- 1 | typeit>=3.10 2 | -------------------------------------------------------------------------------- /requirements/test.txt: -------------------------------------------------------------------------------- 1 | -r ./minimal.txt 2 | pytest>=5.4.1,<6.3 3 | coverage>=5.1,<5.6 4 | pytest-cov>=2.8.1,<2.12 5 | mypy==0.961 6 | PyYAML>=5.3.1,<5.5 7 | # typing support 8 | types-PyYAML 9 | types-setuptools 10 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [mypy] 2 | warn_unused_ignores = true 3 | follow_imports = normal 4 | show_error_context = true 5 | warn_incomplete_stub = true 6 | ignore_missing_imports = true 7 | check_untyped_defs = true 8 | cache_dir = /dev/null 9 | warn_redundant_casts = true 10 | warn_unused_configs = true 11 | strict_optional = true 12 | strict_equality = true 13 | 14 | [coverage:report] 15 | # Regexes for lines to exclude from consideration 16 | exclude_lines = 17 | # Have to re-enable the standard pragma 18 | pragma: no cover 19 | 20 | ignore_errors = True 21 | 22 | [coverage:run] 23 | source = 24 | openapi_type 25 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from setuptools import setup 3 | from setuptools import find_packages 4 | 5 | 6 | here = Path(__file__).absolute().parent 7 | 8 | 9 | EXTRAS = frozenset({ 10 | 'third_party', 11 | }) 12 | 13 | 14 | def extras_require(all_extras=EXTRAS): 15 | """ Get map of all extra requirements 16 | """ 17 | return { 18 | x: requirements(here / 'requirements' / 'extras' / f'{x}.txt') for x in all_extras 19 | } 20 | 21 | 22 | def requirements(at_path: Path): 23 | with at_path.open() as f: 24 | rows = f.read().strip().split('\n') 25 | requires = [] 26 | for row in rows: 27 | row = row.strip() 28 | if row and not (row.startswith('#') or row.startswith('http')): 29 | requires.append(row) 30 | return requires 31 | 32 | 33 | with (here / 'README.rst').open() as f: 34 | README = f.read() 35 | 36 | 37 | # Setup 38 | # ---------------------------- 39 | 40 | setup(name='openapi-type', 41 | version='0.2.0', 42 | description='OpenAPI Type', 43 | long_description=README, 44 | classifiers=[ 45 | 'Intended Audience :: Developers', 46 | 'License :: OSI Approved', 47 | 'License :: OSI Approved :: MIT License', 48 | 'Programming Language :: Python', 49 | 'Programming Language :: Python :: 3', 50 | 'Operating System :: POSIX', 51 | ], 52 | author='Maxim Avanov', 53 | author_email='maxim.avanov@gmail.com', 54 | url='https://github.com/avanov/openapi-type', 55 | keywords='typing json yaml openapi oas swagger schema serialization deserialization structured-data', 56 | packages=find_packages(exclude=['tests', 'tests.*']), 57 | include_package_data=True, 58 | zip_safe=False, 59 | test_suite='tests', 60 | tests_require=['pytest', 'coverage'], 61 | install_requires=requirements(here / 'requirements' / 'minimal.txt'), 62 | extras_require=extras_require(), 63 | entry_points={ 64 | 'console_scripts': [ 65 | 'openapi-type = openapi_type.cli:main' 66 | ], 67 | } 68 | ) 69 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? (import (builtins.fetchGit { 3 | url = "https://github.com/avanov/nix-common.git"; 4 | ref = "master"; 5 | rev = "be2dc05bf6beac92fc12da9a2adae6994c9f2ee6"; 6 | }) {}).pkgs 7 | , pyVersion ? "310" 8 | , isDevEnv ? true 9 | }: 10 | 11 | let 12 | 13 | python = pkgs."python${pyVersion}"; 14 | pythonPkgs = pkgs."python${pyVersion}Packages"; 15 | devLibs = if isDevEnv then [ pythonPkgs.twine pythonPkgs.wheel ] else [ pythonPkgs.coveralls ]; 16 | in 17 | 18 | # Make a new "derivation" that represents our shell 19 | pkgs.mkShellNoCC { 20 | name = "openapi-type"; 21 | 22 | # The packages in the `buildInputs` list will be added to the PATH in our shell 23 | # Python-specific guide: 24 | # https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/python.section.md 25 | nativeBuildInputs = with pkgs; [ 26 | # see https://nixos.org/nixos/packages.html 27 | # Python distribution 28 | python 29 | pythonPkgs.virtualenv 30 | ncurses 31 | libxml2 32 | libxslt 33 | libzip 34 | zlib 35 | which 36 | jq 37 | ] ++ devLibs; 38 | shellHook = '' 39 | # set SOURCE_DATE_EPOCH so that we can use python wheels 40 | export SOURCE_DATE_EPOCH=$(date +%s) 41 | 42 | export VENV_DIR="$PWD/.venv${pyVersion}" 43 | 44 | export PATH=$VENV_DIR/bin:$PATH 45 | export PYTHONPATH="" 46 | export LANG=en_US.UTF-8 47 | 48 | # https://python-poetry.org/docs/configuration/ 49 | export PIP_CACHE_DIR="$PWD/.local/pip-cache${pyVersion}" 50 | 51 | # Setup virtualenv 52 | if [ ! -d $VENV_DIR ]; then 53 | virtualenv $VENV_DIR 54 | $VENV_DIR/bin/python -m pip install -e $PWD 55 | $VENV_DIR/bin/python -m pip install -r $PWD/requirements/test.txt 56 | $VENV_DIR/bin/python -m pip install -r $PWD/requirements/extras/* 57 | fi 58 | ''; 59 | } 60 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avanov/openapi-type/e60bf78fa9e9fb2956ca1c6cbcb54192f5f15fe0/tests/__init__.py -------------------------------------------------------------------------------- /tests/custom_examples/one.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "title": "Title", 5 | "description": "Desc", 6 | "contact": { 7 | "name": "Me", 8 | "url": "https://github.com/avanov", 9 | "email": "me@example.com" 10 | }, 11 | "version": "1.0" 12 | }, 13 | "servers": [ 14 | { 15 | "url": "https://api-dev.example.com/v2", 16 | "description": "Development server" 17 | }, 18 | { 19 | "url": "https://api-integration.example.com/v2", 20 | "description": "Integration server" 21 | }, 22 | { 23 | "url": "https://api.example.com/v2", 24 | "description": "Production server" 25 | } 26 | ], 27 | "paths": { 28 | "/auth": { 29 | "post": { 30 | "tags": [ 31 | "Authentication & Authorization" 32 | ], 33 | "summary": "Authentication", 34 | "description": "Authentication", 35 | "operationId": "auth", 36 | "parameters": [ 37 | { 38 | "name": "Accept-Language", 39 | "in": "header", 40 | "description": "List of acceptable human languages for response.", 41 | "required": false, 42 | "style": "simple", 43 | "explode": false, 44 | "schema": { 45 | "type": "string" 46 | } 47 | }, 48 | { 49 | "name": "X-HTTP-Method-Override", 50 | "in": "header", 51 | "description": "Put here the HTTP method you want, when making call by the POST method to avoid interference of nasty firewall rules.", 52 | "required": false, 53 | "style": "simple", 54 | "explode": false, 55 | "schema": { 56 | "type": "string" 57 | } 58 | } 59 | ], 60 | "requestBody": { 61 | "content": { 62 | "application/json;charset=utf-8": { 63 | "schema": { 64 | "type": "object", 65 | "properties": { 66 | "apiKey": { 67 | "type": "string", 68 | "description": "The open api key that is given to the specific system." 69 | }, 70 | "locale": { 71 | "type": "string", 72 | "description": "Locale" 73 | }, 74 | "timezone": { 75 | "type": "string", 76 | "description": "Timezone in POSIX format." 77 | }, 78 | "source": { 79 | "allOf": [ 80 | { 81 | "type": "object", 82 | "properties": { 83 | "tenant": { 84 | "type": "string", 85 | "description": "The tenant to which the origin belongs." 86 | } 87 | } 88 | }, 89 | { 90 | "title": "Source", 91 | "required": [ 92 | "type", 93 | "name", 94 | "version", 95 | "instance" 96 | ], 97 | "type": "object", 98 | "properties": { 99 | "type": { 100 | "type": "string", 101 | "enum": [ 102 | "system", 103 | "database", 104 | "file-system" 105 | ], 106 | "description": "The type of the source." 107 | }, 108 | "name": { 109 | "type": "string", 110 | "description": "The name of the source." 111 | }, 112 | "version": { 113 | "type": "string", 114 | "description": "The version of the source." 115 | }, 116 | "instance": { 117 | "type": "string", 118 | "description": "Specific instance of the source." 119 | } 120 | }, 121 | "x-tags": [ 122 | "domain" 123 | ] 124 | } 125 | ] 126 | } 127 | } 128 | } 129 | } 130 | } 131 | }, 132 | "responses": { 133 | "200": { 134 | "description": "OK", 135 | "content": { 136 | "application/json": { 137 | "schema": { 138 | "allOf": [ 139 | { 140 | "title": "Response", 141 | "required": [ 142 | "code", 143 | "locale" 144 | ], 145 | "type": "object", 146 | "properties": { 147 | "code": { 148 | "minimum": 0, 149 | "type": "integer", 150 | "description": "Response code" 151 | }, 152 | "locale": { 153 | "type": "string", 154 | "description": "Response locale" 155 | } 156 | }, 157 | "x-tags": [ 158 | "system" 159 | ] 160 | } 161 | ] 162 | } 163 | } 164 | } 165 | } 166 | } 167 | } 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /tests/custom_examples/petstore.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.2", 3 | "info": { 4 | "title": "Swagger Petstore - OpenAPI 3.0", 5 | "description": "This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\nSwagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\n\nSome useful links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)", 6 | "termsOfService": "http://swagger.io/terms/", 7 | "contact": { 8 | "email": "apiteam@swagger.io" 9 | }, 10 | "license": { 11 | "name": "Apache 2.0", 12 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 13 | }, 14 | "version": "1.0.5" 15 | }, 16 | "externalDocs": { 17 | "description": "Find out more about Swagger", 18 | "url": "http://swagger.io" 19 | }, 20 | "servers": [ 21 | { 22 | "url": "/api/v3" 23 | } 24 | ], 25 | "tags": [ 26 | { 27 | "name": "pet", 28 | "description": "Everything about your Pets", 29 | "externalDocs": { 30 | "description": "Find out more", 31 | "url": "http://swagger.io" 32 | } 33 | }, 34 | { 35 | "name": "store", 36 | "description": "Operations about user" 37 | }, 38 | { 39 | "name": "user", 40 | "description": "Access to Petstore orders", 41 | "externalDocs": { 42 | "description": "Find out more about our store", 43 | "url": "http://swagger.io" 44 | } 45 | } 46 | ], 47 | "paths": { 48 | "/pet": { 49 | "put": { 50 | "tags": [ 51 | "pet" 52 | ], 53 | "summary": "Update an existing pet", 54 | "description": "Update an existing pet by Id", 55 | "operationId": "updatePet", 56 | "requestBody": { 57 | "description": "Update an existent pet in the store", 58 | "content": { 59 | "application/json": { 60 | "schema": { 61 | "$ref": "#/components/schemas/Pet" 62 | } 63 | }, 64 | "application/xml": { 65 | "schema": { 66 | "$ref": "#/components/schemas/Pet" 67 | } 68 | }, 69 | "application/x-www-form-urlencoded": { 70 | "schema": { 71 | "$ref": "#/components/schemas/Pet" 72 | } 73 | } 74 | }, 75 | "required": true 76 | }, 77 | "responses": { 78 | "200": { 79 | "description": "Successful operation", 80 | "content": { 81 | "application/xml": { 82 | "schema": { 83 | "$ref": "#/components/schemas/Pet" 84 | } 85 | }, 86 | "application/json": { 87 | "schema": { 88 | "$ref": "#/components/schemas/Pet" 89 | } 90 | } 91 | } 92 | }, 93 | "400": { 94 | "description": "Invalid ID supplied" 95 | }, 96 | "404": { 97 | "description": "Pet not found" 98 | }, 99 | "405": { 100 | "description": "Validation exception" 101 | } 102 | }, 103 | "security": [ 104 | { 105 | "petstore_auth": [ 106 | "write:pets", 107 | "read:pets" 108 | ] 109 | } 110 | ] 111 | }, 112 | "post": { 113 | "tags": [ 114 | "pet" 115 | ], 116 | "summary": "Add a new pet to the store", 117 | "description": "Add a new pet to the store", 118 | "operationId": "addPet", 119 | "requestBody": { 120 | "description": "Create a new pet in the store", 121 | "content": { 122 | "application/json": { 123 | "schema": { 124 | "$ref": "#/components/schemas/Pet" 125 | } 126 | }, 127 | "application/xml": { 128 | "schema": { 129 | "$ref": "#/components/schemas/Pet" 130 | } 131 | }, 132 | "application/x-www-form-urlencoded": { 133 | "schema": { 134 | "$ref": "#/components/schemas/Pet" 135 | } 136 | } 137 | }, 138 | "required": true 139 | }, 140 | "responses": { 141 | "200": { 142 | "description": "Successful operation", 143 | "content": { 144 | "application/xml": { 145 | "schema": { 146 | "$ref": "#/components/schemas/Pet" 147 | } 148 | }, 149 | "application/json": { 150 | "schema": { 151 | "$ref": "#/components/schemas/Pet" 152 | } 153 | } 154 | } 155 | }, 156 | "405": { 157 | "description": "Invalid input" 158 | } 159 | }, 160 | "security": [ 161 | { 162 | "petstore_auth": [ 163 | "write:pets", 164 | "read:pets" 165 | ] 166 | } 167 | ] 168 | } 169 | }, 170 | "/pet/findByStatus": { 171 | "get": { 172 | "tags": [ 173 | "pet" 174 | ], 175 | "summary": "Finds Pets by status", 176 | "description": "Multiple status values can be provided with comma separated strings", 177 | "operationId": "findPetsByStatus", 178 | "parameters": [ 179 | { 180 | "name": "status", 181 | "in": "query", 182 | "description": "Status values that need to be considered for filter", 183 | "required": false, 184 | "explode": true, 185 | "schema": { 186 | "type": "string", 187 | "default": "available", 188 | "enum": [ 189 | "available", 190 | "pending", 191 | "sold" 192 | ] 193 | } 194 | } 195 | ], 196 | "responses": { 197 | "200": { 198 | "description": "successful operation", 199 | "content": { 200 | "application/xml": { 201 | "schema": { 202 | "type": "array", 203 | "items": { 204 | "$ref": "#/components/schemas/Pet" 205 | } 206 | } 207 | }, 208 | "application/json": { 209 | "schema": { 210 | "type": "array", 211 | "items": { 212 | "$ref": "#/components/schemas/Pet" 213 | } 214 | } 215 | } 216 | } 217 | }, 218 | "400": { 219 | "description": "Invalid status value" 220 | } 221 | }, 222 | "security": [ 223 | { 224 | "petstore_auth": [ 225 | "write:pets", 226 | "read:pets" 227 | ] 228 | } 229 | ] 230 | } 231 | }, 232 | "/pet/findByTags": { 233 | "get": { 234 | "tags": [ 235 | "pet" 236 | ], 237 | "summary": "Finds Pets by tags", 238 | "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", 239 | "operationId": "findPetsByTags", 240 | "parameters": [ 241 | { 242 | "name": "tags", 243 | "in": "query", 244 | "description": "Tags to filter by", 245 | "required": false, 246 | "explode": true, 247 | "schema": { 248 | "type": "array", 249 | "items": { 250 | "type": "string" 251 | } 252 | } 253 | } 254 | ], 255 | "responses": { 256 | "200": { 257 | "description": "successful operation", 258 | "content": { 259 | "application/xml": { 260 | "schema": { 261 | "type": "array", 262 | "items": { 263 | "$ref": "#/components/schemas/Pet" 264 | } 265 | } 266 | }, 267 | "application/json": { 268 | "schema": { 269 | "type": "array", 270 | "items": { 271 | "$ref": "#/components/schemas/Pet" 272 | } 273 | } 274 | } 275 | } 276 | }, 277 | "400": { 278 | "description": "Invalid tag value" 279 | } 280 | }, 281 | "security": [ 282 | { 283 | "petstore_auth": [ 284 | "write:pets", 285 | "read:pets" 286 | ] 287 | } 288 | ] 289 | } 290 | }, 291 | "/pet/{petId}": { 292 | "get": { 293 | "tags": [ 294 | "pet" 295 | ], 296 | "summary": "Find pet by ID", 297 | "description": "Returns a single pet", 298 | "operationId": "getPetById", 299 | "parameters": [ 300 | { 301 | "name": "petId", 302 | "in": "path", 303 | "description": "ID of pet to return", 304 | "required": true, 305 | "schema": { 306 | "type": "integer", 307 | "format": "int64" 308 | } 309 | } 310 | ], 311 | "responses": { 312 | "200": { 313 | "description": "successful operation", 314 | "content": { 315 | "application/xml": { 316 | "schema": { 317 | "$ref": "#/components/schemas/Pet" 318 | } 319 | }, 320 | "application/json": { 321 | "schema": { 322 | "$ref": "#/components/schemas/Pet" 323 | } 324 | } 325 | } 326 | }, 327 | "400": { 328 | "description": "Invalid ID supplied" 329 | }, 330 | "404": { 331 | "description": "Pet not found" 332 | } 333 | }, 334 | "security": [ 335 | { 336 | "api_key": [] 337 | }, 338 | { 339 | "petstore_auth": [ 340 | "write:pets", 341 | "read:pets" 342 | ] 343 | } 344 | ] 345 | }, 346 | "post": { 347 | "tags": [ 348 | "pet" 349 | ], 350 | "summary": "Updates a pet in the store with form data", 351 | "description": "", 352 | "operationId": "updatePetWithForm", 353 | "parameters": [ 354 | { 355 | "name": "petId", 356 | "in": "path", 357 | "description": "ID of pet that needs to be updated", 358 | "required": true, 359 | "schema": { 360 | "type": "integer", 361 | "format": "int64" 362 | } 363 | }, 364 | { 365 | "name": "name", 366 | "in": "query", 367 | "description": "Name of pet that needs to be updated", 368 | "schema": { 369 | "type": "string" 370 | } 371 | }, 372 | { 373 | "name": "status", 374 | "in": "query", 375 | "description": "Status of pet that needs to be updated", 376 | "schema": { 377 | "type": "string" 378 | } 379 | } 380 | ], 381 | "responses": { 382 | "405": { 383 | "description": "Invalid input" 384 | } 385 | }, 386 | "security": [ 387 | { 388 | "petstore_auth": [ 389 | "write:pets", 390 | "read:pets" 391 | ] 392 | } 393 | ] 394 | }, 395 | "delete": { 396 | "tags": [ 397 | "pet" 398 | ], 399 | "summary": "Deletes a pet", 400 | "description": "", 401 | "operationId": "deletePet", 402 | "parameters": [ 403 | { 404 | "name": "api_key", 405 | "in": "header", 406 | "description": "", 407 | "required": false, 408 | "schema": { 409 | "type": "string" 410 | } 411 | }, 412 | { 413 | "name": "petId", 414 | "in": "path", 415 | "description": "Pet id to delete", 416 | "required": true, 417 | "schema": { 418 | "type": "integer", 419 | "format": "int64" 420 | } 421 | } 422 | ], 423 | "responses": { 424 | "400": { 425 | "description": "Invalid pet value" 426 | } 427 | }, 428 | "security": [ 429 | { 430 | "petstore_auth": [ 431 | "write:pets", 432 | "read:pets" 433 | ] 434 | } 435 | ] 436 | } 437 | }, 438 | "/pet/{petId}/uploadImage": { 439 | "post": { 440 | "tags": [ 441 | "pet" 442 | ], 443 | "summary": "uploads an image", 444 | "description": "", 445 | "operationId": "uploadFile", 446 | "parameters": [ 447 | { 448 | "name": "petId", 449 | "in": "path", 450 | "description": "ID of pet to update", 451 | "required": true, 452 | "schema": { 453 | "type": "integer", 454 | "format": "int64" 455 | } 456 | }, 457 | { 458 | "name": "additionalMetadata", 459 | "in": "query", 460 | "description": "Additional Metadata", 461 | "required": false, 462 | "schema": { 463 | "type": "string" 464 | } 465 | } 466 | ], 467 | "requestBody": { 468 | "content": { 469 | "application/octet-stream": { 470 | "schema": { 471 | "type": "string", 472 | "format": "binary" 473 | } 474 | } 475 | } 476 | }, 477 | "responses": { 478 | "200": { 479 | "description": "successful operation", 480 | "content": { 481 | "application/json": { 482 | "schema": { 483 | "$ref": "#/components/schemas/ApiResponse" 484 | } 485 | } 486 | } 487 | } 488 | }, 489 | "security": [ 490 | { 491 | "petstore_auth": [ 492 | "write:pets", 493 | "read:pets" 494 | ] 495 | } 496 | ] 497 | } 498 | }, 499 | "/store/inventory": { 500 | "get": { 501 | "tags": [ 502 | "store" 503 | ], 504 | "summary": "Returns pet inventories by status", 505 | "description": "Returns a map of status codes to quantities", 506 | "operationId": "getInventory", 507 | "responses": { 508 | "200": { 509 | "description": "successful operation", 510 | "content": { 511 | "application/json": { 512 | "schema": { 513 | "type": "object", 514 | "additionalProperties": { 515 | "type": "integer", 516 | "format": "int32" 517 | } 518 | } 519 | } 520 | } 521 | } 522 | }, 523 | "security": [ 524 | { 525 | "api_key": [] 526 | } 527 | ] 528 | } 529 | }, 530 | "/store/order": { 531 | "post": { 532 | "tags": [ 533 | "store" 534 | ], 535 | "summary": "Place an order for a pet", 536 | "description": "Place a new order in the store", 537 | "operationId": "placeOrder", 538 | "requestBody": { 539 | "content": { 540 | "application/json": { 541 | "schema": { 542 | "$ref": "#/components/schemas/Order" 543 | } 544 | }, 545 | "application/xml": { 546 | "schema": { 547 | "$ref": "#/components/schemas/Order" 548 | } 549 | }, 550 | "application/x-www-form-urlencoded": { 551 | "schema": { 552 | "$ref": "#/components/schemas/Order" 553 | } 554 | } 555 | } 556 | }, 557 | "responses": { 558 | "200": { 559 | "description": "successful operation", 560 | "content": { 561 | "application/json": { 562 | "schema": { 563 | "$ref": "#/components/schemas/Order" 564 | } 565 | } 566 | } 567 | }, 568 | "405": { 569 | "description": "Invalid input" 570 | } 571 | } 572 | } 573 | }, 574 | "/store/order/{orderId}": { 575 | "get": { 576 | "tags": [ 577 | "store" 578 | ], 579 | "summary": "Find purchase order by ID", 580 | "description": "For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions", 581 | "operationId": "getOrderById", 582 | "parameters": [ 583 | { 584 | "name": "orderId", 585 | "in": "path", 586 | "description": "ID of order that needs to be fetched", 587 | "required": true, 588 | "schema": { 589 | "type": "integer", 590 | "format": "int64" 591 | } 592 | } 593 | ], 594 | "responses": { 595 | "200": { 596 | "description": "successful operation", 597 | "content": { 598 | "application/xml": { 599 | "schema": { 600 | "$ref": "#/components/schemas/Order" 601 | } 602 | }, 603 | "application/json": { 604 | "schema": { 605 | "$ref": "#/components/schemas/Order" 606 | } 607 | } 608 | } 609 | }, 610 | "400": { 611 | "description": "Invalid ID supplied" 612 | }, 613 | "404": { 614 | "description": "Order not found" 615 | } 616 | } 617 | }, 618 | "delete": { 619 | "tags": [ 620 | "store" 621 | ], 622 | "summary": "Delete purchase order by ID", 623 | "description": "For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors", 624 | "operationId": "deleteOrder", 625 | "parameters": [ 626 | { 627 | "name": "orderId", 628 | "in": "path", 629 | "description": "ID of the order that needs to be deleted", 630 | "required": true, 631 | "schema": { 632 | "type": "integer", 633 | "format": "int64" 634 | } 635 | } 636 | ], 637 | "responses": { 638 | "400": { 639 | "description": "Invalid ID supplied" 640 | }, 641 | "404": { 642 | "description": "Order not found" 643 | } 644 | } 645 | } 646 | }, 647 | "/user": { 648 | "post": { 649 | "tags": [ 650 | "user" 651 | ], 652 | "summary": "Create user", 653 | "description": "This can only be done by the logged in user.", 654 | "operationId": "createUser", 655 | "requestBody": { 656 | "description": "Created user object", 657 | "content": { 658 | "application/json": { 659 | "schema": { 660 | "$ref": "#/components/schemas/User" 661 | } 662 | }, 663 | "application/xml": { 664 | "schema": { 665 | "$ref": "#/components/schemas/User" 666 | } 667 | }, 668 | "application/x-www-form-urlencoded": { 669 | "schema": { 670 | "$ref": "#/components/schemas/User" 671 | } 672 | } 673 | } 674 | }, 675 | "responses": { 676 | "default": { 677 | "description": "successful operation", 678 | "content": { 679 | "application/json": { 680 | "schema": { 681 | "$ref": "#/components/schemas/User" 682 | } 683 | }, 684 | "application/xml": { 685 | "schema": { 686 | "$ref": "#/components/schemas/User" 687 | } 688 | } 689 | } 690 | } 691 | } 692 | } 693 | }, 694 | "/user/createWithList": { 695 | "post": { 696 | "tags": [ 697 | "user" 698 | ], 699 | "summary": "Creates list of users with given input array", 700 | "description": "Creates list of users with given input array", 701 | "operationId": "createUsersWithListInput", 702 | "requestBody": { 703 | "content": { 704 | "application/json": { 705 | "schema": { 706 | "type": "array", 707 | "items": { 708 | "$ref": "#/components/schemas/User" 709 | } 710 | } 711 | } 712 | } 713 | }, 714 | "responses": { 715 | "200": { 716 | "description": "Successful operation", 717 | "content": { 718 | "application/xml": { 719 | "schema": { 720 | "$ref": "#/components/schemas/User" 721 | } 722 | }, 723 | "application/json": { 724 | "schema": { 725 | "$ref": "#/components/schemas/User" 726 | } 727 | } 728 | } 729 | }, 730 | "default": { 731 | "description": "successful operation" 732 | } 733 | } 734 | } 735 | }, 736 | "/user/login": { 737 | "get": { 738 | "tags": [ 739 | "user" 740 | ], 741 | "summary": "Logs user into the system", 742 | "description": "", 743 | "operationId": "loginUser", 744 | "parameters": [ 745 | { 746 | "name": "username", 747 | "in": "query", 748 | "description": "The user name for login", 749 | "required": false, 750 | "schema": { 751 | "type": "string" 752 | } 753 | }, 754 | { 755 | "name": "password", 756 | "in": "query", 757 | "description": "The password for login in clear text", 758 | "required": false, 759 | "schema": { 760 | "type": "string" 761 | } 762 | } 763 | ], 764 | "responses": { 765 | "200": { 766 | "description": "successful operation", 767 | "headers": { 768 | "X-Rate-Limit": { 769 | "description": "calls per hour allowed by the user", 770 | "schema": { 771 | "type": "integer", 772 | "format": "int32" 773 | } 774 | }, 775 | "X-Expires-After": { 776 | "description": "date in UTC when toekn expires", 777 | "schema": { 778 | "type": "string", 779 | "format": "date-time" 780 | } 781 | } 782 | }, 783 | "content": { 784 | "application/xml": { 785 | "schema": { 786 | "type": "string" 787 | } 788 | }, 789 | "application/json": { 790 | "schema": { 791 | "type": "string" 792 | } 793 | } 794 | } 795 | }, 796 | "400": { 797 | "description": "Invalid username/password supplied" 798 | } 799 | } 800 | } 801 | }, 802 | "/user/logout": { 803 | "get": { 804 | "tags": [ 805 | "user" 806 | ], 807 | "summary": "Logs out current logged in user session", 808 | "description": "", 809 | "operationId": "logoutUser", 810 | "parameters": [], 811 | "responses": { 812 | "default": { 813 | "description": "successful operation" 814 | } 815 | } 816 | } 817 | }, 818 | "/user/{username}": { 819 | "get": { 820 | "tags": [ 821 | "user" 822 | ], 823 | "summary": "Get user by user name", 824 | "description": "", 825 | "operationId": "getUserByName", 826 | "parameters": [ 827 | { 828 | "name": "username", 829 | "in": "path", 830 | "description": "The name that needs to be fetched. Use user1 for testing. ", 831 | "required": true, 832 | "schema": { 833 | "type": "string" 834 | } 835 | } 836 | ], 837 | "responses": { 838 | "200": { 839 | "description": "successful operation", 840 | "content": { 841 | "application/xml": { 842 | "schema": { 843 | "$ref": "#/components/schemas/User" 844 | } 845 | }, 846 | "application/json": { 847 | "schema": { 848 | "$ref": "#/components/schemas/User" 849 | } 850 | } 851 | } 852 | }, 853 | "400": { 854 | "description": "Invalid username supplied" 855 | }, 856 | "404": { 857 | "description": "User not found" 858 | } 859 | } 860 | }, 861 | "put": { 862 | "tags": [ 863 | "user" 864 | ], 865 | "summary": "Update user", 866 | "description": "This can only be done by the logged in user.", 867 | "operationId": "updateUser", 868 | "parameters": [ 869 | { 870 | "name": "username", 871 | "in": "path", 872 | "description": "name that need to be deleted", 873 | "required": true, 874 | "schema": { 875 | "type": "string" 876 | } 877 | } 878 | ], 879 | "requestBody": { 880 | "description": "Update an existent user in the store", 881 | "content": { 882 | "application/json": { 883 | "schema": { 884 | "$ref": "#/components/schemas/User" 885 | } 886 | }, 887 | "application/xml": { 888 | "schema": { 889 | "$ref": "#/components/schemas/User" 890 | } 891 | }, 892 | "application/x-www-form-urlencoded": { 893 | "schema": { 894 | "$ref": "#/components/schemas/User" 895 | } 896 | } 897 | } 898 | }, 899 | "responses": { 900 | "default": { 901 | "description": "successful operation" 902 | } 903 | } 904 | }, 905 | "delete": { 906 | "tags": [ 907 | "user" 908 | ], 909 | "summary": "Delete user", 910 | "description": "This can only be done by the logged in user.", 911 | "operationId": "deleteUser", 912 | "parameters": [ 913 | { 914 | "name": "username", 915 | "in": "path", 916 | "description": "The name that needs to be deleted", 917 | "required": true, 918 | "schema": { 919 | "type": "string" 920 | } 921 | } 922 | ], 923 | "responses": { 924 | "400": { 925 | "description": "Invalid username supplied" 926 | }, 927 | "404": { 928 | "description": "User not found" 929 | } 930 | } 931 | } 932 | } 933 | }, 934 | "components": { 935 | "schemas": { 936 | "Order": { 937 | "type": "object", 938 | "properties": { 939 | "id": { 940 | "type": "integer", 941 | "format": "int64", 942 | "example": 10 943 | }, 944 | "petId": { 945 | "type": "integer", 946 | "format": "int64", 947 | "example": 198772 948 | }, 949 | "quantity": { 950 | "type": "integer", 951 | "format": "int32", 952 | "example": 7 953 | }, 954 | "shipDate": { 955 | "type": "string", 956 | "format": "date-time" 957 | }, 958 | "status": { 959 | "type": "string", 960 | "description": "Order Status", 961 | "example": "approved", 962 | "enum": [ 963 | "placed", 964 | "approved", 965 | "delivered" 966 | ] 967 | }, 968 | "complete": { 969 | "type": "boolean" 970 | } 971 | }, 972 | "xml": { 973 | "name": "order" 974 | } 975 | }, 976 | "Customer": { 977 | "type": "object", 978 | "properties": { 979 | "id": { 980 | "type": "integer", 981 | "format": "int64", 982 | "example": 100000 983 | }, 984 | "username": { 985 | "type": "string", 986 | "example": "fehguy" 987 | }, 988 | "address": { 989 | "type": "array", 990 | "xml": { 991 | "name": "addresses", 992 | "wrapped": true 993 | }, 994 | "items": { 995 | "$ref": "#/components/schemas/Address" 996 | } 997 | } 998 | }, 999 | "xml": { 1000 | "name": "customer" 1001 | } 1002 | }, 1003 | "Address": { 1004 | "type": "object", 1005 | "properties": { 1006 | "street": { 1007 | "type": "string", 1008 | "example": "437 Lytton" 1009 | }, 1010 | "city": { 1011 | "type": "string", 1012 | "example": "Palo Alto" 1013 | }, 1014 | "state": { 1015 | "type": "string", 1016 | "example": "CA" 1017 | }, 1018 | "zip": { 1019 | "type": "string", 1020 | "example": "94301" 1021 | } 1022 | }, 1023 | "xml": { 1024 | "name": "address" 1025 | } 1026 | }, 1027 | "Category": { 1028 | "type": "object", 1029 | "properties": { 1030 | "id": { 1031 | "type": "integer", 1032 | "format": "int64", 1033 | "example": 1 1034 | }, 1035 | "name": { 1036 | "type": "string", 1037 | "example": "Dogs" 1038 | } 1039 | }, 1040 | "xml": { 1041 | "name": "category" 1042 | } 1043 | }, 1044 | "User": { 1045 | "type": "object", 1046 | "properties": { 1047 | "id": { 1048 | "type": "integer", 1049 | "format": "int64", 1050 | "example": 10 1051 | }, 1052 | "username": { 1053 | "type": "string", 1054 | "example": "theUser" 1055 | }, 1056 | "firstName": { 1057 | "type": "string", 1058 | "example": "John" 1059 | }, 1060 | "lastName": { 1061 | "type": "string", 1062 | "example": "James" 1063 | }, 1064 | "email": { 1065 | "type": "string", 1066 | "example": "john@email.com" 1067 | }, 1068 | "password": { 1069 | "type": "string", 1070 | "example": "12345" 1071 | }, 1072 | "phone": { 1073 | "type": "string", 1074 | "example": "12345" 1075 | }, 1076 | "userStatus": { 1077 | "type": "integer", 1078 | "description": "User Status", 1079 | "format": "int32", 1080 | "example": 1 1081 | } 1082 | }, 1083 | "xml": { 1084 | "name": "user" 1085 | } 1086 | }, 1087 | "Tag": { 1088 | "type": "object", 1089 | "properties": { 1090 | "id": { 1091 | "type": "integer", 1092 | "format": "int64" 1093 | }, 1094 | "name": { 1095 | "type": "string" 1096 | } 1097 | }, 1098 | "xml": { 1099 | "name": "tag" 1100 | } 1101 | }, 1102 | "Pet": { 1103 | "required": [ 1104 | "name", 1105 | "photoUrls" 1106 | ], 1107 | "type": "object", 1108 | "properties": { 1109 | "id": { 1110 | "type": "integer", 1111 | "format": "int64", 1112 | "example": 10 1113 | }, 1114 | "name": { 1115 | "type": "string", 1116 | "example": "doggie" 1117 | }, 1118 | "category": { 1119 | "$ref": "#/components/schemas/Category" 1120 | }, 1121 | "photoUrls": { 1122 | "type": "array", 1123 | "xml": { 1124 | "wrapped": true 1125 | }, 1126 | "items": { 1127 | "type": "string", 1128 | "xml": { 1129 | "name": "photoUrl" 1130 | } 1131 | } 1132 | }, 1133 | "tags": { 1134 | "type": "array", 1135 | "xml": { 1136 | "wrapped": true 1137 | }, 1138 | "items": { 1139 | "$ref": "#/components/schemas/Tag" 1140 | } 1141 | }, 1142 | "status": { 1143 | "type": "string", 1144 | "description": "pet status in the store", 1145 | "enum": [ 1146 | "available", 1147 | "pending", 1148 | "sold" 1149 | ] 1150 | } 1151 | }, 1152 | "xml": { 1153 | "name": "pet" 1154 | } 1155 | }, 1156 | "ApiResponse": { 1157 | "type": "object", 1158 | "properties": { 1159 | "code": { 1160 | "type": "integer", 1161 | "format": "int32" 1162 | }, 1163 | "type": { 1164 | "type": "string" 1165 | }, 1166 | "message": { 1167 | "type": "string" 1168 | } 1169 | }, 1170 | "xml": { 1171 | "name": "##default" 1172 | } 1173 | } 1174 | }, 1175 | "requestBodies": { 1176 | "Pet": { 1177 | "description": "Pet object that needs to be added to the store", 1178 | "content": { 1179 | "application/json": { 1180 | "schema": { 1181 | "$ref": "#/components/schemas/Pet" 1182 | } 1183 | }, 1184 | "application/xml": { 1185 | "schema": { 1186 | "$ref": "#/components/schemas/Pet" 1187 | } 1188 | } 1189 | } 1190 | }, 1191 | "UserArray": { 1192 | "description": "List of user object", 1193 | "content": { 1194 | "application/json": { 1195 | "schema": { 1196 | "type": "array", 1197 | "items": { 1198 | "$ref": "#/components/schemas/User" 1199 | } 1200 | } 1201 | } 1202 | } 1203 | } 1204 | }, 1205 | "securitySchemes": { 1206 | "petstore_auth": { 1207 | "type": "oauth2", 1208 | "flows": { 1209 | "implicit": { 1210 | "authorizationUrl": "https://petstore3.swagger.io/oauth/authorize", 1211 | "scopes": { 1212 | "write:pets": "modify pets in your account", 1213 | "read:pets": "read your pets" 1214 | } 1215 | } 1216 | } 1217 | }, 1218 | "api_key": { 1219 | "type": "apiKey", 1220 | "name": "api_key", 1221 | "in": "header" 1222 | } 1223 | } 1224 | } 1225 | } 1226 | -------------------------------------------------------------------------------- /tests/paths.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | TESTS_ROOT = Path(__file__).parent.absolute() 4 | SPEC_EXAMPLES_DIR = TESTS_ROOT.parent / "specification" / "examples" / "v3.0" 5 | CUSTOM_EXAMPLES_DIR = TESTS_ROOT / "custom_examples" 6 | 7 | SPECS = [ 8 | (x, SPEC_EXAMPLES_DIR / f"{x}.json") for x in [ 9 | 'petstore', 10 | 'petstore-expanded', 11 | 'api-with-examples', 12 | 'callback-example', 13 | 'link-example', 14 | 'uspto', 15 | ] 16 | ] + [ 17 | (x, CUSTOM_EXAMPLES_DIR / f"{x}.json") for x in [ 18 | "one", 19 | "petstore" 20 | ] 21 | ] 22 | -------------------------------------------------------------------------------- /tests/test_spec_parsing.py: -------------------------------------------------------------------------------- 1 | import pytest as pt 2 | 3 | from openapi_type import serialize_spec, parse_spec, OpenAPI 4 | 5 | from .utils import load_spec 6 | from .paths import SPECS 7 | 8 | 9 | @pt.mark.parametrize('name, spec_file', SPECS) 10 | def test_parsing(name, spec_file): 11 | oapi = load_spec(spec_file) 12 | assert isinstance(oapi, OpenAPI) 13 | assert parse_spec(serialize_spec(oapi)) == oapi 14 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | from openapi_type import OpenAPI, parse_spec 5 | 6 | 7 | def load_spec(s: Path) -> OpenAPI: 8 | with s.open() as fd: 9 | return parse_spec(json.load(fd)) 10 | --------------------------------------------------------------------------------