├── .checkignore ├── .editorconfig ├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile ├── authors.rst ├── conf.py ├── contributing.rst ├── history.rst ├── index.rst ├── installation.rst ├── make.bat ├── readme.rst └── usage.rst ├── requirements.txt ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── swagger_parser ├── __init__.py └── swagger_parser.py ├── tests ├── __init__.py ├── conftest.py ├── files │ ├── allof.yaml │ ├── array_items_list.yaml │ ├── array_ref_simple.yaml │ ├── inline.yaml │ ├── no_properties.yaml │ ├── null_type.yaml │ ├── object_no_schema.yaml │ ├── swagger.yaml │ ├── swagger_arrays.yaml │ └── type_list.yaml └── test_swagger_parser.py └── tox.ini /.checkignore: -------------------------------------------------------------------------------- 1 | tests/* 2 | travis_pypi_setup.py 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | matrix: 3 | include: 4 | - python: 3.6 5 | env: TOX_ENV=py36 6 | - python: 3.7 7 | env: TOX_ENV=py37 8 | - python: 3.8 9 | env: TOX_ENV=py38 10 | - python: 3.6 11 | env: TOX_ENV=flake8 12 | install: 13 | - pip install -U tox 14 | script: 15 | - tox -e $TOX_ENV 16 | deploy: 17 | provider: pypi 18 | distributions: sdist bdist_wheel 19 | user: "__token__" 20 | password: "pypi-AgEIcHlwaS5vcmcCJDE0YzU1YjU2LWMxOWItNGMyNi05ZDkwLWVlYTc3MzZmZmM5NwACP3sicGVybWlzc2lvbnMiOiB7InByb2plY3RzIjogWyJzd2FnZ2VyLXBhcnNlciJdfSwgInZlcnNpb24iOiAxfQAABiCBt51FKTKc5aKkDo7oXxBtFvbQxy_iDvSrlqUzcZk5cg" 21 | on: 22 | tags: true 23 | branch: master 24 | repo: cyprieng/swagger-parser 25 | condition: $TOX_ENV == py36 26 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Cyprien Guillemot 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every 8 | little bit helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/cyprieng/swagger_parser/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" 30 | is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "feature" 36 | is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | Swagger Parser could always use more documentation, whether as part of the 42 | official Swagger Parser docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/cyprieng/swagger_parser/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `swagger_parser` for local development. 61 | 62 | 1. Fork the `swagger_parser` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/swagger_parser.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv swagger_parser 70 | $ cd swagger_parser/ 71 | $ python setup.py develop 72 | 73 | 4. Create a branch for local development:: 74 | 75 | $ git checkout -b name-of-your-bugfix-or-feature 76 | 77 | Now you can make your changes locally. 78 | 79 | 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: 80 | 81 | $ flake8 swagger_parser tests 82 | $ python setup.py test 83 | $ tox 84 | 85 | To get flake8 and tox, just pip install them into your virtualenv. 86 | 87 | 6. Commit your changes and push your branch to GitHub:: 88 | 89 | $ git add . 90 | $ git commit -m "Your detailed description of your changes." 91 | $ git push origin name-of-your-bugfix-or-feature 92 | 93 | 7. Submit a pull request through the GitHub website. 94 | 95 | Pull Request Guidelines 96 | ----------------------- 97 | 98 | Before you submit a pull request, check that it meets these guidelines: 99 | 100 | 1. The pull request should include tests. 101 | 2. If the pull request adds functionality, the docs should be updated. Put 102 | your new functionality into a function with a docstring, and add the 103 | feature to the list in README.rst. 104 | 3. The pull request should work for Python 2.6, 2.7, 3.3, 3.4 and 3.5, and for PyPy. Check 105 | https://travis-ci.org/cyprieng/swagger_parser/pull_requests 106 | and make sure that the tests pass for all supported Python versions. 107 | 108 | Tips 109 | ---- 110 | 111 | To run a subset of tests:: 112 | 113 | $ python -m unittest tests.test_swagger_parser 114 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 1.0.0 (2017-6-11) 6 | ----------------- 7 | 8 | * Drop support for python 2.6, add support for python 3.5, python 3.6 and pypy 9 | * Fix issue `#35 `_ 10 | * `Add file parser tests and fixes for #40, #41, #42, #43, #44, #45 `_, thanks to @mtherieau 11 | * `Use isinstance for simple type checking `_, thanks to @pankaj28843 12 | * `Fixes for #31, #32, #33 `_, thanks to @crudo10 and @beanqueen for the review 13 | * `Bug fix when dictionary only contains 1 element `_, thanks to @TenOs 14 | * `Add tests for "official" petstore json and yaml `_, thanks to @beanqueen 15 | 16 | 17 | 0.1.11 (2016-9-25) 18 | ------------------ 19 | 20 | * Support additionalProperties. 21 | 22 | 0.1.10 (2016-8-25) 23 | ------------------ 24 | 25 | * Don't choke if there are no definitions 26 | * Generate operations without operationId 27 | * Generate example from properties 28 | 29 | 0.1.9 (2016-7-28) 30 | ------------------ 31 | 32 | * Support array definitions. 33 | 34 | 0.1.8 (2016-5-11) 35 | ------------------ 36 | 37 | * Support type field to be an array. 38 | * Use base path to validate request. 39 | 40 | 0.1.7 (2016-4-1) 41 | ------------------ 42 | 43 | * Support UTF-8 in swagger.yaml. 44 | 45 | 0.1.6 (2016-3-16) 46 | ------------------ 47 | 48 | * Add support for path-level parameters. 49 | 50 | 0.1.5 (2016-2-17) 51 | ------------------ 52 | 53 | * Add support for parameters references in path specs. 54 | 55 | 0.1.4 (2016-2-10) 56 | ------------------ 57 | 58 | * Handle string as status_code. 59 | 60 | 0.1.3 (2016-2-3) 61 | ------------------ 62 | 63 | * Fix a bug in get_response_example with schema only containing a type field. 64 | 65 | 0.1.2 (2016-2-3) 66 | ------------------ 67 | 68 | * Support schema with only a type field. 69 | 70 | 0.1.1 (2016-1-31) 71 | ------------------ 72 | 73 | * Change license to MIT. 74 | 75 | 0.1 (2016-1-28) 76 | ------------------ 77 | 78 | * First release on PyPI. 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 TraxAir 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | include requirements.txt 7 | include requirements_dev.txt 8 | 9 | recursive-include tests * 10 | recursive-exclude * __pycache__ 11 | recursive-exclude * *.py[co] 12 | 13 | recursive-include docs *.rst conf.py Makefile make.bat 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean-pyc clean-build docs clean 2 | define BROWSER_PYSCRIPT 3 | import os, webbrowser, sys 4 | try: 5 | from urllib import pathname2url 6 | except: 7 | from urllib.request import pathname2url 8 | 9 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 10 | endef 11 | export BROWSER_PYSCRIPT 12 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 13 | 14 | help: 15 | @echo "clean - remove all build, test, coverage and Python artifacts" 16 | @echo "clean-build - remove build artifacts" 17 | @echo "clean-pyc - remove Python file artifacts" 18 | @echo "clean-test - remove test and coverage artifacts" 19 | @echo "lint - check style with flake8" 20 | @echo "test - run tests quickly with the default Python" 21 | @echo "test-all - run tests on every Python version with tox" 22 | @echo "coverage - check code coverage quickly with the default Python" 23 | @echo "docs - generate Sphinx HTML documentation, including API docs" 24 | @echo "release - package and upload a release" 25 | @echo "dist - package" 26 | @echo "install - install the package to the active Python's site-packages" 27 | 28 | clean: clean-build clean-pyc clean-test 29 | 30 | clean-build: 31 | rm -fr build/ 32 | rm -fr dist/ 33 | rm -fr .eggs/ 34 | find . -name '*.egg-info' -exec rm -fr {} + 35 | find . -name '*.egg' -exec rm -f {} + 36 | 37 | clean-pyc: 38 | find . -name '*.pyc' -exec rm -f {} + 39 | find . -name '*.pyo' -exec rm -f {} + 40 | find . -name '*~' -exec rm -f {} + 41 | find . -name '__pycache__' -exec rm -fr {} + 42 | 43 | clean-test: 44 | rm -fr .tox/ 45 | rm -f .coverage 46 | rm -fr htmlcov/ 47 | 48 | lint: 49 | flake8 swagger_parser tests 50 | 51 | test: 52 | python setup.py test 53 | 54 | test-all: 55 | tox 56 | 57 | coverage: 58 | coverage run --source swagger_parser setup.py test 59 | coverage report -m 60 | coverage html 61 | $(BROWSER) htmlcov/index.html 62 | 63 | docs: 64 | rm -f docs/swagger_parser.rst 65 | rm -f docs/modules.rst 66 | sphinx-apidoc -o docs/ swagger_parser 67 | $(MAKE) -C docs clean 68 | $(MAKE) -C docs html 69 | $(BROWSER) docs/_build/html/index.html 70 | 71 | servedocs: docs 72 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 73 | 74 | release: clean 75 | python setup.py sdist upload 76 | python setup.py bdist_wheel upload 77 | 78 | dist: clean 79 | python setup.py sdist 80 | python setup.py bdist_wheel 81 | ls -l dist 82 | 83 | install: clean 84 | python setup.py install 85 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://travis-ci.org/Trax-air/swagger-parser.svg?branch=master 2 | :alt: Travis status 3 | :target: https://travis-ci.org/Trax-air/swagger-parser 4 | .. image:: https://badges.gitter.im/Trax-air/swagger-parser.svg 5 | :alt: Join the chat at https://gitter.im/Trax-air/swagger-parser 6 | :target: https://gitter.im/Trax-air/swagger-parser?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 7 | .. image:: https://img.shields.io/pypi/v/swagger-parser.svg 8 | :target: https://pypi.python.org/pypi/swagger-parser/ 9 | 10 | swagger-parser 11 | ============== 12 | 13 | Swagger-parser is a python module giving you access to some interesting data about your swagger file. Like getting a dictionary example from a definition name, get the definition of a dictionary, and more. 14 | 15 | Related Libraries 16 | ----------------- 17 | You may find related libraries to this one: 18 | 19 | * https://github.com/Trax-air/swagger-tester: Auto-test your swagger API in your unit tests. All test calls are generated by your swagger file. 20 | * https://github.com/Trax-air/swagger-stub: A stub you can use in your client's unit tests. All the HTTP calls to your swagger API are mocked by default. You can also add your own mocked_calls in your test functions. 21 | * https://github.com/Trax-air/swagger-aggregator: Aggregate several swagger specs into one. Useful for your API gateways! 22 | 23 | Example Usage 24 | ------------- 25 | 26 | .. code:: python 27 | 28 | from swagger_parser import SwaggerParser 29 | 30 | parser = SwaggerParser(swagger_path='swagger_path') # Init with file 31 | parser = SwaggerParser(swagger_dict={}) # Init with dictionary 32 | 33 | # Get an example of dict for the definition Foo 34 | parser.definitions_example.get('Foo') 35 | 36 | # Get the definition of a dictionary 37 | test = { 38 | 'foo': 'bar' 39 | } 40 | parser.get_dict_definition(test) 41 | 42 | # Validate the definition of a dict 43 | parser.validate_definition('Foo', test) 44 | 45 | # Validate that the given data match a path specification 46 | parser.validate_request('/foo', 'post', body=test, query={'foo': 'bar'}) 47 | 48 | # Get the possible return value of a path 49 | # It will return a dictionary with keys as status_code 50 | # and value as example of return value. 51 | parser.get_request_data('/foo', 'post', body=test) 52 | 53 | # Get an example of a correct body for a path 54 | parser.get_send_request_correct_body('/foo', 'post') 55 | 56 | Documentation 57 | ------------- 58 | 59 | More documentation is available at https://swagger-parser.readthedocs.org/en/latest/. 60 | 61 | Setup 62 | ----- 63 | 64 | `make install` or `pip install swagger-parser` 65 | 66 | License 67 | ------- 68 | 69 | swagger-parser is licensed under http://opensource.org/licenses/MIT. 70 | -------------------------------------------------------------------------------- /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 clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/swagger_parser.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/swagger_parser.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/swagger_parser" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/swagger_parser" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # swagger_parser documentation build configuration file, created by 5 | # sphinx-quickstart on Tue Jul 9 22:26:36 2013. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | # If extensions (or modules to document with autodoc) are in another 20 | # directory, add these directories to sys.path here. If the directory is 21 | # relative to the documentation root, use os.path.abspath to make it 22 | # absolute, like shown here. 23 | #sys.path.insert(0, os.path.abspath('.')) 24 | 25 | # Get the project root dir, which is the parent dir of this 26 | cwd = os.getcwd() 27 | project_root = os.path.dirname(cwd) 28 | 29 | # Insert the project root dir as the first element in the PYTHONPATH. 30 | # This lets us ensure that the source package is imported, and that its 31 | # version is used. 32 | sys.path.insert(0, project_root) 33 | 34 | import swagger_parser 35 | 36 | # -- General configuration --------------------------------------------- 37 | 38 | # If your documentation needs a minimal Sphinx version, state it here. 39 | #needs_sphinx = '1.0' 40 | 41 | # Add any Sphinx extension module names here, as strings. They can be 42 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 43 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 44 | 45 | # Add any paths that contain templates here, relative to this directory. 46 | templates_path = ['_templates'] 47 | 48 | # The suffix of source filenames. 49 | source_suffix = '.rst' 50 | 51 | # The encoding of source files. 52 | #source_encoding = 'utf-8-sig' 53 | 54 | # The master toctree document. 55 | master_doc = 'index' 56 | 57 | # General information about the project. 58 | project = u'Swagger Parser' 59 | copyright = u'2016, Cyprien Guillemot' 60 | 61 | # The version info for the project you're documenting, acts as replacement 62 | # for |version| and |release|, also used in various other places throughout 63 | # the built documents. 64 | # 65 | # The short X.Y version. 66 | version = swagger_parser.__version__ 67 | # The full version, including alpha/beta/rc tags. 68 | release = swagger_parser.__version__ 69 | 70 | # The language for content autogenerated by Sphinx. Refer to documentation 71 | # for a list of supported languages. 72 | #language = None 73 | 74 | # There are two options for replacing |today|: either, you set today to 75 | # some non-false value, then it is used: 76 | #today = '' 77 | # Else, today_fmt is used as the format for a strftime call. 78 | #today_fmt = '%B %d, %Y' 79 | 80 | # List of patterns, relative to source directory, that match files and 81 | # directories to ignore when looking for source files. 82 | exclude_patterns = ['_build'] 83 | 84 | # The reST default role (used for this markup: `text`) to use for all 85 | # documents. 86 | #default_role = None 87 | 88 | # If true, '()' will be appended to :func: etc. cross-reference text. 89 | #add_function_parentheses = True 90 | 91 | # If true, the current module name will be prepended to all description 92 | # unit titles (such as .. function::). 93 | #add_module_names = True 94 | 95 | # If true, sectionauthor and moduleauthor directives will be shown in the 96 | # output. They are ignored by default. 97 | #show_authors = False 98 | 99 | # The name of the Pygments (syntax highlighting) style to use. 100 | pygments_style = 'sphinx' 101 | 102 | # A list of ignored prefixes for module index sorting. 103 | #modindex_common_prefix = [] 104 | 105 | # If true, keep warnings as "system message" paragraphs in the built 106 | # documents. 107 | #keep_warnings = False 108 | 109 | 110 | # -- Options for HTML output ------------------------------------------- 111 | 112 | # The theme to use for HTML and HTML Help pages. See the documentation for 113 | # a list of builtin themes. 114 | html_theme = 'default' 115 | 116 | # Theme options are theme-specific and customize the look and feel of a 117 | # theme further. For a list of options available for each theme, see the 118 | # documentation. 119 | #html_theme_options = {} 120 | 121 | # Add any paths that contain custom themes here, relative to this directory. 122 | #html_theme_path = [] 123 | 124 | # The name for this set of Sphinx documents. If None, it defaults to 125 | # " v documentation". 126 | #html_title = None 127 | 128 | # A shorter title for the navigation bar. Default is the same as 129 | # html_title. 130 | #html_short_title = None 131 | 132 | # The name of an image file (relative to this directory) to place at the 133 | # top of the sidebar. 134 | #html_logo = None 135 | 136 | # The name of an image file (within the static path) to use as favicon 137 | # of the docs. This file should be a Windows icon file (.ico) being 138 | # 16x16 or 32x32 pixels large. 139 | #html_favicon = None 140 | 141 | # Add any paths that contain custom static files (such as style sheets) 142 | # here, relative to this directory. They are copied after the builtin 143 | # static files, so a file named "default.css" will overwrite the builtin 144 | # "default.css". 145 | html_static_path = ['_static'] 146 | 147 | # If not '', a 'Last updated on:' timestamp is inserted at every page 148 | # bottom, using the given strftime format. 149 | #html_last_updated_fmt = '%b %d, %Y' 150 | 151 | # If true, SmartyPants will be used to convert quotes and dashes to 152 | # typographically correct entities. 153 | #html_use_smartypants = True 154 | 155 | # Custom sidebar templates, maps document names to template names. 156 | #html_sidebars = {} 157 | 158 | # Additional templates that should be rendered to pages, maps page names 159 | # to template names. 160 | #html_additional_pages = {} 161 | 162 | # If false, no module index is generated. 163 | #html_domain_indices = True 164 | 165 | # If false, no index is generated. 166 | #html_use_index = True 167 | 168 | # If true, the index is split into individual pages for each letter. 169 | #html_split_index = False 170 | 171 | # If true, links to the reST sources are added to the pages. 172 | #html_show_sourcelink = True 173 | 174 | # If true, "Created using Sphinx" is shown in the HTML footer. 175 | # Default is True. 176 | #html_show_sphinx = True 177 | 178 | # If true, "(C) Copyright ..." is shown in the HTML footer. 179 | # Default is True. 180 | #html_show_copyright = True 181 | 182 | # If true, an OpenSearch description file will be output, and all pages 183 | # will contain a tag referring to it. The value of this option 184 | # must be the base URL from which the finished HTML is served. 185 | #html_use_opensearch = '' 186 | 187 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 188 | #html_file_suffix = None 189 | 190 | # Output file base name for HTML help builder. 191 | htmlhelp_basename = 'swagger_parserdoc' 192 | 193 | 194 | # -- Options for LaTeX output ------------------------------------------ 195 | 196 | latex_elements = { 197 | # The paper size ('letterpaper' or 'a4paper'). 198 | #'papersize': 'letterpaper', 199 | 200 | # The font size ('10pt', '11pt' or '12pt'). 201 | #'pointsize': '10pt', 202 | 203 | # Additional stuff for the LaTeX preamble. 204 | #'preamble': '', 205 | } 206 | 207 | # Grouping the document tree into LaTeX files. List of tuples 208 | # (source start file, target name, title, author, documentclass 209 | # [howto/manual]). 210 | latex_documents = [ 211 | ('index', 'swagger_parser.tex', 212 | u'Swagger Parser Documentation', 213 | u'Cyprien Guillemot', 'manual'), 214 | ] 215 | 216 | # The name of an image file (relative to this directory) to place at 217 | # the top of the title page. 218 | #latex_logo = None 219 | 220 | # For "manual" documents, if this is true, then toplevel headings 221 | # are parts, not chapters. 222 | #latex_use_parts = False 223 | 224 | # If true, show page references after internal links. 225 | #latex_show_pagerefs = False 226 | 227 | # If true, show URL addresses after external links. 228 | #latex_show_urls = False 229 | 230 | # Documents to append as an appendix to all manuals. 231 | #latex_appendices = [] 232 | 233 | # If false, no module index is generated. 234 | #latex_domain_indices = True 235 | 236 | 237 | # -- Options for manual page output ------------------------------------ 238 | 239 | # One entry per manual page. List of tuples 240 | # (source start file, name, description, authors, manual section). 241 | man_pages = [ 242 | ('index', 'swagger_parser', 243 | u'Swagger Parser Documentation', 244 | [u'Cyprien Guillemot'], 1) 245 | ] 246 | 247 | # If true, show URL addresses after external links. 248 | #man_show_urls = False 249 | 250 | 251 | # -- Options for Texinfo output ---------------------------------------- 252 | 253 | # Grouping the document tree into Texinfo files. List of tuples 254 | # (source start file, target name, title, author, 255 | # dir menu entry, description, category) 256 | texinfo_documents = [ 257 | ('index', 'swagger_parser', 258 | u'Swagger Parser Documentation', 259 | u'Cyprien Guillemot', 260 | 'swagger_parser', 261 | 'One line description of project.', 262 | 'Miscellaneous'), 263 | ] 264 | 265 | # Documents to append as an appendix to all manuals. 266 | #texinfo_appendices = [] 267 | 268 | # If false, no module index is generated. 269 | #texinfo_domain_indices = True 270 | 271 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 272 | #texinfo_show_urls = 'footnote' 273 | 274 | # If true, do not generate a @detailmenu in the "Top" node's menu. 275 | #texinfo_no_detailmenu = False 276 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. swagger_parser documentation master file, created by 2 | sphinx-quickstart on Tue Jul 9 22:26:36 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Swagger Parser's documentation! 7 | ====================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | readme 15 | installation 16 | usage 17 | contributing 18 | authors 19 | history 20 | 21 | Indices and tables 22 | ================== 23 | 24 | * :ref:`genindex` 25 | * :ref:`modindex` 26 | * :ref:`search` 27 | 28 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | At the command line:: 8 | 9 | $ easy_install swagger_parser 10 | 11 | Or, if you have virtualenvwrapper installed:: 12 | 13 | $ mkvirtualenv swagger_parser 14 | $ pip install swagger_parser 15 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\swagger_parser.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\swagger_parser.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | To use Swagger Parser in a project: 6 | 7 | .. code:: python 8 | 9 | from swagger_parser import SwaggerParser 10 | 11 | parser = SwaggerParser(swagger_path='swagger_path') # Init with file 12 | parser = SwaggerParser(swagger_dict={}) # Init with dictionary 13 | 14 | # Get an example of dict for the definition Foo 15 | parser.definitions_example.get('Foo') 16 | 17 | # Get the definition of a dictionary 18 | test = { 19 | 'foo': 'bar' 20 | } 21 | parser.get_dict_definition(test) 22 | 23 | # Validate the definition of a dict 24 | parser.validate_definition('Foo', test) 25 | 26 | # Validate that the given data match a path specification 27 | parser.validate_request('/foo', 'post', body=test, query={'foo': 'bar'}) 28 | 29 | # Get the possible return value of a path 30 | # It will return a dictionary with keys as status_code 31 | # and value as example of return value. 32 | parser.get_request_data('/foo', 'post', body=test) 33 | 34 | # Get an example of a correct body for a path 35 | parser.get_send_request_correct_body('/foo', 'post') 36 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | swagger-spec-validator>=2.0.2 2 | jsonschema>=2.5.1 3 | requests 4 | PyYAML==5.2b1 5 | jinja2>=2.8 6 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | bumpversion>=0.5.3 2 | wheel>=0.26.0 3 | watchdog>=0.8.3 4 | flake8>=2.4.1 5 | tox>=2.1.1 6 | coverage>=4.0 7 | Sphinx>=1.4.6 8 | pytest>=2.7.0 9 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.0.2 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | 8 | [bumpversion:file:swagger_parser/__init__.py] 9 | 10 | [wheel] 11 | universal = 1 12 | 13 | [aliases] 14 | test = pytest 15 | 16 | [flake8] 17 | max-line-length = 119 18 | exclude = docs/conf.py,.eggs* 19 | 20 | [tool:pytest] 21 | addopts = -v -s 22 | 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | try: 5 | from setuptools import setup 6 | except ImportError: 7 | from distutils.core import setup 8 | 9 | 10 | def parse_requirements(filename): 11 | """ load requirements from a pip requirements file """ 12 | lineiter = (line.strip() for line in open(filename)) 13 | return [line for line in lineiter if line and not line.startswith("#")] 14 | 15 | 16 | with open('README.rst') as readme_file: 17 | readme = readme_file.read() 18 | 19 | with open('HISTORY.rst') as history_file: 20 | history = history_file.read() 21 | 22 | requirements = parse_requirements('requirements.txt') 23 | test_requirements = parse_requirements('requirements_dev.txt') 24 | 25 | setup( 26 | name='swagger_parser', 27 | version='1.0.2', 28 | description="Swagger parser giving useful informations about your swagger files", 29 | long_description=readme + '\n\n' + history, 30 | author="Cyprien Guillemot", 31 | author_email='cyprien.guillemot@gmail.com', 32 | url='https://github.com/Trax-air/swagger-parser', 33 | packages=[ 34 | 'swagger_parser', 35 | ], 36 | package_dir={'swagger_parser': 37 | 'swagger_parser'}, 38 | include_package_data=True, 39 | setup_requires=['pytest-runner'], 40 | install_requires=requirements, 41 | license="MIT", 42 | zip_safe=False, 43 | keywords='swagger, parser, API, REST, swagger-parser', 44 | classifiers=[ 45 | 'Development Status :: 5 - Production/Stable', 46 | 'Intended Audience :: Developers', 47 | 'License :: OSI Approved :: MIT License', 48 | 'Natural Language :: English', 49 | 'Programming Language :: Python :: 3' 50 | ], 51 | test_suite='tests', 52 | tests_require=test_requirements 53 | ) 54 | -------------------------------------------------------------------------------- /swagger_parser/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .swagger_parser import SwaggerParser # noqa: F401 4 | 5 | __author__ = 'Cyprien Guillemot' 6 | __email__ = 'cyprien.guillemot@gmail.com' 7 | __version__ = '1.0.0' 8 | -------------------------------------------------------------------------------- /swagger_parser/swagger_parser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import codecs 4 | import datetime 5 | import hashlib 6 | import jinja2 7 | import json 8 | import logging 9 | import re 10 | import six 11 | import sys 12 | import yaml 13 | 14 | from copy import deepcopy 15 | 16 | try: 17 | from StringIO import StringIO 18 | except ImportError: # Python 3 19 | from io import StringIO 20 | 21 | from swagger_spec_validator.validator20 import validate_spec 22 | 23 | 24 | class SwaggerParser(object): 25 | """Parse a swagger YAML file. 26 | 27 | Get definitions examples, routes default data, and routes validator. 28 | This only works with swagger 2.0. 29 | 30 | Attributes: 31 | specification: dict of the yaml file. 32 | definitions_example: dict of definition with an example. 33 | paths: dict of path with their actions, parameters, and responses. 34 | """ 35 | 36 | _HTTP_VERBS = set(['get', 'put', 'post', 'delete', 'options', 'head', 'patch']) 37 | 38 | def __init__(self, swagger_path=None, swagger_dict=None, swagger_yaml=None, use_example=True): 39 | """Run parsing from either a file or a dict. 40 | 41 | Args: 42 | swagger_path: path of the swagger file. 43 | swagger_dict: swagger dict. 44 | use_example: Define if we use the example from the YAML when we 45 | build definitions example (False value can be useful 46 | when making test. Problem can happen if set to True, eg 47 | POST {'id': 'example'}, GET /string => 404). 48 | 49 | Raises: 50 | - ValueError: if no swagger_path or swagger_dict is specified. 51 | Or if the given swagger is not valid. 52 | """ 53 | try: 54 | if swagger_path is not None: 55 | # Open yaml file 56 | arguments = {} 57 | with codecs.open(swagger_path, 'r', 'utf-8') as swagger_yaml: 58 | swagger_template = swagger_yaml.read() 59 | swagger_string = jinja2.Template(swagger_template).render(**arguments) 60 | self.specification = yaml.safe_load(swagger_string) 61 | elif swagger_yaml is not None: 62 | json_ = yaml.safe_load(swagger_yaml) 63 | json_string = json.dumps(json_) 64 | self.specification = json.loads(json_string) 65 | elif swagger_dict is not None: 66 | self.specification = swagger_dict 67 | else: 68 | raise ValueError('You must specify a swagger_path or dict') 69 | validate_spec(self.specification, '') 70 | except Exception as e: 71 | six.reraise( 72 | ValueError, 73 | ValueError('{0} is not a valid swagger2.0 file: {1}'.format(swagger_path, e)), 74 | sys.exc_info()[2]) 75 | 76 | # Run parsing 77 | self.use_example = use_example 78 | self.base_path = self.specification.get('basePath', '') 79 | self.definitions_example = {} 80 | self.build_definitions_example() 81 | self.paths = {} 82 | self.operation = {} 83 | self.generated_operation = {} 84 | self.get_paths_data() 85 | 86 | def build_definitions_example(self): 87 | """Parse all definitions in the swagger specification.""" 88 | for def_name, def_spec in self.specification.get('definitions', {}).items(): 89 | self.build_one_definition_example(def_name) 90 | 91 | def build_one_definition_example(self, def_name): 92 | """Build the example for the given definition. 93 | 94 | Args: 95 | def_name: Name of the definition. 96 | 97 | Returns: 98 | True if the example has been created, False if an error occured. 99 | """ 100 | if def_name in self.definitions_example.keys(): # Already processed 101 | return True 102 | elif def_name not in self.specification['definitions'].keys(): # Def does not exist 103 | return False 104 | 105 | self.definitions_example[def_name] = {} 106 | def_spec = self.specification['definitions'][def_name] 107 | 108 | if def_spec.get('type') == 'array' and 'items' in def_spec: 109 | item = self.get_example_from_prop_spec(def_spec['items']) 110 | self.definitions_example[def_name] = [item] 111 | return True 112 | 113 | if 'properties' not in def_spec: 114 | self.definitions_example[def_name] = self.get_example_from_prop_spec(def_spec) 115 | return True 116 | 117 | # Get properties example value 118 | for prop_name, prop_spec in def_spec['properties'].items(): 119 | example = self.get_example_from_prop_spec(prop_spec) 120 | if example is None: 121 | return False 122 | self.definitions_example[def_name][prop_name] = example 123 | 124 | return True 125 | 126 | @staticmethod 127 | def check_type(value, type_def): 128 | """Check if the value is in the type given in type_def. 129 | 130 | Args: 131 | value: the var to test. 132 | type_def: string representing the type in swagger. 133 | 134 | Returns: 135 | True if the type is correct, False otherwise. 136 | """ 137 | if type_def == 'integer': 138 | try: 139 | # We accept string with integer ex: '123' 140 | int(value) 141 | return True 142 | except ValueError: 143 | return isinstance(value, six.integer_types) and not isinstance(value, bool) 144 | elif type_def == 'number': 145 | return isinstance(value, (six.integer_types, float)) and not isinstance(value, bool) 146 | elif type_def == 'string': 147 | return isinstance(value, (six.text_type, six.string_types, datetime.datetime)) 148 | elif type_def == 'boolean': 149 | return (isinstance(value, bool) or 150 | (isinstance(value, (six.text_type, six.string_types,)) and 151 | value.lower() in ['true', 'false']) 152 | ) 153 | else: 154 | return False 155 | 156 | def get_example_from_prop_spec(self, prop_spec, from_allof=False): 157 | """Return an example value from a property specification. 158 | 159 | Args: 160 | prop_spec: the specification of the property. 161 | from_allof: whether these properties are part of an 162 | allOf section 163 | 164 | Returns: 165 | An example value 166 | """ 167 | # Read example directly from (X-)Example or Default value 168 | easy_keys = ['example', 'x-example', 'default'] 169 | for key in easy_keys: 170 | if key in prop_spec.keys() and self.use_example: 171 | return prop_spec[key] 172 | # Enum 173 | if 'enum' in prop_spec.keys(): 174 | return prop_spec['enum'][0] 175 | # From definition 176 | if '$ref' in prop_spec.keys(): 177 | return self._example_from_definition(prop_spec) 178 | # Process AllOf section 179 | if 'allOf' in prop_spec.keys(): 180 | return self._example_from_allof(prop_spec) 181 | # Complex type 182 | if 'type' not in prop_spec: 183 | return self._example_from_complex_def(prop_spec) 184 | # Object - read from properties, without references 185 | if prop_spec['type'] == 'object': 186 | example, additional_properties = self._get_example_from_properties(prop_spec) 187 | if additional_properties or from_allof: 188 | return example 189 | return [example] 190 | # Array 191 | if prop_spec['type'] == 'array' or (isinstance(prop_spec['type'], list) and prop_spec['type'][0] == 'array'): 192 | return self._example_from_array_spec(prop_spec) 193 | # File 194 | if prop_spec['type'] == 'file': 195 | return (StringIO('my file contents'), 'hello world.txt') 196 | # Date time 197 | if 'format' in prop_spec.keys() and prop_spec['format'] == 'date-time': 198 | return self._get_example_from_basic_type('datetime')[0] 199 | # List 200 | if isinstance(prop_spec['type'], list): 201 | return self._get_example_from_basic_type(prop_spec['type'][0])[0] 202 | 203 | # Default - basic type 204 | logging.info("falling back to basic type, no other match found") 205 | return self._get_example_from_basic_type(prop_spec['type'])[0] 206 | 207 | def _get_example_from_properties(self, spec): 208 | """Get example from the properties of an object defined inline. 209 | 210 | Args: 211 | prop_spec: property specification you want an example of. 212 | 213 | Returns: 214 | An example for the given spec 215 | A boolean, whether we had additionalProperties in the spec, or not 216 | """ 217 | local_spec = deepcopy(spec) 218 | 219 | # Handle additionalProperties if they exist 220 | # we replace additionalProperties with two concrete 221 | # properties so that examples can be generated 222 | additional_property = False 223 | if 'additionalProperties' in local_spec: 224 | additional_property = True 225 | if 'properties' not in local_spec: 226 | local_spec['properties'] = {} 227 | 228 | if isinstance(local_spec['additionalProperties'], bool): 229 | local_spec['additionalProperties'] = {} 230 | 231 | local_spec['properties'].update({ 232 | 'any_prop1': local_spec['additionalProperties'], 233 | 'any_prop2': local_spec['additionalProperties'], 234 | }) 235 | del(local_spec['additionalProperties']) 236 | required = local_spec.get('required', []) 237 | required += ['any_prop1', 'any_prop2'] 238 | local_spec['required'] = required 239 | 240 | example = {} 241 | properties = local_spec.get('properties') 242 | if properties is not None: 243 | required = local_spec.get('required', properties.keys()) 244 | 245 | for inner_name, inner_spec in properties.items(): 246 | if inner_name not in required: 247 | continue 248 | partial = self.get_example_from_prop_spec(inner_spec) 249 | # While get_example_from_prop_spec is supposed to return a list, 250 | # we don't actually want that when recursing to build from 251 | # properties 252 | if isinstance(partial, list): 253 | partial = partial[0] 254 | example[inner_name] = partial 255 | 256 | return example, additional_property 257 | 258 | @staticmethod 259 | def _get_example_from_basic_type(type): 260 | """Get example from the given type. 261 | 262 | Args: 263 | type: the type you want an example of. 264 | 265 | Returns: 266 | An array with two example values of the given type. 267 | """ 268 | if type == 'integer': 269 | return [42, 24] 270 | elif type == 'number': 271 | return [5.5, 5.5] 272 | elif type == 'string': 273 | return ['string', 'string2'] 274 | elif type == 'datetime': 275 | return ['2015-08-28T09:02:57.481Z', '2015-08-28T09:02:57.481Z'] 276 | elif type == 'boolean': 277 | return [False, True] 278 | elif type == 'null': 279 | return ['null', 'null'] 280 | 281 | @staticmethod 282 | def _definition_from_example(example): 283 | """Generates a swagger definition json from a given example 284 | Works only for simple types in the dict 285 | 286 | Args: 287 | example: The example for which we want a definition 288 | Type is DICT 289 | 290 | Returns: 291 | A dict that is the swagger definition json 292 | """ 293 | assert isinstance(example, dict) 294 | 295 | def _has_simple_type(value): 296 | accepted = (str, int, float, bool) 297 | return isinstance(value, accepted) 298 | 299 | definition = { 300 | 'type': 'object', 301 | 'properties': {}, 302 | } 303 | for key, value in example.items(): 304 | if not _has_simple_type(value): 305 | raise Exception("Not implemented yet") 306 | ret_value = None 307 | if isinstance(value, str): 308 | ret_value = {'type': 'string'} 309 | elif isinstance(value, int): 310 | ret_value = {'type': 'integer', 'format': 'int64'} 311 | elif isinstance(value, float): 312 | ret_value = {'type': 'number', 'format': 'double'} 313 | elif isinstance(value, bool): 314 | ret_value = {'type': 'boolean'} 315 | else: 316 | raise Exception("Not implemented yet") 317 | definition['properties'][key] = ret_value 318 | 319 | return definition 320 | 321 | def _example_from_allof(self, prop_spec): 322 | """Get the examples from an allOf section. 323 | 324 | Args: 325 | prop_spec: property specification you want an example of. 326 | 327 | Returns: 328 | An example dict 329 | """ 330 | example_dict = {} 331 | for definition in prop_spec['allOf']: 332 | update = self.get_example_from_prop_spec(definition, True) 333 | example_dict.update(update) 334 | return example_dict 335 | 336 | def _example_from_definition(self, prop_spec): 337 | """Get an example from a property specification linked to a definition. 338 | 339 | Args: 340 | prop_spec: specification of the property you want an example of. 341 | 342 | Returns: 343 | An example. 344 | """ 345 | # Get value from definition 346 | definition_name = self.get_definition_name_from_ref(prop_spec['$ref']) 347 | 348 | if self.build_one_definition_example(definition_name): 349 | example_dict = self.definitions_example[definition_name] 350 | if not isinstance(example_dict, dict): 351 | return example_dict 352 | example = dict((example_name, example_value) for example_name, example_value in example_dict.items()) 353 | return example 354 | 355 | def _example_from_complex_def(self, prop_spec): 356 | """Get an example from a property specification. 357 | 358 | In case there is no "type" key in the root of the dictionary. 359 | 360 | Args: 361 | prop_spec: property specification you want an example of. 362 | 363 | Returns: 364 | An example. 365 | """ 366 | if 'schema' not in prop_spec: 367 | return [{}] 368 | elif 'type' not in prop_spec['schema']: 369 | definition_name = self.get_definition_name_from_ref(prop_spec['schema']['$ref']) 370 | if self.build_one_definition_example(definition_name): 371 | return self.definitions_example[definition_name] 372 | elif prop_spec['schema']['type'] == 'array': # Array with definition 373 | # Get value from definition 374 | if 'items' in prop_spec.keys(): 375 | definition_name = self.get_definition_name_from_ref(prop_spec['items']['$ref']) 376 | else: 377 | if '$ref' in prop_spec['schema']['items']: 378 | definition_name = self.get_definition_name_from_ref(prop_spec['schema']['items']['$ref']) 379 | else: 380 | definition_name = self.get_definition_name_from_ref(prop_spec['schema']['items']['type']) 381 | return [definition_name] 382 | return [self.definitions_example[definition_name]] 383 | else: 384 | return self.get_example_from_prop_spec(prop_spec['schema']) 385 | 386 | def _example_from_array_spec(self, prop_spec): 387 | """Get an example from a property specification of an array. 388 | 389 | Args: 390 | prop_spec: property specification you want an example of. 391 | 392 | Returns: 393 | An example array. 394 | """ 395 | # if items is a list, then each item has its own spec 396 | if isinstance(prop_spec['items'], list): 397 | return [self.get_example_from_prop_spec(item_prop_spec) for item_prop_spec in prop_spec['items']] 398 | # Standard types in array 399 | elif 'type' in prop_spec['items'].keys(): 400 | if 'format' in prop_spec['items'].keys() and prop_spec['items']['format'] == 'date-time': 401 | return self._get_example_from_basic_type('datetime') 402 | else: 403 | return self._get_example_from_basic_type(prop_spec['items']['type']) 404 | 405 | # Array with definition 406 | elif ('$ref' in prop_spec['items'].keys() or 407 | ('schema' in prop_spec and '$ref' in prop_spec['schema']['items'].keys())): 408 | # Get value from definition 409 | definition_name = self.get_definition_name_from_ref(prop_spec['items']['$ref']) or \ 410 | self.get_definition_name_from_ref(prop_spec['schema']['items']['$ref']) 411 | if self.build_one_definition_example(definition_name): 412 | example_dict = self.definitions_example[definition_name] 413 | if not isinstance(example_dict, dict): 414 | return [example_dict] 415 | if len(example_dict) == 1: 416 | try: # Python 2.7 417 | res = example_dict[example_dict.keys()[0]] 418 | except TypeError: # Python 3 419 | res = example_dict[list(example_dict)[0]] 420 | return res 421 | 422 | else: 423 | return_value = {} 424 | for example_name, example_value in example_dict.items(): 425 | return_value[example_name] = example_value 426 | return [return_value] 427 | elif 'properties' in prop_spec['items']: 428 | prop_example = {} 429 | for prop_name, prop_spec in prop_spec['items']['properties'].items(): 430 | example = self.get_example_from_prop_spec(prop_spec) 431 | if example is not None: 432 | prop_example[prop_name] = example 433 | return [prop_example] 434 | 435 | def get_dict_definition(self, dict, get_list=False): 436 | """Get the definition name of the given dict. 437 | 438 | Args: 439 | dict: dict to test. 440 | get_list: if set to true, return a list of definition that match the body. 441 | if False, only return the first. 442 | 443 | Returns: 444 | The definition name or None if the dict does not match any definition. 445 | If get_list is True, return a list of definition_name. 446 | """ 447 | list_def_candidate = [] 448 | for definition_name in self.specification['definitions'].keys(): 449 | if self.validate_definition(definition_name, dict): 450 | if not get_list: 451 | return definition_name 452 | list_def_candidate.append(definition_name) 453 | if get_list: 454 | return list_def_candidate 455 | return None 456 | 457 | def validate_additional_properties(self, valid_response, response): 458 | """Validates additional properties. In additional properties, we only 459 | need to compare the values of the dict, not the keys 460 | 461 | Args: 462 | valid_response: An example response (for example generated in 463 | _get_example_from_properties(self, spec)) 464 | Type is DICT 465 | response: The actual dict coming from the response 466 | Type is DICT 467 | 468 | Returns: 469 | A boolean - whether the actual response validates against the given example 470 | """ 471 | assert isinstance(valid_response, dict) 472 | assert isinstance(response, dict) 473 | 474 | # the type of the value of the first key/value in valid_response is our 475 | # expected type - if it is a dict or list, we must go deeper 476 | first_value = valid_response[list(valid_response)[0]] 477 | 478 | # dict 479 | if isinstance(first_value, dict): 480 | # try to find a definition for that first value 481 | definition = None 482 | definition_name = self.get_dict_definition(first_value) 483 | if definition_name is None: 484 | definition = self._definition_from_example(first_value) 485 | definition_name = 'self generated' 486 | for item in response.values(): 487 | if not self.validate_definition(definition_name, 488 | item, 489 | definition=definition): 490 | return False 491 | return True 492 | 493 | # TODO: list 494 | if isinstance(first_value, list): 495 | raise Exception("Not implemented yet") 496 | 497 | # simple types 498 | # all values must be of that type in both valid and actual response 499 | try: 500 | assert all(isinstance(y, type(first_value)) for _, y in response.items()) 501 | assert all(isinstance(y, type(first_value)) for _, y in valid_response.items()) 502 | return True 503 | except Exception: 504 | return False 505 | 506 | def validate_definition(self, definition_name, dict_to_test, definition=None): 507 | """Validate the given dict according to the given definition. 508 | 509 | Args: 510 | definition_name: name of the the definition. 511 | dict_to_test: dict to test. 512 | 513 | Returns: 514 | True if the given dict match the definition, False otherwise. 515 | """ 516 | if (definition_name not in self.specification['definitions'].keys() and 517 | definition is None): 518 | # reject unknown definition 519 | return False 520 | 521 | # Check all required in dict_to_test 522 | spec_def = definition or self.specification['definitions'][definition_name] 523 | all_required_keys_present = all(req in dict_to_test.keys() for req in spec_def.get('required', {})) 524 | if 'required' in spec_def and not all_required_keys_present: 525 | return False 526 | 527 | # Check no extra arg & type 528 | properties_dict = spec_def.get('properties', {}) 529 | for key, value in dict_to_test.items(): 530 | if value is not None: 531 | if key not in properties_dict: # Extra arg 532 | return False 533 | else: # Check type 534 | if not self._validate_type(properties_dict[key], value): 535 | return False 536 | 537 | return True 538 | 539 | def _validate_type(self, properties_spec, value): 540 | """Validate the given value with the given property spec. 541 | 542 | Args: 543 | properties_dict: specification of the property to check (From definition not route). 544 | value: value to check. 545 | 546 | Returns: 547 | True if the value is valid for the given spec. 548 | """ 549 | if 'type' not in properties_spec.keys(): 550 | # Validate sub definition 551 | def_name = self.get_definition_name_from_ref(properties_spec['$ref']) 552 | return self.validate_definition(def_name, value) 553 | 554 | # Validate array 555 | elif properties_spec['type'] == 'array': 556 | if not isinstance(value, list): 557 | return False 558 | 559 | # Check type 560 | if ('type' in properties_spec['items'].keys() and 561 | any(not self.check_type(item, properties_spec['items']['type']) for item in value)): 562 | return False 563 | # Check ref 564 | elif ('$ref' in properties_spec['items'].keys()): 565 | def_name = self.get_definition_name_from_ref(properties_spec['items']['$ref']) 566 | if any(not self.validate_definition(def_name, item) for item in value): 567 | return False 568 | 569 | else: # Classic types 570 | if not self.check_type(value, properties_spec['type']): 571 | return False 572 | 573 | return True 574 | 575 | def get_paths_data(self): 576 | """Get data for each paths in the swagger specification. 577 | 578 | Get also the list of operationId. 579 | """ 580 | for path, path_spec in self.specification['paths'].items(): 581 | path = u'{0}{1}'.format(self.base_path, path) 582 | self.paths[path] = {} 583 | 584 | # Add path-level parameters 585 | default_parameters = {} 586 | if 'parameters' in path_spec: 587 | self._add_parameters(default_parameters, path_spec['parameters']) 588 | 589 | for http_method in path_spec.keys(): 590 | if http_method not in self._HTTP_VERBS: 591 | continue 592 | 593 | self.paths[path][http_method] = {} 594 | 595 | # Add to operation list 596 | action = path_spec[http_method] 597 | tag = action['tags'][0] if 'tags' in action.keys() and action['tags'] else None 598 | if 'operationId' in action.keys(): 599 | self.operation[action['operationId']] = (path, http_method, tag) 600 | else: 601 | # Note: the encoding chosen below isn't very important in this 602 | # case; what matters is a byte string that is unique. 603 | # URL paths and http methods should encode to UTF-8 safely. 604 | h = hashlib.sha256() 605 | h.update(("{0}|{1}".format(http_method, path)).encode('utf-8')) 606 | self.generated_operation[h.hexdigest()] = (path, http_method, tag) 607 | 608 | # Get parameters 609 | self.paths[path][http_method]['parameters'] = default_parameters.copy() 610 | if 'parameters' in action.keys(): 611 | self._add_parameters(self.paths[path][http_method]['parameters'], action['parameters']) 612 | 613 | # Get responses 614 | self.paths[path][http_method]['responses'] = action['responses'] 615 | 616 | # Get mime types for this action 617 | if 'consumes' in action.keys(): 618 | self.paths[path][http_method]['consumes'] = action['consumes'] 619 | 620 | def _add_parameters(self, parameter_map, parameter_list): 621 | """Populates the given parameter map with the list of parameters provided, resolving any reference objects encountered. 622 | 623 | Args: 624 | parameter_map: mapping from parameter names to parameter objects 625 | parameter_list: list of either parameter objects or reference objects 626 | """ 627 | for parameter in parameter_list: 628 | if parameter.get('$ref'): 629 | # expand parameter from $ref if not specified inline 630 | parameter = self.specification['parameters'].get(parameter.get('$ref').split('/')[-1]) 631 | parameter_map[parameter['name']] = parameter 632 | 633 | @staticmethod 634 | def get_definition_name_from_ref(ref): 635 | """Get the definition name of the given $ref value(Swagger value). 636 | 637 | Args: 638 | ref: ref value (ex: "#/definitions/CustomDefinition") 639 | 640 | Returns: 641 | The definition name corresponding to the ref. 642 | """ 643 | p = re.compile('#/definitions/(.*)') 644 | definition_name = re.sub(p, r'\1', ref) 645 | return definition_name 646 | 647 | def get_path_spec(self, path, action=None): 648 | """Get the specification matching with the given path. 649 | 650 | Args: 651 | path: path we want the specification. 652 | action: get the specification for the given action. 653 | 654 | Returns: 655 | A tuple with the base name of the path and the specification. 656 | Or (None, None) if no specification is found. 657 | """ 658 | # Get the specification of the given path 659 | path_spec = None 660 | path_name = None 661 | for base_path in self.paths.keys(): 662 | if path == base_path: 663 | path_spec = self.paths[base_path] 664 | path_name = base_path 665 | 666 | # Path parameter 667 | if path_spec is None: 668 | for base_path in self.paths.keys(): 669 | regex_from_path = re.compile(re.sub('{[^/]*}', '([^/]*)', base_path) + r'$') 670 | if re.match(regex_from_path, path): 671 | path_spec = self.paths[base_path] 672 | path_name = base_path 673 | 674 | # Test action if given 675 | if path_spec is not None and action is not None: 676 | if action not in path_spec.keys(): 677 | return (None, None) 678 | else: 679 | path_spec = path_spec[action] 680 | 681 | return (path_name, path_spec) 682 | 683 | def validate_request(self, path, action, body=None, query=None): 684 | """Check if the given request is valid. 685 | Validates the body and the query 686 | 687 | # Rules to validate the BODY: 688 | # Let's limit this to mime types that either contain 'text' or 'json' 689 | # 1. if body is None, there must not be any required parameters in 690 | # the given schema 691 | # 2. if the mime type contains 'json', body must not be '', but can 692 | # be {} 693 | # 3. if the mime type contains 'text', body can be any string 694 | # 4. if no mime type ('consumes') is given.. DISALLOW 695 | # 5. if the body is empty ('' or {}), there must not be any required parameters 696 | # 6. if there is something in the body, it must adhere to the given schema 697 | # -> will call the validate body function 698 | 699 | Args: 700 | path: path of the request. 701 | action: action of the request(get, post, delete...). 702 | body: body of the request. 703 | query: dict with the query parameters. 704 | 705 | Returns: 706 | True if the request is valid, False otherwise. 707 | 708 | TODO: 709 | - For every http method, we might want to have some general checks 710 | before we go deeper into the parameters 711 | - Check form data parameters 712 | """ 713 | path_name, path_spec = self.get_path_spec(path) 714 | 715 | if path_spec is None: # reject unknown path 716 | logging.warn("there is no path") 717 | return False 718 | 719 | if action not in path_spec.keys(): # reject unknown http method 720 | logging.warn("this http method is unknown '{0}'".format(action)) 721 | return False 722 | 723 | action_spec = path_spec[action] 724 | 725 | # check general post body guidelines (body + mime type) 726 | if action == 'post': 727 | is_ok, msg = _validate_post_body(body, action_spec) 728 | if not is_ok: 729 | logging.warn("the general post body did not validate due to '{0}'".format(msg)) 730 | return False 731 | 732 | # If the body is empty and it validated so far, we can return here 733 | # unless there is something in the query parameters we need to check 734 | body_is_empty = body in [None, {}, ''] 735 | if body_is_empty and query is None: 736 | return True 737 | 738 | # Check body parameters 739 | is_ok, msg = self._validate_body_parameters(body, action_spec) 740 | if not is_ok: 741 | logging.warn("the parameters in the body did not validate due to '{0}'".format(msg)) 742 | return False 743 | 744 | # Check query parameters 745 | if query is not None and not self._validate_query_parameters(query, action_spec): 746 | return False 747 | 748 | return True 749 | 750 | def _validate_query_parameters(self, query, action_spec): 751 | """Check the query parameter for the action specification. 752 | 753 | Args: 754 | query: query parameter to check. 755 | action_spec: specification of the action. 756 | 757 | Returns: 758 | True if the query is valid. 759 | """ 760 | processed_params = [] 761 | for param_name, param_value in query.items(): 762 | if param_name in action_spec['parameters'].keys(): 763 | processed_params.append(param_name) 764 | 765 | # Check array 766 | if action_spec['parameters'][param_name]['type'] == 'array': 767 | if not isinstance(param_value, list): # Not an array 768 | return False 769 | else: 770 | for i in param_value: # Check type of all elements in array 771 | if not self.check_type(i, action_spec['parameters'][param_name]['items']['type']): 772 | return False 773 | 774 | elif not self.check_type(param_value, action_spec['parameters'][param_name]['type']): 775 | return False 776 | 777 | # Check required 778 | if not all(param in processed_params for param, spec in action_spec['parameters'].items() 779 | if spec['in'] == 'query' and 'required' in spec and spec['required']): 780 | return False 781 | return True 782 | 783 | def _validate_body_parameters(self, body, action_spec): 784 | """Check the body parameter for the action specification. 785 | 786 | Args: 787 | body: body parameter to check. 788 | action_spec: specification of the action. 789 | 790 | Returns: 791 | True if the body is valid. 792 | A string containing an error msg in case the body did not validate, 793 | otherwise the string is empty 794 | """ 795 | processed_params = [] 796 | for param_name, param_spec in action_spec['parameters'].items(): 797 | if param_spec['in'] == 'body': 798 | processed_params.append(param_name) 799 | 800 | # Check type 801 | if 'type' in param_spec.keys() and not self.check_type(body, param_spec['type']): 802 | msg = "Check type did not validate for {0} and {1}".format(param_spec['type'], body) 803 | return False, msg 804 | # Check schema 805 | elif 'schema' in param_spec.keys(): 806 | if 'type' in param_spec['schema'].keys() and param_spec['schema']['type'] == 'array': 807 | # It is an array get value from definition 808 | definition_name = self.get_definition_name_from_ref(param_spec['schema']['items']['$ref']) 809 | if len(body) > 0 and not self.validate_definition(definition_name, body[0]): 810 | msg = "The body did not validate against its definition" 811 | return False, msg 812 | elif ('type' in param_spec['schema'].keys() and not 813 | self.check_type(body, param_spec['schema']['type'])): 814 | # Type but not array 815 | msg = "Check type did not validate for {0} and {1}".format(param_spec['schema']['type'], body) 816 | return False, msg 817 | else: 818 | definition_name = self.get_definition_name_from_ref(param_spec['schema']['$ref']) 819 | if not self.validate_definition(definition_name, body): 820 | msg = "The body did not validate against its definition" 821 | return False, msg 822 | # Check required 823 | if not all(param in processed_params for param, spec in action_spec['parameters'].items() 824 | if spec['in'] == 'body' and 'required' in spec and spec['required']): 825 | msg = "Not all required parameters were present" 826 | return False, msg 827 | 828 | return True, "" 829 | 830 | def get_response_example(self, resp_spec): 831 | """Get a response example from a response spec. 832 | 833 | """ 834 | if 'schema' in resp_spec.keys(): 835 | if '$ref' in resp_spec['schema']: # Standard definition 836 | definition_name = self.get_definition_name_from_ref(resp_spec['schema']['$ref']) 837 | return self.definitions_example[definition_name] 838 | elif 'items' in resp_spec['schema'] and resp_spec['schema']['type'] == 'array': # Array 839 | if '$ref' in resp_spec['schema']['items']: 840 | definition_name = self.get_definition_name_from_ref(resp_spec['schema']['items']['$ref']) 841 | else: 842 | if 'type' in resp_spec['schema']['items']: 843 | definition_name = self.get_definition_name_from_ref(resp_spec['schema']['items']) 844 | return [definition_name] 845 | else: 846 | logging.warn("No item type in: " + resp_spec['schema']) 847 | return '' 848 | return [self.definitions_example[definition_name]] 849 | elif 'type' in resp_spec['schema']: 850 | return self.get_example_from_prop_spec(resp_spec['schema']) 851 | else: 852 | return '' 853 | 854 | def get_request_data(self, path, action, body=None): 855 | """Get the default data and status code of the given path + action request. 856 | 857 | Args: 858 | path: path of the request. 859 | action: action of the request(get, post, delete...) 860 | body: body sent, used to sent it back for post request. 861 | 862 | Returns: 863 | A tuple with the default response data and status code 864 | In case of default status_code, use 0 865 | """ 866 | body = body or '' 867 | path_name, path_spec = self.get_path_spec(path) 868 | response = {} 869 | 870 | # Get all status code 871 | if path_spec is not None and action in path_spec.keys(): 872 | for status_code in path_spec[action]['responses'].keys(): 873 | resp = path_spec[action]['responses'][status_code] 874 | try: 875 | response[int(status_code)] = self.get_response_example(resp) 876 | except ValueError: 877 | response[status_code] = self.get_response_example(resp) 878 | 879 | # If there is no status_code add a default 400 880 | if response == {}: 881 | response[400] = '' 882 | return response 883 | 884 | def get_send_request_correct_body(self, path, action): 885 | """Get an example body which is correct to send to the given path with the given action. 886 | 887 | Args: 888 | path: path of the request 889 | action: action of the request (get, post, put, delete) 890 | 891 | Returns: 892 | A dict representing a correct body for the request or None if no 893 | body is required. 894 | """ 895 | path_name, path_spec = self.get_path_spec(path) 896 | 897 | if path_spec is not None and action in path_spec.keys(): 898 | for name, spec in path_spec[action]['parameters'].items(): 899 | if spec['in'] == 'body': # Get body parameter 900 | if 'type' in spec.keys(): 901 | # Get value from type 902 | return self.get_example_from_prop_spec(spec) 903 | elif 'schema' in spec.keys(): 904 | if 'type' in spec['schema'].keys() and spec['schema']['type'] == 'array': 905 | # It is an array 906 | # Get value from definition 907 | if '$ref' in spec['schema']['items']: 908 | definition_name = self.get_definition_name_from_ref(spec['schema'] 909 | ['items']['$ref']) 910 | return [self.definitions_example[definition_name]] 911 | else: 912 | definition_name = self.get_definition_name_from_ref(spec['schema'] 913 | ['items']['type']) 914 | return [definition_name] 915 | elif 'type' in spec['schema'].keys(): 916 | # Type but not array 917 | return self.get_example_from_prop_spec(spec['schema']) 918 | else: 919 | # Get value from definition 920 | definition_name = self.get_definition_name_from_ref(spec['schema']['$ref']) 921 | return self.definitions_example[definition_name] 922 | 923 | 924 | def _validate_post_body(actual_request_body, body_specification): 925 | """ returns a tuple (boolean, msg) 926 | to indicate whether the validation passed 927 | if False then msg contains the reason 928 | if True then msg is empty 929 | """ 930 | 931 | # If no body specified, return True (POST with empty body is allowed): 932 | if "body" not in body_specification["parameters"]: 933 | return True, "" 934 | 935 | # Are there required parameters? - there is only ONE body, so we check that one 936 | parameters_required = body_specification['parameters']['body']['required'] 937 | 938 | # What if it says 'required' but there is no schema ? - we reject it 939 | schema_present = body_specification['parameters']['body'].get('schema') 940 | if parameters_required and not schema_present: 941 | msg = "there is no schema given, but it says there are required parameters" 942 | return False, msg 943 | 944 | # What is the mime type ? 945 | text_is_accepted = any('text' in item for item in body_specification.get('consumes', [])) 946 | json_is_accepted = any('json' in item for item in body_specification.get('consumes', [])) 947 | 948 | if actual_request_body == '' and not text_is_accepted: 949 | msg = "post body is an empty string, but text is not an accepted mime type" 950 | return False, msg 951 | 952 | if actual_request_body == {} and not json_is_accepted: 953 | msg = "post body is an empty dict, but json is not an accepted mime type" 954 | return False, msg 955 | 956 | # If only json is accepted, but the body is a string, we transform the 957 | # string to json and check it then (not sure if the server would accept 958 | # that string, though) 959 | if (json_is_accepted and not 960 | text_is_accepted and 961 | type(actual_request_body).__name__ == 'str'): 962 | actual_request_body = json.loads(actual_request_body) 963 | 964 | # Handle empty body 965 | body_is_empty = actual_request_body in [None, '', {}] 966 | if body_is_empty: 967 | if parameters_required: 968 | msg = "there is no body, but it says there are required parameters" 969 | return False, msg 970 | else: 971 | return True, "" 972 | 973 | return True, "" 974 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import pytest 5 | 6 | from swagger_parser import SwaggerParser 7 | 8 | 9 | @pytest.fixture 10 | def swagger_parser(): 11 | return SwaggerParser(os.path.join(os.path.dirname(__file__), 'files', 'swagger.yaml')) 12 | 13 | 14 | @pytest.fixture 15 | def swagger_allof_parser(): 16 | return SwaggerParser(os.path.join(os.path.dirname(__file__), 'files', 'allof.yaml')) 17 | 18 | 19 | @pytest.fixture 20 | def swagger_test2_parser(): 21 | return SwaggerParser(os.path.join(os.path.dirname(__file__), 'files', 'test.yaml')) 22 | 23 | 24 | @pytest.fixture 25 | def inline_parser(): 26 | return SwaggerParser(os.path.join(os.path.dirname(__file__), 'files', 'inline.yaml')) 27 | 28 | 29 | @pytest.fixture(scope="module", 30 | params=[os.path.join(os.path.dirname(__file__), 'files', 'no_properties.yaml'), 31 | os.path.join(os.path.dirname(__file__), 'files', 'object_no_schema.yaml'), 32 | os.path.join(os.path.dirname(__file__), 'files', 'allof.yaml'), 33 | os.path.join(os.path.dirname(__file__), 'files', 'array_ref_simple.yaml'), 34 | os.path.join(os.path.dirname(__file__), 'files', 'null_type.yaml'), 35 | os.path.join(os.path.dirname(__file__), 'files', 'array_items_list.yaml'), 36 | os.path.join(os.path.dirname(__file__), 'files', 'type_list.yaml'), 37 | ]) 38 | def swagger_file_parser(request): 39 | return SwaggerParser(request.param) 40 | 41 | 42 | @pytest.fixture 43 | def pet_definition_example(): 44 | return { 45 | 'category': { 46 | 'id': 42, 47 | 'name': 'string' 48 | }, 49 | 'status': 'string', 50 | 'name': 'doggie', 51 | 'tags': [ 52 | { 53 | 'id': 42, 54 | 'name': 'string' 55 | } 56 | ], 57 | 'photoUrls': [ 58 | 'string', 59 | 'string2' 60 | ], 61 | 'id': 42 62 | } 63 | 64 | 65 | @pytest.fixture 66 | def inline_example(): 67 | return { 68 | '3bd818fb7d55daf2fb8bf3354c061f9ba7f8cece39b30bdcb7e05551053ec2e8': ('/test', 'post', None) 69 | } 70 | 71 | 72 | @pytest.fixture 73 | def get_path_data(): 74 | pet_get = { 75 | 'description': "Pet's Unique identifier", 76 | 'name': 'petId', 77 | 'in': 'path', 78 | 'pattern': '^[a-zA-Z0-9-]+$', 79 | 'required': True, 80 | 'type': 'string', 81 | } 82 | get_responses = { 83 | '200': { 84 | 'description': u'successful \xb5-\xf8per\xe4tio\xf1', 85 | 'schema': { 86 | '$ref': '#/definitions/Pet', 87 | 'x-scope': [''] 88 | } 89 | }, 90 | '400': {'description': 'Invalid ID supplied'}, 91 | '404': {'description': 'Pet not found'} 92 | } 93 | expected_get_pet_path = { 94 | 'parameters': {'petId': pet_get}, 95 | 'responses': get_responses 96 | } 97 | return expected_get_pet_path 98 | 99 | 100 | @pytest.fixture 101 | def post_put_path_data(): 102 | pet_post = { 103 | 'description': u'Pet object that needs to be added to the store (it may be a \xb5Pig or a Sm\xf8rebr\xf6d)', 104 | 'in': 'body', 105 | 'name': 'body', 106 | 'required': False, 107 | 'schema': { 108 | '$ref': '#/definitions/Pet', 109 | 'x-scope': [''] 110 | } 111 | } 112 | pet_put = pet_post.copy() 113 | pet_put['description'] = 'Pet object that needs to be added to the store' 114 | schema_created = { 115 | 'description': 'Created', 116 | 'schema': { 117 | '$ref': '#/definitions/Pet', 118 | 'x-scope': [''] 119 | } 120 | } 121 | expected_post_put_paths = { 122 | 'post': { 123 | 'consumes': ['application/json'], 124 | 'parameters': {'body': pet_post}, 125 | 'responses': { 126 | '201': schema_created, 127 | '405': {'description': 'Invalid input'} 128 | } 129 | }, 130 | 'put': { 131 | 'consumes': ['application/json'], 132 | 'parameters': {'body': pet_put}, 133 | 'responses': { 134 | '200': schema_created, 135 | '400': {'description': 'Invalid ID supplied'}, 136 | '404': {'description': 'Pet not found'}, 137 | '405': {'description': 'Validation exception'} 138 | } 139 | } 140 | } 141 | return expected_post_put_paths 142 | 143 | 144 | @pytest.fixture 145 | def swagger_array_parser(): 146 | return SwaggerParser(os.path.join(os.path.dirname(__file__), 'files', 'swagger_arrays.yaml')) 147 | -------------------------------------------------------------------------------- /tests/files/allof.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | version: '2016-01-19T11:24:50Z' 4 | title: authentication tests 5 | schemes: 6 | - http 7 | paths: 8 | /test: 9 | get: 10 | description: Get something 11 | responses: 12 | '200': 13 | description: OK 14 | schema: 15 | $ref: '#/definitions/someObject' 16 | definitions: 17 | baseObject: 18 | description: base object 19 | properties: 20 | a_property: 21 | description: a property 22 | type: string 23 | anotherBaseObject: 24 | description: another base object 25 | properties: 26 | another_property: 27 | description: another property 28 | type: string 29 | some_integer: 30 | description: an integer 31 | type: integer 32 | someObject: 33 | description: some object 34 | allOf: 35 | - $ref: '#/definitions/baseObject' 36 | - type: object 37 | properties: 38 | some_property: 39 | description: some property 40 | type: string 41 | required: 42 | - some_property 43 | anotherObject: 44 | description: another object 45 | allOf: 46 | - $ref: '#/definitions/baseObject' 47 | - $ref: '#/definitions/anotherBaseObject' 48 | -------------------------------------------------------------------------------- /tests/files/array_items_list.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | version: '2016-01-19T11:24:50Z' 4 | title: authentication tests 5 | schemes: 6 | - http 7 | paths: 8 | /test: 9 | get: 10 | description: Get something 11 | responses: 12 | '200': 13 | description: OK 14 | schema: 15 | $ref: '#/definitions/object_definition' 16 | definitions: 17 | object_definition: 18 | properties: 19 | an_array: 20 | items: 21 | type: number 22 | type: array 23 | type: object 24 | -------------------------------------------------------------------------------- /tests/files/array_ref_simple.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | version: '2016-01-19T11:24:50Z' 4 | title: authentication tests 5 | schemes: 6 | - http 7 | paths: 8 | /test: 9 | get: 10 | description: Get something 11 | responses: 12 | '200': 13 | description: OK 14 | schema: 15 | $ref: '#/definitions/object_definition' 16 | definitions: 17 | simple_definition: 18 | type: string 19 | object_definition: 20 | properties: 21 | an_array: 22 | items: 23 | $ref: '#/definitions/simple_definition' 24 | type: array 25 | type: object 26 | -------------------------------------------------------------------------------- /tests/files/inline.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | version: '2016-01-19T11:24:50Z' 4 | title: authentication tests 5 | schemes: 6 | - http 7 | paths: 8 | /test: 9 | post: 10 | summary: Post something complex 11 | produces: 12 | - application/json 13 | consumes: 14 | - application/json 15 | parameters: 16 | - name: complex 17 | in: body 18 | required: true 19 | schema: 20 | type: object 21 | properties: 22 | foo: 23 | type: object 24 | properties: 25 | bar: 26 | type: string 27 | baz: 28 | type: integer 29 | required: 30 | - bar 31 | - baz 32 | required: 33 | - foo 34 | example: 35 | foo: 36 | bar: 'hello' 37 | baz: 123 38 | responses: 39 | '201': 40 | description: Created 41 | default: 42 | description: Not created 43 | 44 | -------------------------------------------------------------------------------- /tests/files/no_properties.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | version: '2016-01-19T11:24:50Z' 4 | title: authentication tests 5 | schemes: 6 | - http 7 | paths: 8 | /test: 9 | get: 10 | description: Get something 11 | responses: 12 | '200': 13 | description: OK 14 | schema: 15 | $ref: '#/definitions/someObject' 16 | definitions: 17 | baseObject: 18 | type: object 19 | additionalProperties: false 20 | properties: 21 | info: 22 | type: object 23 | someObject: 24 | type: object 25 | additionalProperties: 26 | $ref: '#/definitions/baseObject' 27 | -------------------------------------------------------------------------------- /tests/files/null_type.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | version: '2016-01-19T11:24:50Z' 4 | title: authentication tests 5 | schemes: 6 | - http 7 | paths: 8 | /test: 9 | get: 10 | description: Get something 11 | responses: 12 | '200': 13 | description: OK 14 | schema: 15 | $ref: '#/definitions/null_definition' 16 | definitions: 17 | null_definition: 18 | type: 'null' 19 | -------------------------------------------------------------------------------- /tests/files/object_no_schema.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | version: '2016-01-19T11:24:50Z' 4 | title: authentication tests 5 | schemes: 6 | - http 7 | paths: 8 | /test: 9 | get: 10 | description: Get something 11 | responses: 12 | '200': 13 | description: OK 14 | schema: 15 | $ref: '#/definitions/someObject' 16 | definitions: 17 | someObject: 18 | description: some object 19 | properties: 20 | inner_object: 21 | description: an inner object 22 | type: object -------------------------------------------------------------------------------- /tests/files/swagger.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | description: | 4 | This is a sample server Petstore server. 5 | 6 | [Learn about Swagger](http://swagger.wordnik.com) or join the IRC channel `#swagger` on irc.freenode.net. 7 | 8 | For this sample, you can use the api key `special-key` to test the authorization filters 9 | 10 | Swagger files may contain non-ascii characters like µ, ü or ç and ñ, please save your file in UTF-8 to preserve them. 11 | 12 | version: "1.0.0" 13 | title: Swagger Petstore 14 | termsOfService: http://helloreverb.com/terms/ 15 | contact: 16 | name: apiteam@wordnik.com 17 | license: 18 | name: Apache 2.0 19 | url: http://www.apache.org/licenses/LICENSE-2.0.html 20 | basePath: /v2 21 | schemes: 22 | - http 23 | paths: 24 | /store/inventory: 25 | get: 26 | tags: 27 | - store 28 | summary: Returns pet inventories by status 29 | description: Returns a map of status codes to quantities 30 | operationId: getInventory 31 | produces: 32 | - application/json 33 | parameters: [] 34 | responses: 35 | "200": 36 | description: successful operation 37 | schema: 38 | type: object 39 | additionalProperties: 40 | type: integer 41 | format: int32 42 | /pets: 43 | post: 44 | tags: 45 | - pet 46 | summary: Add a new pet to the store 47 | description: "" 48 | operationId: examples.api.api.addPet 49 | consumes: 50 | - application/json 51 | produces: 52 | - application/json 53 | parameters: 54 | - in: body 55 | name: body 56 | description: Pet object that needs to be added to the store (it may be a µPig or a Smørebröd) 57 | required: false 58 | schema: 59 | $ref: "#/definitions/Pet" 60 | responses: 61 | "201": 62 | description: Created 63 | schema: 64 | $ref: "#/definitions/Pet" 65 | "405": 66 | description: Invalid input 67 | security: 68 | - petstore_auth: 69 | - write_pets 70 | - read_pets 71 | put: 72 | tags: 73 | - pet 74 | summary: Update an existing pet 75 | description: "" 76 | operationId: examples.api.api.updatePet 77 | consumes: 78 | - application/json 79 | produces: 80 | - application/json 81 | parameters: 82 | - in: body 83 | name: body 84 | description: Pet object that needs to be added to the store 85 | required: false 86 | schema: 87 | $ref: "#/definitions/Pet" 88 | responses: 89 | "200": 90 | description: Created 91 | schema: 92 | $ref: "#/definitions/Pet" 93 | "405": 94 | description: Validation exception 95 | "404": 96 | description: Pet not found 97 | "400": 98 | description: Invalid ID supplied 99 | security: 100 | - petstore_auth: 101 | - write_pets 102 | - read_pets 103 | /pets/findByStatus: 104 | get: 105 | tags: 106 | - pet 107 | summary: Finds Pets by status 108 | description: Multiple status values can be provided with comma seperated strings 109 | operationId: examples.api.api.findPetsByStatus 110 | produces: 111 | - application/json 112 | parameters: 113 | - in: query 114 | name: status 115 | description: Status values that need to be considered for filter 116 | required: false 117 | type: array 118 | items: 119 | type: string 120 | collectionFormat: multi 121 | responses: 122 | "200": 123 | description: successful operation 124 | schema: 125 | type: array 126 | items: 127 | $ref: "#/definitions/Pet" 128 | "400": 129 | description: Invalid status value 130 | security: 131 | - petstore_auth: 132 | - write_pets 133 | - read_pets 134 | /pets/findByTags: 135 | get: 136 | tags: 137 | - pet 138 | summary: Finds Pets by tags 139 | description: Muliple tags can be provided with comma seperated strings. Use tag1, tag2, tag3 for testing. 140 | operationId: examples.api.api.findPetsByTags 141 | produces: 142 | - application/json 143 | parameters: 144 | - in: query 145 | name: tags 146 | description: Tags to filter by 147 | required: false 148 | type: array 149 | items: 150 | type: string 151 | collectionFormat: multi 152 | responses: 153 | "200": 154 | description: successful operation 155 | schema: 156 | type: array 157 | items: 158 | $ref: "#/definitions/Pet" 159 | "400": 160 | description: Invalid tag value 161 | security: 162 | - petstore_auth: 163 | - write_pets 164 | - read_pets 165 | /pets/{petId}: 166 | get: 167 | tags: 168 | - pet 169 | summary: Find pet by ID 170 | description: Returns a pet when ID < 10. ID > 10 or nonintegers will simulate API error conditions 171 | operationId: examples.api.api.getPetById 172 | produces: 173 | - application/json 174 | responses: 175 | "404": 176 | description: Pet not found 177 | "200": 178 | description: successful µ-øperätioñ 179 | schema: 180 | $ref: "#/definitions/Pet" 181 | "400": 182 | description: Invalid ID supplied 183 | security: 184 | - api_key: [] 185 | - petstore_auth: 186 | - write_pets 187 | - read_pets 188 | post: 189 | tags: 190 | - pet 191 | summary: Updates a pet in the store with form data 192 | description: "" 193 | operationId: examples.api.api.updatePetWithForm 194 | consumes: 195 | - application/x-www-form-urlencoded 196 | produces: 197 | - application/json 198 | parameters: 199 | - in: query 200 | name: name 201 | description: Updated name of the pet 202 | required: true 203 | type: string 204 | - in: query 205 | name: status 206 | description: Updated status of the pet 207 | required: true 208 | type: string 209 | responses: 210 | "201": 211 | description: Created 212 | schema: 213 | $ref: "#/definitions/Pet" 214 | "405": 215 | description: Invalid input 216 | security: 217 | - petstore_auth: 218 | - write_pets 219 | - read_pets 220 | delete: 221 | tags: 222 | - pet 223 | summary: Deletes a pet 224 | description: "" 225 | operationId: examples.api.api.deletePet 226 | produces: 227 | - application/json 228 | responses: 229 | "204": 230 | description: Deleted 231 | "400": 232 | description: Invalid pet value 233 | security: 234 | - petstore_auth: 235 | - write_pets 236 | - read_pets 237 | parameters: 238 | - $ref: '#/parameters/pet_id' 239 | /stores/order: 240 | post: 241 | tags: 242 | - store 243 | summary: Place an order for a pet 244 | description: "" 245 | operationId: examples.api.api.placeOrder 246 | produces: 247 | - application/json 248 | parameters: 249 | - in: body 250 | name: body 251 | description: order placed for purchasing the pet 252 | required: false 253 | schema: 254 | $ref: "#/definitions/Order" 255 | responses: 256 | "200": 257 | description: successful operation 258 | schema: 259 | $ref: "#/definitions/Order" 260 | "400": 261 | description: Invalid Order 262 | /stores/order/{orderId}: 263 | get: 264 | tags: 265 | - store 266 | summary: Find purchase order by ID 267 | description: For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions 268 | operationId: examples.api.api.getOrderById 269 | produces: 270 | - application/json 271 | parameters: 272 | - in: path 273 | name: orderId 274 | description: ID of pet that needs to be fetched 275 | required: true 276 | type: string 277 | responses: 278 | "404": 279 | description: Order not found 280 | "200": 281 | description: successful operation 282 | schema: 283 | $ref: "#/definitions/Order" 284 | "400": 285 | description: Invalid ID supplied 286 | delete: 287 | tags: 288 | - store 289 | summary: Delete purchase order by ID 290 | description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors 291 | operationId: examples.api.api.deleteOrder 292 | produces: 293 | - application/json 294 | parameters: 295 | - in: path 296 | name: orderId 297 | description: ID of the order that needs to be deleted 298 | required: true 299 | type: string 300 | responses: 301 | "204": 302 | description: Deleted 303 | "404": 304 | description: Order not found 305 | "400": 306 | description: Invalid ID supplied 307 | /users: 308 | post: 309 | tags: 310 | - user 311 | summary: Create user 312 | description: This can only be done by the logged in user. 313 | operationId: examples.api.api.createUser 314 | produces: 315 | - application/json 316 | parameters: 317 | - in: body 318 | name: body 319 | description: Created user object 320 | required: false 321 | schema: 322 | $ref: "#/definitions/User" 323 | responses: 324 | "201": 325 | description: successful operation 326 | /users/createWithArray: 327 | post: 328 | tags: 329 | - user 330 | summary: Creates list of users with given input array 331 | description: "" 332 | operationId: examples.api.api.createUsersWithArrayInput 333 | produces: 334 | - application/json 335 | parameters: 336 | - in: body 337 | name: body 338 | description: List of user object 339 | required: false 340 | schema: 341 | type: array 342 | items: 343 | $ref: "#/definitions/User" 344 | responses: 345 | "201": 346 | description: successful operation 347 | /users/createWithList: 348 | post: 349 | tags: 350 | - user 351 | summary: Creates list of users with given input array 352 | description: "" 353 | operationId: examples.api.api.createUsersWithListInput 354 | produces: 355 | - application/json 356 | parameters: 357 | - in: body 358 | name: body 359 | description: List of user object 360 | required: false 361 | schema: 362 | type: array 363 | items: 364 | $ref: "#/definitions/User" 365 | responses: 366 | "201": 367 | description: successful operation 368 | /users/login: 369 | get: 370 | tags: 371 | - user 372 | summary: Logs user into the system 373 | description: "" 374 | operationId: examples.api.api.loginUser 375 | produces: 376 | - application/json 377 | parameters: 378 | - in: query 379 | name: username 380 | description: The user name for login 381 | required: false 382 | type: string 383 | - in: query 384 | name: password 385 | description: The password for login in clear text 386 | required: false 387 | type: string 388 | responses: 389 | "200": 390 | description: successful operation 391 | "400": 392 | description: Invalid username/password supplied 393 | /users/logout: 394 | get: 395 | tags: 396 | - user 397 | summary: Logs out current logged in user session 398 | description: "" 399 | operationId: examples.api.api.logoutUser 400 | produces: 401 | - application/json 402 | responses: 403 | "200": 404 | description: successful operation 405 | /users/{username}: 406 | get: 407 | tags: 408 | - user 409 | summary: Get user by user name 410 | description: "" 411 | operationId: examples.api.api.getUserByName 412 | produces: 413 | - application/json 414 | parameters: 415 | - in: path 416 | name: username 417 | description: The name that needs to be fetched. Use user1 for testing. 418 | required: true 419 | type: string 420 | responses: 421 | "404": 422 | description: User not found 423 | "200": 424 | description: successful operation 425 | schema: 426 | $ref: "#/definitions/User" 427 | "400": 428 | description: Invalid username supplied 429 | put: 430 | summary: Updated user 431 | description: This can only be done by the logged in user. 432 | operationId: examples.api.api.updateUser 433 | produces: 434 | - application/json 435 | parameters: 436 | - in: path 437 | name: username 438 | description: name that need to be deleted 439 | required: true 440 | type: string 441 | - in: body 442 | name: body 443 | description: Updated user object 444 | required: false 445 | schema: 446 | type: string 447 | responses: 448 | "200": 449 | description: Created 450 | schema: 451 | $ref: "#/definitions/Pet" 452 | "404": 453 | description: User not found 454 | "400": 455 | description: Invalid user supplied 456 | delete: 457 | tags: [] 458 | summary: Delete user 459 | description: This can only be done by the logged in user. 460 | operationId: examples.api.api.deleteUser 461 | produces: 462 | - application/json 463 | parameters: 464 | - in: path 465 | name: username 466 | description: The name that needs to be deleted 467 | required: true 468 | type: string 469 | responses: 470 | "204": 471 | description: Deleted 472 | "404": 473 | description: User not found 474 | "400": 475 | description: Invalid username supplied 476 | securityDefinitions: 477 | api_key: 478 | type: apiKey 479 | name: api_key 480 | in: header 481 | petstore_auth: 482 | type: oauth2 483 | authorizationUrl: http://petstore.swagger.wordnik.com/api/oauth/dialog 484 | flow: implicit 485 | scopes: 486 | write_pets: modify pets in your account 487 | read_pets: read your pets 488 | 489 | parameters: 490 | pet_id: 491 | name: petId 492 | description: Pet's Unique identifier 493 | in: path 494 | type: string 495 | required: true 496 | pattern: "^[a-zA-Z0-9-]+$" 497 | 498 | definitions: 499 | User: 500 | type: object 501 | properties: 502 | id: 503 | type: integer 504 | format: int64 505 | username: 506 | type: string 507 | firstName: 508 | type: string 509 | lastName: 510 | type: string 511 | email: 512 | type: string 513 | password: 514 | type: string 515 | phone: 516 | type: string 517 | userStatus: 518 | type: integer 519 | format: int32 520 | description: User Status 521 | Category: 522 | type: object 523 | properties: 524 | id: 525 | type: integer 526 | format: int64 527 | name: 528 | type: string 529 | Pet: 530 | type: object 531 | required: 532 | - name 533 | - photoUrls 534 | properties: 535 | id: 536 | type: integer 537 | format: int64 538 | category: 539 | $ref: "#/definitions/Category" 540 | name: 541 | type: string 542 | example: doggie 543 | photoUrls: 544 | type: array 545 | items: 546 | type: string 547 | tags: 548 | type: array 549 | items: 550 | $ref: "#/definitions/Tag" 551 | status: 552 | type: string 553 | description: pet status in the store 554 | Tag: 555 | type: object 556 | properties: 557 | id: 558 | type: integer 559 | format: int64 560 | name: 561 | type: string 562 | Order: 563 | type: object 564 | properties: 565 | id: 566 | type: integer 567 | format: int64 568 | petId: 569 | type: integer 570 | format: int64 571 | quantity: 572 | type: integer 573 | format: int32 574 | shipDate: 575 | type: string 576 | format: string 577 | status: 578 | type: string 579 | description: Order Status 580 | complete: 581 | type: boolean 582 | -------------------------------------------------------------------------------- /tests/files/swagger_arrays.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | swagger: "2.0" 3 | info: 4 | version: "0.0.1" 5 | title: "Test array definition" 6 | paths: 7 | /path1: 8 | get: 9 | responses: 10 | "200": 11 | description: "Response contains string of arrays" 12 | schema: 13 | $ref: "#/definitions/StringArray" 14 | /path2: 15 | get: 16 | responses: 17 | "200": 18 | description: "Response contains string of arrays" 19 | schema: 20 | $ref: "#/definitions/WidgetArray" 21 | 22 | definitions: 23 | StringArray: 24 | type: "array" 25 | items: 26 | type: "string" 27 | WidgetArray: 28 | type: "array" 29 | items: 30 | $ref: "#/definitions/Widget" 31 | Widget: 32 | type: "object" 33 | properties: 34 | prop1: 35 | type: string 36 | prop2: 37 | type: string 38 | -------------------------------------------------------------------------------- /tests/files/type_list.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | version: '2016-01-19T11:24:50Z' 4 | title: authentication tests 5 | schemes: 6 | - http 7 | paths: 8 | /test: 9 | get: 10 | description: Get something 11 | responses: 12 | '200': 13 | description: OK 14 | schema: 15 | $ref: '#/definitions/a_definition' 16 | definitions: 17 | a_definition: 18 | items: {} 19 | type: 'null' 20 | 21 | -------------------------------------------------------------------------------- /tests/test_swagger_parser.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import pytest 4 | import requests 5 | 6 | from copy import deepcopy 7 | 8 | from swagger_parser import SwaggerParser 9 | 10 | 11 | # whatever is defined in the petstore, we should be able to parse the json 12 | def test_validate_petstore_swagger_json(): 13 | complete_json = requests.get("http://petstore.swagger.io/v2/swagger.json").json() 14 | SwaggerParser(swagger_dict=complete_json, use_example=True) 15 | SwaggerParser(swagger_dict=complete_json, use_example=False) 16 | 17 | 18 | # whatever is defined in the petstore, we should be able to parse the yaml 19 | def test_validate_petstore_swagger_yaml(): 20 | complete_yaml = requests.get("http://petstore.swagger.io/v2/swagger.yaml").text 21 | SwaggerParser(swagger_yaml=complete_yaml, use_example=True) 22 | SwaggerParser(swagger_yaml=complete_yaml, use_example=False) 23 | 24 | 25 | def test_inline_examples(inline_parser, inline_example): 26 | assert inline_parser.generated_operation == inline_example 27 | 28 | 29 | def test_swagger_file_parser(swagger_file_parser): 30 | assert swagger_file_parser 31 | 32 | 33 | def test_build_definitions_example(swagger_parser, pet_definition_example): 34 | # Test definitions_example 35 | swagger_parser.build_definitions_example() 36 | assert len(swagger_parser.definitions_example) == 5 37 | assert swagger_parser.definitions_example['Pet'] == pet_definition_example 38 | 39 | # Test wrong definition 40 | swagger_parser.specification['definitions']['Pet']['properties']['category']['$ref'] = '#/definitions/Error' 41 | del swagger_parser.definitions_example['Pet'] 42 | assert not swagger_parser.build_one_definition_example('Pet') 43 | 44 | # Test wrong def name 45 | assert not swagger_parser.build_one_definition_example('Error') 46 | 47 | 48 | def test_check_type(swagger_parser): 49 | # Test int 50 | assert swagger_parser.check_type(int(5), 'integer') 51 | assert swagger_parser.check_type(int(5), 'number') 52 | assert swagger_parser.check_type('5', 'integer') 53 | assert not swagger_parser.check_type(int(5), 'string') 54 | assert not swagger_parser.check_type(int(5), 'boolean') 55 | 56 | # Test float 57 | assert swagger_parser.check_type(5.5, 'number') 58 | assert not swagger_parser.check_type(5.5, 'string') 59 | assert not swagger_parser.check_type(5.5, 'boolean') 60 | 61 | # Test string 62 | assert not swagger_parser.check_type('test', 'integer') 63 | assert not swagger_parser.check_type('test', 'number') 64 | assert swagger_parser.check_type('test', 'string') 65 | assert not swagger_parser.check_type('test', 'boolean') 66 | 67 | # Test boolean 68 | assert not swagger_parser.check_type(False, 'number') 69 | assert not swagger_parser.check_type(False, 'string') 70 | assert swagger_parser.check_type(False, 'boolean') 71 | 72 | # Test other 73 | assert not swagger_parser.check_type(swagger_parser, 'string') 74 | 75 | 76 | def test_get_example_from_prop_spec(swagger_parser): 77 | prop_spec = {} 78 | 79 | # Primitive types 80 | prop_spec['type'] = 'integer' 81 | assert swagger_parser.get_example_from_prop_spec(prop_spec) == 42 82 | prop_spec['type'] = 'number' 83 | assert swagger_parser.get_example_from_prop_spec(prop_spec) == 5.5 84 | prop_spec['type'] = 'string' 85 | assert swagger_parser.get_example_from_prop_spec(prop_spec) == 'string' 86 | prop_spec['type'] = 'boolean' 87 | assert not swagger_parser.get_example_from_prop_spec(prop_spec) 88 | 89 | # Array 90 | prop_spec['type'] = 'array' 91 | prop_spec['items'] = {} 92 | 93 | # Primitive types 94 | prop_spec['items']['type'] = 'integer' 95 | assert swagger_parser.get_example_from_prop_spec(prop_spec) == [42, 24] 96 | prop_spec['items']['type'] = 'number' 97 | assert swagger_parser.get_example_from_prop_spec(prop_spec) == [5.5, 5.5] 98 | prop_spec['items']['type'] = 'string' 99 | assert swagger_parser.get_example_from_prop_spec(prop_spec) == ['string', 'string2'] 100 | prop_spec['items']['type'] = 'boolean' 101 | assert swagger_parser.get_example_from_prop_spec(prop_spec) == [False, True] 102 | 103 | # definition 104 | del prop_spec['items']['type'] 105 | prop_spec['items']['$ref'] = '#/definitions/Tag' 106 | assert swagger_parser.get_example_from_prop_spec(prop_spec) == [{'id': 42, 'name': 'string'}] 107 | 108 | # Inline complex 109 | prop_spec = { 110 | 'type': 'object', 111 | 'properties': { 112 | 'error': { 113 | 'type': 'object', 114 | 'properties': { 115 | 'code': {'type': 'string'}, 116 | 'title': {'type': 'string'}, 117 | 'detail': {'type': 'string'}, 118 | }, 119 | 'required': ['code', 'title', 'detail'], 120 | }, 121 | }, 122 | 'required': ['error'], 123 | } 124 | example = swagger_parser.get_example_from_prop_spec(prop_spec) 125 | assert example == [{'error': {'code': 'string', 'detail': 'string', 'title': 'string'}}] 126 | 127 | 128 | def test_get_example_from_prop_spec_with_additional_properties(swagger_parser): 129 | prop_spec = { 130 | 'type': 'object', 131 | 'properties': { 132 | 'error': { 133 | 'type': 'object', 134 | 'properties': { 135 | 'code': {'type': 'string'}, 136 | 'title': {'type': 'string'}, 137 | 'detail': {'type': 'string'}, 138 | }, 139 | 'required': ['code', 'title', 'detail'], 140 | }, 141 | }, 142 | 'required': ['error'], 143 | } 144 | 145 | # additionalProperties - $ref (complex prop_spec with required keys) 146 | prop_spec['additionalProperties'] = {'$ref': '#/definitions/Category'} 147 | example = swagger_parser.get_example_from_prop_spec(prop_spec) 148 | assert example == { 149 | 'any_prop2': {'id': 42, 'name': 'string'}, 150 | 'any_prop1': {'id': 42, 'name': 'string'}, 151 | 'error': {'code': 'string', 'detail': 'string', 'title': 'string'}, 152 | } 153 | 154 | # additionalProperties - string (with complex prop_spec without required keys) 155 | del prop_spec['required'] 156 | prop_spec['additionalProperties'] = {'type': 'string'} 157 | example = swagger_parser.get_example_from_prop_spec(prop_spec) 158 | assert example == { 159 | 'any_prop2': 'string', 160 | 'any_prop1': 'string', 161 | } 162 | 163 | # additionalProperties - integer (prop spec with only additional properties) 164 | easy_prop_spec = { 165 | 'type': 'object', 166 | 'additionalProperties': {'type': 'integer', 'format': 'int64'}, 167 | } 168 | example = swagger_parser.get_example_from_prop_spec(easy_prop_spec) 169 | assert example == {'any_prop1': 42, 'any_prop2': 42} 170 | 171 | # additionalProperties - dict not satisfying any definition 172 | # (with complex prop_spec without required keys) 173 | prop_spec['additionalProperties'] = { 174 | 'type': 'object', 175 | 'properties': { 176 | 'food': {'type': 'string'}, 177 | 'drink': {'type': 'number', 'format': 'double'}, 178 | 'movies': {'type': 'boolean'}, 179 | } 180 | } 181 | example = swagger_parser.get_example_from_prop_spec(prop_spec) 182 | assert example == { 183 | 'any_prop2': {'food': 'string', 'movies': False, 'drink': 5.5}, 184 | 'any_prop1': {'food': 'string', 'movies': False, 'drink': 5.5}, 185 | } 186 | 187 | # additionalProperties - dict satisfying the 'Category' definition 188 | prop_spec['additionalProperties'] = { 189 | 'type': 'object', 190 | 'properties': { 191 | 'id': {'type': 'integer', 'format': 'int64'}, 192 | 'name': {'type': 'string'}, 193 | } 194 | } 195 | example = swagger_parser.get_example_from_prop_spec(prop_spec) 196 | assert example == { 197 | 'any_prop2': {'id': 42, 'name': 'string'}, 198 | 'any_prop1': {'id': 42, 'name': 'string'}, 199 | } 200 | 201 | 202 | def test_get_dict_definition(swagger_parser, pet_definition_example): 203 | assert swagger_parser.get_dict_definition(pet_definition_example) == 'Pet' 204 | assert swagger_parser.get_dict_definition({'error': 'error'}) is None 205 | 206 | 207 | def test_validate_definition(swagger_parser, pet_definition_example): 208 | # Check good 209 | assert swagger_parser.validate_definition('Pet', pet_definition_example) 210 | 211 | # Check missing required 212 | del pet_definition_example['name'] 213 | assert not swagger_parser.validate_definition('Pet', pet_definition_example) 214 | 215 | # Check extra arg 216 | pet_definition_example['name'] = 'string' 217 | pet_definition_example['extra'] = 'extra' 218 | assert not swagger_parser.validate_definition('Pet', pet_definition_example) 219 | 220 | # Check wrong type 221 | del pet_definition_example['extra'] 222 | pet_definition_example['name'] = 2 223 | assert not swagger_parser.validate_definition('Pet', pet_definition_example) 224 | 225 | 226 | def test_get_paths_data(swagger_parser, post_put_path_data, get_path_data): 227 | swagger_parser.get_paths_data() 228 | assert len(swagger_parser.paths) == 13 229 | assert swagger_parser.paths['/v2/pets'] == post_put_path_data 230 | assert swagger_parser.paths['/v2/pets/{petId}']['get'] == get_path_data 231 | post_pet_id = swagger_parser.paths['/v2/pets/{petId}']['post']['parameters']['petId'] 232 | delete_pet_id = swagger_parser.paths['/v2/pets/{petId}']['delete']['parameters']['petId'] 233 | assert post_pet_id == get_path_data['parameters']['petId'] 234 | assert delete_pet_id == get_path_data['parameters']['petId'] 235 | 236 | 237 | def test_get_definition_name_from_ref(swagger_parser): 238 | assert swagger_parser.get_definition_name_from_ref('#/definitions/Pet') == 'Pet' 239 | 240 | 241 | def test_get_path_spec(swagger_parser): 242 | assert swagger_parser.get_path_spec('/v2/pets')[0] == '/v2/pets' 243 | assert swagger_parser.get_path_spec('/v2/users/createWithList')[0] == '/v2/users/createWithList' 244 | assert swagger_parser.get_path_spec('/v2/stores/order/1253')[0] == '/v2/stores/order/{orderId}' 245 | assert swagger_parser.get_path_spec('/v2/stores/order/1253/123')[0] is None 246 | assert swagger_parser.get_path_spec('/v2/error')[0] is None 247 | 248 | 249 | def test_validate_request(swagger_parser, pet_definition_example): 250 | 251 | def _get_faulty_pet_definition_example(): 252 | faulty_pet_definition_example = deepcopy(pet_definition_example) 253 | # add item to sublevel dict 254 | faulty_pet_definition_example['category']['foo'] = 'bar' 255 | # delete item from toplevel dict 256 | del faulty_pet_definition_example['status'] 257 | # add item to toplevel dict 258 | faulty_pet_definition_example['fooo'] = 'baar' 259 | # change value to wrong type in toplevel dict 260 | faulty_pet_definition_example['id'] = 'fourtytwo' 261 | return faulty_pet_definition_example 262 | 263 | # In the given schema.yaml, the expected mime type is "json". 264 | # Since 'body' is not mandatory, we can send an empty json body {}, too. 265 | # - '' will be rejected 266 | # - None will be accepted 267 | # - Any other string body will be transformed to json and then checked 268 | # - {} will be accepted 269 | 270 | # wrong endpoint 271 | assert not swagger_parser.validate_request('/v2/foo', 'get') 272 | # empty string body (no json format, but in our schema, json is expected) 273 | assert not swagger_parser.validate_request('/v2/pets', 'post', body='') 274 | # wrong http method 275 | assert not swagger_parser.validate_request('/v2/pets', 'foo') 276 | # bad body - json, but not according to our given schema 277 | assert not swagger_parser.validate_request( 278 | '/v2/pets', 279 | 'post', 280 | body=_get_faulty_pet_definition_example(), 281 | ) 282 | # bad query - tags should be a list of strings, not a string 283 | assert not swagger_parser.validate_request('/v2/pets/findByTags', 'get', query={'tags': 'string'}) 284 | 285 | # no body (post generally does not require a body, and in our schema, no 286 | # parameters in body are required) 287 | # http://stackoverflow.com/questions/7323958/are-put-and-post-requests-required-expected-to-have-a-request-body 288 | assert swagger_parser.validate_request('/v2/pets', 'post') 289 | # empty body (in our schema, no parameters in body are required) 290 | assert swagger_parser.validate_request('/v2/pets', 'post', body={}) 291 | # valid body 292 | assert swagger_parser.validate_request( 293 | '/v2/pets', 294 | 'post', 295 | body=pet_definition_example, 296 | ) 297 | # valid query 298 | assert swagger_parser.validate_request('/v2/pets/findByTags', 'get', query={'tags': ['string']}) 299 | 300 | 301 | def test_get_request_data(swagger_parser, pet_definition_example): 302 | assert swagger_parser.get_request_data('error', 'get') == {400: ''} 303 | assert swagger_parser.get_request_data('/v2/pets/123', 'get') == {200: pet_definition_example, 400: '', 404: ''} 304 | assert swagger_parser.get_request_data('/v2/pets/123', 'error') == {400: ''} 305 | 306 | 307 | def test_get_send_request_correct_body(swagger_parser, pet_definition_example): 308 | assert swagger_parser.get_send_request_correct_body('/v2/pets', 'post') == pet_definition_example 309 | assert swagger_parser.get_send_request_correct_body('/v2/pets/findByStatus', 'get') is None 310 | assert swagger_parser.get_send_request_correct_body('/v2/users/username', 'put') == 'string' 311 | 312 | 313 | def test_array_definitions(swagger_array_parser): 314 | swagger_array_parser.build_definitions_example() 315 | 316 | stringArray = swagger_array_parser.definitions_example['StringArray'] 317 | widgetArray = swagger_array_parser.definitions_example['WidgetArray'] 318 | widget = swagger_array_parser.definitions_example['Widget'] 319 | 320 | assert isinstance(stringArray, list) 321 | assert stringArray[0] == 'string' 322 | 323 | assert isinstance(widgetArray, list) 324 | assert widgetArray[0] == widget 325 | 326 | 327 | def test_simple_additional_property_handling(swagger_parser): 328 | # value of type = int 329 | additional_properties_1 = {'any_prop2': 42, 'any_prop1': 42} 330 | valid_response_1 = {'aa': 3, 'ssssssss': 1, 'Not available': 6, 'xyz': 1, 'yyy': 2} 331 | bad_response_1 = {'a': 1, 'b': 222, 35: '23', 'c': False} 332 | assert swagger_parser.validate_additional_properties( 333 | additional_properties_1, valid_response_1) 334 | assert not swagger_parser.validate_additional_properties( 335 | additional_properties_1, bad_response_1) 336 | 337 | # value of type = string 338 | additional_properties_2 = {'any_prop2': 'hello', 'any_prop1': 'world'} 339 | valid_response_2 = {'aa': '3', 'ssssssss': 'one', 'Not available': '6', 555: 'yes', 'yyy': '$$$'} 340 | bad_response_2 = {'a': '1', 'b': 222, 35: '23', 'c': False} 341 | assert swagger_parser.validate_additional_properties(additional_properties_2, valid_response_2) 342 | assert not swagger_parser.validate_additional_properties(additional_properties_2, bad_response_2) 343 | 344 | 345 | def test_complex_additional_property_handling(swagger_parser): 346 | # value of type = object/complex 347 | additional_properties_3 = { 348 | 'any_prop2': {'word': 'hello', 'number': 1}, 349 | 'any_prop1': {'word': 'world', 'number': 2}, 350 | } 351 | valid_response_3 = { 352 | 'aa': {'word': 'hi', 'number': 3}, 353 | 5555: {'word': 'one', 'number': 1}, 354 | 'Not available': {'word': 'foo', 'number': 6}, 355 | } 356 | bad_response_3 = { 357 | 'a': {'word': 1, 'number': '1'}, 358 | 'b': {'word': 'bar', 'number': 222}, 359 | 35: {'word': 'baz', 'number': '23', 'anotherone': 'gna'}, 360 | 'c': {'word': 'True', 'number': False}, 361 | } 362 | assert swagger_parser.validate_additional_properties(additional_properties_3, valid_response_3) 363 | assert not swagger_parser.validate_additional_properties(additional_properties_3, bad_response_3) 364 | 365 | 366 | def test_referenced_additional_property_handling(swagger_parser): 367 | # This example here should match 'Category' definition 368 | additional_properties = { 369 | 'first_': {'id': 4, 'name': 'blub'}, 370 | 'second': {'id': 5, 'name': 'blubblub'}, 371 | } 372 | valid_response = { 373 | 'somekey_': {'id': 40, 'name': 'blub0'}, 374 | 'otherkey': {'id': 50, 'name': 'blubblub0'}, 375 | } 376 | bad_response = { 377 | 'somekey_': {'badkey': 40, 'name': 'blub0'}, 378 | 'otherkey': {'id': 'bad_value', 'name': 'blubblub0'}, 379 | 'thirdkey': {'id': 10, 'name': 'meandmyself', 'another': True}, 380 | } 381 | assert swagger_parser.validate_additional_properties(additional_properties, valid_response) 382 | assert not swagger_parser.validate_additional_properties(additional_properties, bad_response) 383 | 384 | 385 | def test_allof_handling(swagger_allof_parser): 386 | example = swagger_allof_parser.definitions_example['someObject'] 387 | assert 'a_property' in example 388 | assert 'some_property' in example 389 | 390 | another_example = swagger_allof_parser.definitions_example['anotherObject'] 391 | assert 'a_property' in another_example 392 | assert 'another_property' in another_example 393 | assert 'some_integer' in another_example 394 | 395 | 396 | @pytest.mark.skip 397 | def test_list_additional_property_handling(swagger_parser): 398 | # TODO list handling in additionalProperties is not implemented yet 399 | additional_properties_4 = { 400 | 'any_prop2': [{'word': 'hello', 'number': 1}, {'word': 'something', 'number': 3}], 401 | 'any_prop1': [{'word': 'world', 'number': 2}], 402 | } 403 | valid_response_4 = { 404 | 'aa': [{'word': 'hi', 'number': 3}, {'word': 'salut', 'number': 4}], 405 | 5555: [{'word': 'one', 'number': 1}], 406 | } 407 | bad_response_4 = { 408 | 'a': [{'word': 'x', 'number': 1}, {'word': 'y', 'number': 5}], 409 | 35: [{'word': 'baz', 'number': False}], 410 | 'c': 'a string', 411 | 'd': {'name': 'lala', 'number': 333}, 412 | 'e': ['one', 2, True] 413 | } 414 | assert swagger_parser.validate_additional_properties(additional_properties_4, valid_response_4) 415 | assert not swagger_parser.validate_additional_properties(additional_properties_4, bad_response_4) 416 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36, py37, py38, flake8 3 | 4 | [testenv] 5 | setenv = 6 | PYTHONPATH = {toxinidir}:{toxinidir}/swagger_parser 7 | commands = python setup.py test 8 | 9 | [testenv:flake8] 10 | commands = flake8 swagger_parser tests setup.py 11 | deps = flake8 12 | --------------------------------------------------------------------------------