├── .flake8 ├── .github ├── dependabot.yml └── workflows │ ├── bumpversion.yml │ ├── pythonpackage.yml │ └── pythonpublish.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.rst ├── pyproject.toml ├── scripts ├── format ├── publish └── test ├── starlette_apispec ├── __init__.py └── schemas.py └── tests ├── __init__.py └── test_starlette_apispec.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | exclude = .git,__pycache__,docs/source/conf.py,old,build,dist 4 | max-complexity = 10 -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: mypy 11 | versions: 12 | - "0.800" 13 | -------------------------------------------------------------------------------- /.github/workflows/bumpversion.yml: -------------------------------------------------------------------------------- 1 | name: Bump version 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | bump-version: 10 | if: "!startsWith(github.event.head_commit.message, 'bump:')" 11 | runs-on: ubuntu-latest 12 | name: "Bump version and create changelog with commitizen" 13 | steps: 14 | - name: Check out 15 | uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 18 | token: '${{ secrets.PERSONAL_ACCESS_TOKEN }}' 19 | - name: Create bump and changelog 20 | uses: commitizen-tools/commitizen-action@master 21 | with: 22 | github_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/pythonpackage.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | python-check: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | max-parallel: 4 10 | matrix: 11 | python-version: ["3.8", "3.9", "3.10", "3.11"] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v1 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install -U pip poetry 24 | poetry --version 25 | poetry install 26 | - name: Run tests and linters 27 | run: | 28 | git config --global user.email "action@github.com" 29 | git config --global user.name "GitHub Action" 30 | ./scripts/test 31 | -------------------------------------------------------------------------------- /.github/workflows/pythonpublish.yaml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | token: '${{ secrets.PERSONAL_ACCESS_TOKEN }}' 15 | fetch-depth: 0 16 | - name: Set up Python 17 | uses: actions/setup-python@v1 18 | with: 19 | python-version: '3.x' 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install -U pip poetry 23 | poetry --version 24 | poetry install 25 | - name: Publish 26 | env: 27 | PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} 28 | PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | ./scripts/publish 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Text editors 2 | .vscode/ 3 | todo 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # pyenv 80 | .python-version 81 | 82 | # celery beat schedule file 83 | celerybeat-schedule 84 | 85 | # SageMath parsed files 86 | *.sage.py 87 | 88 | # Environments 89 | .env 90 | .venv 91 | env/ 92 | venv/ 93 | ENV/ 94 | env.bak/ 95 | venv.bak/ 96 | 97 | # Spyder project settings 98 | .spyderproject 99 | .spyproject 100 | 101 | # Rope project settings 102 | .ropeproject 103 | 104 | # mkdocs documentation 105 | /site 106 | 107 | # mypy 108 | .mypy_cache/ 109 | 110 | # poetry 111 | *.lock -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## v2.2.1 (2024-02-06) 9 | 10 | ### Fix 11 | 12 | - **deps**: update starlette requirement (#72) 13 | 14 | ## v2.2.0 (2023-11-06) 15 | 16 | ### Feat 17 | 18 | - **deps**: update starlette requirement from ^0.31.0 to >=0.31,<0.33 (#65) 19 | 20 | ## v2.1.0 (2023-09-08) 21 | 22 | ### Feat 23 | 24 | - **deps**: update apispec requirement from >=1,<6 to >=1,<7 (#64) 25 | 26 | ## v2.0.0 (2023-08-22) 27 | 28 | ### BREAKING CHANGE 29 | 30 | - `py3.6` and `py3.7` have been dropped, please upgrade to `>=py3.8` 31 | 32 | ### Fix 33 | 34 | - update bump action 35 | - drop support for python deprecated versions and update packages 36 | 37 | ## v1.0.5 (2021-12-20) 38 | 39 | ### Fix 40 | 41 | - **deps**: update apispec requirement from >=1,<5 to >=1,<6 42 | 43 | ## v1.0.4 (2021-07-15) 44 | 45 | ### Fix 46 | 47 | - format to bump version 48 | 49 | ## [1.0.3] - 2019-11-12 50 | 51 | - Bumped dependecies: starlette-apispec and dev deps 52 | 53 | ## [1.0.2] - 2019-06-12 54 | 55 | ### Fix 56 | 57 | - `app.schema` was deprecated in starlette so the tests were fixed. 58 | 59 | ## [1.0.1] - 2019-02-16 60 | 61 | - Restructured text syntax errors in README preventing a good render in pypi. 62 | 63 | ## [1.0.0] - 2019-02-16 64 | 65 | - Support for apispec `^1.0.0` 66 | 67 | ## [1.0.0b1] - 2019-01-03 68 | 69 | ### BREAKING CHANGE 70 | 71 | - Support for apispec `>1.0.0b5` 72 | 73 | ## [0.1.3] - 2018-12-18 74 | 75 | - Improved version range from caret req to higher than `0.8.8` 76 | 77 | ## [0.1.2] - 2018-11-23 78 | 79 | ### Updated 80 | 81 | - Documentation 82 | 83 | ## [0.1.1] - 2018-11-23 84 | 85 | ### Added 86 | 87 | - Documentation 88 | - APISpecSchemaGenerator ready to be used with starlette 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Santiago 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================== 2 | Starlette APISpec 3 | ================== 4 | 5 | Easy APISpec integration for Starlette 6 | 7 | 8 | .. image:: https://github.com/Woile/starlette-apispec/actions/workflows/pythonpackage.yml/badge.svg?style=flat-square 9 | :alt: GitHub Workflow Status 10 | :target: https://github.com/Woile/starlette-apispec/actions/workflows/pythonpackage.yml 11 | 12 | .. image:: https://img.shields.io/codecov/c/github/Woile/starlette-apispec.svg?style=flat-square 13 | :alt: Codecov 14 | :target: https://codecov.io/gh/Woile/starlette-apispec 15 | 16 | .. image:: https://img.shields.io/pypi/v/starlette-apispec.svg?style=flat-square 17 | :alt: PyPI 18 | :target: https://pypi.org/project/starlette-apispec/ 19 | 20 | .. image:: https://img.shields.io/pypi/pyversions/starlette-apispec.svg?style=flat-square 21 | :alt: PyPI - Python Version 22 | :target: https://pypi.org/project/starlette-apispec/ 23 | 24 | .. contents:: 25 | :depth: 2 26 | 27 | .. code-block:: python 28 | 29 | from apispec import APISpec 30 | from apispec.ext.marshmallow import MarshmallowPlugin 31 | from starlette.applications import Starlette 32 | from starlette_apispec import APISpecSchemaGenerator 33 | 34 | app = Starlette() 35 | 36 | schemas = APISpecSchemaGenerator( 37 | APISpec( 38 | title="Example API", 39 | version="1.0", 40 | openapi_version="3.0.0", 41 | info={"description": "explanation of the api purpose"}, 42 | plugins=[MarshmallowPlugin()], 43 | ) 44 | ) 45 | 46 | @app.route("/schema", methods=["GET"], include_in_schema=False) 47 | def schema(request): 48 | return schemas.OpenAPIResponse(request=request) 49 | 50 | 51 | Installation 52 | ============ 53 | 54 | :: 55 | 56 | pip install -U starlette-apispec 57 | 58 | Alternatively you can do 59 | 60 | :: 61 | 62 | poetry add starlette-apispec 63 | 64 | About 65 | ----- 66 | 67 | This library helps you easily document your REST API built with starlette. 68 | 69 | Starlette_ is a is a lightweight ASGI framework/toolkit, 70 | which is ideal for building high performance asyncio services. 71 | 72 | APISpec_ supports the `OpenApi Specification `_ 73 | and it has some useful plugins like marshmallow_ support. 74 | 75 | Version supported: :code:`^1.0.0` 76 | 77 | 78 | Usage 79 | ===== 80 | 81 | 82 | This example includes marshmallow_ integration 83 | 84 | .. code-block:: python 85 | 86 | from apispec import APISpec 87 | 88 | from starlette.applications import Starlette 89 | from starlette.endpoints import HTTPEndpoint 90 | from starlette.testclient import TestClient 91 | 92 | from starlette_apispec import APISpecSchemaGenerator 93 | 94 | 95 | app = Starlette() 96 | 97 | schemas = APISpecSchemaGenerator( 98 | APISpec( 99 | title="Example API", 100 | version="1.0", 101 | openapi_version="3.0.0", 102 | info={"description": "explanation of the api purpose"}, 103 | ) 104 | ) 105 | 106 | 107 | @app.websocket_route("/ws") 108 | def ws(session): 109 | """ws""" 110 | pass # pragma: no cover 111 | 112 | 113 | @app.route("/users", methods=["GET", "HEAD"]) 114 | def list_users(request): 115 | """ 116 | responses: 117 | 200: 118 | description: A list of users. 119 | examples: 120 | [{"username": "tom"}, {"username": "lucy"}] 121 | """ 122 | pass # pragma: no cover 123 | 124 | 125 | @app.route("/users", methods=["POST"]) 126 | def create_user(request): 127 | """ 128 | responses: 129 | 200: 130 | description: A user. 131 | examples: 132 | {"username": "tom"} 133 | """ 134 | pass # pragma: no cover 135 | 136 | 137 | @app.route("/orgs") 138 | class OrganisationsEndpoint(HTTPEndpoint): 139 | def get(self, request): 140 | """ 141 | responses: 142 | 200: 143 | description: A list of organisations. 144 | examples: 145 | [{"name": "Foo Corp."}, {"name": "Acme Ltd."}] 146 | """ 147 | pass # pragma: no cover 148 | 149 | def post(self, request): 150 | """ 151 | responses: 152 | 200: 153 | description: An organisation. 154 | examples: 155 | {"name": "Foo Corp."} 156 | """ 157 | pass # pragma: no cover 158 | 159 | 160 | @app.route("/schema", methods=["GET"], include_in_schema=False) 161 | def schema(request): 162 | return schemas.OpenAPIResponse(request=request) 163 | 164 | More documentation 165 | ================== 166 | 167 | This package is basically a proxy, so if you wonder how to do something, 168 | here are the sources you need: 169 | 170 | `Starlette documentation`_ 171 | 172 | `APISpec documentation`_ 173 | 174 | 175 | Testing 176 | ======= 177 | 178 | 1. Clone the repo 179 | 2. Activate venv ``. venv/bin/activate`` 180 | 3. Install dependencies 181 | 182 | :: 183 | 184 | poetry install 185 | 186 | 4. Run tests 187 | 188 | :: 189 | 190 | ./scripts/test 191 | 192 | 193 | Contributing 194 | ============ 195 | 196 | **PRs are welcome!** 197 | 198 | 199 | .. _marshmallow: https://marshmallow.readthedocs.io/ 200 | .. _APISpec: https://apispec.readthedocs.io/en/stable/ 201 | .. _Starlette: https://www.starlette.io/ 202 | .. _`Starlette documentation`: https://www.starlette.io/ 203 | .. _`APISpec documentation`: https://apispec.readthedocs.io/en/stable/ 204 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.commitizen] 2 | tag_format = "v$version" 3 | version_provider = "poetry" 4 | version_files = [ 5 | "starlette_apispec/__init__.py:__version__" 6 | ] 7 | 8 | [tool.black] 9 | line-length = 88 10 | include = '\.pyi?$' 11 | exclude = ''' 12 | /( 13 | \.git 14 | | \.hg 15 | | \.mypy_cache 16 | | \.tox 17 | | \.venv 18 | | _build 19 | | buck-out 20 | | build 21 | | dist 22 | # The following are specific to Black, you probably don't want those. 23 | | blib2to3 24 | | tests/data 25 | | profiling 26 | )/ 27 | ''' 28 | 29 | [tool.poetry] 30 | name = "starlette-apispec" 31 | version = "2.2.1" 32 | description = "APISpec support for starlette" 33 | authors = ["Santiago Fraire Willemoes "] 34 | keywords = ["openapi", "api", "swagger"] 35 | classifiers = [ 36 | "Environment :: Web Environment", 37 | "Intended Audience :: Developers", 38 | "License :: OSI Approved :: BSD License", 39 | "Operating System :: OS Independent", 40 | "Topic :: Internet :: WWW/HTTP", 41 | "Programming Language :: Python :: 3", 42 | "Programming Language :: Python :: 3.6", 43 | "Programming Language :: Python :: 3.7", 44 | ] 45 | readme = "README.rst" 46 | license = "BSD-3-Clause" 47 | homepage = "https://github.com/Woile/starlette-apispec" 48 | 49 | [tool.poetry.dependencies] 50 | python = ">=3.8,<4.0" 51 | apispec = ">=1,<7" 52 | starlette = ">=0.11" 53 | pyyaml = ">=5.1,<7.0" 54 | 55 | [tool.poetry.group.dev.dependencies] 56 | pytest = ">=6,<8" 57 | flake8 = "^5.0" 58 | black = "^23.7.0" 59 | mypy = "^1.5" 60 | pytest-cov = ">=3,<5" 61 | codecov = "^2.0" 62 | isort = "^5.9.2" 63 | starlette = {version = ">=0.31,<0.38", extras = ["full"]} 64 | 65 | [build-system] 66 | requires = ["poetry>=0.12"] 67 | build-backend = "poetry.masonry.api" 68 | 69 | [tool.isort] 70 | profile = "black" 71 | known_first_party = ["starlette_apispec", "tests"] 72 | -------------------------------------------------------------------------------- /scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | export PREFIX="poetry run python -m " 4 | if [ -d '.venv' ] ; then 5 | export PREFIX=".venv/bin/" 6 | fi 7 | 8 | set -x 9 | 10 | ${PREFIX}isort starlette_apispec tests 11 | ${PREFIX}black starlette_apispec tests 12 | -------------------------------------------------------------------------------- /scripts/publish: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | # Publish to pypi 3 | poetry publish --build -u "$PYPI_USERNAME" -p "$PYPI_PASSWORD" 4 | -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | export PREFIX="poetry run python -m " 4 | if [ -d '.venv' ] ; then 5 | export PREFIX=".venv/bin/" 6 | fi 7 | 8 | ${PREFIX}pytest --cov-report term-missing --cov-report=xml:coverage.xml --cov=starlette_apispec tests/ 9 | ${PREFIX}black starlette_apispec tests --check 10 | ${PREFIX}isort --check-only starlette_apispec tests 11 | ${PREFIX}mypy starlette_apispec/ 12 | ${PREFIX}flake8 starlette_apispec/ tests/ 13 | -------------------------------------------------------------------------------- /starlette_apispec/__init__.py: -------------------------------------------------------------------------------- 1 | from .schemas import APISpecSchemaGenerator 2 | 3 | __all__ = ["APISpecSchemaGenerator"] 4 | 5 | __version__ = "2.2.1" 6 | -------------------------------------------------------------------------------- /starlette_apispec/schemas.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from starlette.routing import BaseRoute 4 | from starlette.schemas import BaseSchemaGenerator 5 | 6 | 7 | def make_plain_datastructure(data: dict) -> dict: 8 | """ 9 | Some of the children of the dict might have specialized datatypes. 10 | """ 11 | if isinstance(data, list): 12 | return [make_plain_datastructure(item) for item in data] 13 | elif isinstance(data, dict): 14 | return {key: make_plain_datastructure(value) for key, value in data.items()} 15 | return data 16 | 17 | 18 | class APISpecSchemaGenerator(BaseSchemaGenerator): 19 | def __init__(self, spec: typing.Type) -> None: 20 | self.spec = spec 21 | 22 | def get_schema(self, routes: typing.List[BaseRoute]) -> dict: 23 | endpoints = self.get_endpoints(routes) 24 | for endpoint in endpoints: 25 | self.spec.path( 26 | path=endpoint.path, 27 | operations={endpoint.http_method: self.parse_docstring(endpoint.func)}, 28 | ) 29 | return make_plain_datastructure(self.spec.to_dict()) 30 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woile/starlette-apispec/ad71a67e80e4f059a335dc208bc0667eec3c5ebe/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_starlette_apispec.py: -------------------------------------------------------------------------------- 1 | from apispec import APISpec 2 | from starlette.applications import Starlette 3 | from starlette.endpoints import HTTPEndpoint 4 | from starlette.testclient import TestClient 5 | 6 | from starlette_apispec import APISpecSchemaGenerator 7 | 8 | app = Starlette() 9 | 10 | schemas = APISpecSchemaGenerator( 11 | APISpec( 12 | title="Example API", 13 | version="1.0", 14 | openapi_version="3.0.0", 15 | info={"description": "explanation of the api purpose"}, 16 | ) 17 | ) 18 | 19 | 20 | @app.websocket_route("/ws") 21 | def ws(session): 22 | """ws""" 23 | pass # pragma: no cover 24 | 25 | 26 | @app.route("/users", methods=["GET", "HEAD"]) 27 | def list_users(request): 28 | """ 29 | responses: 30 | 200: 31 | description: A list of users. 32 | examples: 33 | [{"username": "tom"}, {"username": "lucy"}] 34 | """ 35 | pass # pragma: no cover 36 | 37 | 38 | @app.route("/users", methods=["POST"]) 39 | def create_user(request): 40 | """ 41 | responses: 42 | 200: 43 | description: A user. 44 | examples: 45 | {"username": "tom"} 46 | """ 47 | pass # pragma: no cover 48 | 49 | 50 | @app.route("/orgs") 51 | class OrganisationsEndpoint(HTTPEndpoint): 52 | def get(self, request): 53 | """ 54 | responses: 55 | 200: 56 | description: A list of organisations. 57 | examples: 58 | [{"name": "Foo Corp."}, {"name": "Acme Ltd."}] 59 | """ 60 | pass # pragma: no cover 61 | 62 | def post(self, request): 63 | """ 64 | responses: 65 | 200: 66 | description: An organisation. 67 | examples: 68 | {"name": "Foo Corp."} 69 | """ 70 | pass # pragma: no cover 71 | 72 | 73 | @app.route("/schema", methods=["GET"], include_in_schema=False) 74 | def schema(request): 75 | return schemas.OpenAPIResponse(request=request) 76 | 77 | 78 | def test_schema_generation(): 79 | schema = schemas.get_schema(routes=app.routes) 80 | print(schema) 81 | assert schema == { 82 | "info": { 83 | "description": "explanation of the api purpose", 84 | "title": "Example API", 85 | "version": "1.0", 86 | }, 87 | "paths": { 88 | "/users": { 89 | "get": { 90 | "responses": { 91 | "200": { 92 | "description": "A list of users.", 93 | "examples": [{"username": "tom"}, {"username": "lucy"}], 94 | } 95 | } 96 | }, 97 | "post": { 98 | "responses": { 99 | "200": { 100 | "description": "A user.", 101 | "examples": {"username": "tom"}, 102 | } 103 | } 104 | }, 105 | }, 106 | "/orgs": { 107 | "get": { 108 | "responses": { 109 | "200": { 110 | "description": "A list of organisations.", 111 | "examples": [{"name": "Foo Corp."}, {"name": "Acme Ltd."}], 112 | } 113 | } 114 | }, 115 | "post": { 116 | "responses": { 117 | "200": { 118 | "description": "An organisation.", 119 | "examples": {"name": "Foo Corp."}, 120 | } 121 | } 122 | }, 123 | }, 124 | }, 125 | "openapi": "3.0.0", 126 | } 127 | 128 | 129 | EXPECTED_SCHEMA = """ 130 | info: 131 | description: explanation of the api purpose 132 | title: Example API 133 | version: '1.0' 134 | openapi: 3.0.0 135 | paths: 136 | /orgs: 137 | get: 138 | responses: 139 | '200': 140 | description: A list of organisations. 141 | examples: 142 | - name: Foo Corp. 143 | - name: Acme Ltd. 144 | post: 145 | responses: 146 | '200': 147 | description: An organisation. 148 | examples: 149 | name: Foo Corp. 150 | /users: 151 | get: 152 | responses: 153 | '200': 154 | description: A list of users. 155 | examples: 156 | - username: tom 157 | - username: lucy 158 | post: 159 | responses: 160 | '200': 161 | description: A user. 162 | examples: 163 | username: tom 164 | """ 165 | 166 | 167 | def test_schema_endpoint(): 168 | client = TestClient(app) 169 | response = client.get("/schema") 170 | assert response.headers["Content-Type"] == "application/vnd.oai.openapi" 171 | print(response.text.strip()) 172 | assert response.text.strip() == EXPECTED_SCHEMA.strip() 173 | --------------------------------------------------------------------------------