diff --git a/docs/img/tutorial/extending-openapi/image01.png b/docs/img/tutorial/extending-openapi/image01.png new file mode 100644 index 000000000..26d13293d Binary files /dev/null and b/docs/img/tutorial/extending-openapi/image01.png differ diff --git a/docs/src/extending_openapi/tutorial001.py b/docs/src/extending_openapi/tutorial001.py new file mode 100644 index 000000000..561e95898 --- /dev/null +++ b/docs/src/extending_openapi/tutorial001.py @@ -0,0 +1,28 @@ +from fastapi import FastAPI +from fastapi.openapi.utils import get_openapi + +app = FastAPI() + + +@app.get("/items/") +async def read_items(): + return [{"name": "Foo"}] + + +def custom_openapi(): + if app.openapi_schema: + return app.openapi_schema + openapi_schema = get_openapi( + title="Custom title", + version="2.5.0", + description="This is a very custom OpenAPI schema", + routes=app.routes, + ) + openapi_schema["info"]["x-logo"] = { + "url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" + } + app.openapi_schema = openapi_schema + return app.openapi_schema + + +app.openapi = custom_openapi diff --git a/docs/tutorial/extending-openapi.md b/docs/tutorial/extending-openapi.md new file mode 100644 index 000000000..585f29325 --- /dev/null +++ b/docs/tutorial/extending-openapi.md @@ -0,0 +1,90 @@ +!!! warning + This is a rather advanced feature. You probably can skip it. + + If you are just following the tutorial - user guide, you can probably skip this section. + + If you already know that you need to modify the generated OpenAPI schema, continue reading. + + +There are some cases where you might need to modify the generated OpenAPI schema. + +In this section you will see how. + +## The normal process + +The normal (default) process, is as follows. + +A `FastAPI` application (instance) has an `.openapi()` method that is expected to return the OpenAPI schema. + +As part of the application object creation, a *path operation* for `/openapi.json` (or for whatever you set your `openapi_url`) is registered. + +It just returns a JSON response with the result of the application's `.openapi()` method. + +By default, what the method `.openapi()` does is check the property `.openapi_schema` to see if it has contents and return them. + +If it doesn't, it generates them using the utility function at `fastapi.openapi.utils.get_openapi`. + +And that function `get_openapi()` receives as parameters: + +* `title`: The OpenAPI title, shown in the docs. +* `version`: The version of your API, e.g. `2.5.0`. +* `openapi_version`: The version of the OpenAPI specification used. By default, the latest: `3.0.2`. +* `description`: The description of your API. +* `routes`: A list of routes, these are each of the registered *path operations*. They are taken from `app.routes`. +* `openapi_prefix`: The URL prefix to be used in your OpenAPI. + +## Overriding the defaults + +Using the information above, you can use the same utility function to generate the OpenAPI schema and override each part that you need. + +For example, let's add ReDoc's OpenAPI extension to include a custom logo. + +### Normal **FastAPI** + +First, write all your **FastAPI** application as normally: + +```Python hl_lines="1 4 7 8 9" +{!./src/extending_openapi/tutorial001.py!} +``` + +### Generate the OpenAPI schema + +Then, use the same utility function to generate the OpenAPI schema, inside a `custom_openapi()` function: + +```Python hl_lines="2 15 16 17 18 19 20" +{!./src/extending_openapi/tutorial001.py!} +``` + +### Modify the OpenAPI schema + +Now you can add the ReDoc extension, adding a custom `x-logo` to the `info` "object" in the OpenAPI schema: + +```Python hl_lines="21 22 23" +{!./src/extending_openapi/tutorial001.py!} +``` + +### Cache the OpenAPI schema + +You can use the property `.openapi_schema` as a "cache", to store your generated schema. + +That way, your application won't have to generate the schema every time a user opens your API docs. + +It will be generated only once, and then the same cached schema will be used for the next requests. + +```Python hl_lines="13 14 24 25" +{!./src/extending_openapi/tutorial001.py!} +``` + +### Override the method + +Now you can replace the `.openapi()` method with your new function. + +```Python hl_lines="28" +{!./src/extending_openapi/tutorial001.py!} +``` + +### Check it + +Once you go to http://127.0.0.1:8000/redoc you will see that you are using your custom logo (in this example, **FastAPI**'s logo): + + diff --git a/mkdocs.yml b/mkdocs.yml index f550d4867..4edc575f2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -67,6 +67,7 @@ nav: - WebSockets: 'tutorial/websockets.md' - 'Events: startup - shutdown': 'tutorial/events.md' - Debugging: 'tutorial/debugging.md' + - Extending OpenAPI: 'tutorial/extending-openapi.md' - Concurrency and async / await: 'async.md' - Deployment: 'deployment.md' - Project Generation - Template: 'project-generation.md' diff --git a/tests/test_tutorial/test_extending_openapi/__init__.py b/tests/test_tutorial/test_extending_openapi/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial001.py b/tests/test_tutorial/test_extending_openapi/test_tutorial001.py new file mode 100644 index 000000000..860009614 --- /dev/null +++ b/tests/test_tutorial/test_extending_openapi/test_tutorial001.py @@ -0,0 +1,44 @@ +from starlette.testclient import TestClient + +from extending_openapi.tutorial001 import app + +client = TestClient(app) + +openapi_schema = { + "openapi": "3.0.2", + "info": { + "title": "Custom title", + "version": "2.5.0", + "description": "This is a very custom OpenAPI schema", + "x-logo": {"url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png"}, + }, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items Get", + "operationId": "read_items_items__get", + } + } + }, +} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == openapi_schema + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == openapi_schema + + +def test(): + response = client.get("/items/") + assert response.status_code == 200 + assert response.json() == [{"name": "Foo"}]