From a071ddf3cd4a8e9cfa89409a2273c5e284bc1e4b Mon Sep 17 00:00:00 2001 From: Thomas Maschler Date: Sat, 13 Jun 2020 07:58:06 -0400 Subject: [PATCH] =?utf8?q?=E2=9C=A8=20Add=20support=20for=20tag=20metadata?= =?utf8?q?=20in=20OpenAPI=20(#1348)?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit * Allow to add OpenAPI tag descriptions * fix type hint * fix type hint 2 * refactor test to assure 100% coverage * 📝 Update tags metadata example * 📝 Update docs for tags metadata * ✅ Move tags metadata test to tutorial subdir * 🎨 Update format in applications * 🍱 Update docs UI image based on new example * 🎨 Apply formatting after solving conflicts Co-authored-by: Sebastián Ramírez --- .../en/docs/img/tutorial/metadata/image02.png | Bin 0 -> 47719 bytes docs/en/docs/tutorial/metadata.md | 52 ++++++++++++++ docs_src/metadata/tutorial004.py | 28 ++++++++ fastapi/applications.py | 3 + fastapi/openapi/utils.py | 7 +- fastapi/routing.py | 1 - .../test_metadata/test_tutorial004.py | 65 ++++++++++++++++++ 7 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 docs/en/docs/img/tutorial/metadata/image02.png create mode 100644 docs_src/metadata/tutorial004.py create mode 100644 tests/test_tutorial/test_metadata/test_tutorial004.py diff --git a/docs/en/docs/img/tutorial/metadata/image02.png b/docs/en/docs/img/tutorial/metadata/image02.png new file mode 100644 index 0000000000000000000000000000000000000000..7f3ab0a10dc5136b851d5fbf0ff2a24d1cf93903 GIT binary patch literal 47719 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 literal 0 Hc-jL100001 diff --git a/docs/en/docs/tutorial/metadata.md b/docs/en/docs/tutorial/metadata.md index 666fa7648b..b9120c82e4 100644 --- a/docs/en/docs/tutorial/metadata.md +++ b/docs/en/docs/tutorial/metadata.md @@ -21,6 +21,58 @@ With this configuration, the automatic API docs would look like: +## Tag descriptions + +You can also add additional metadata for the different tags used to group your path operations with the parameter `openapi_tags`. + +It takes a list containing one dictionary for each tag. + +Each dictionary can contain: + +* `name` (**required**): a `str` with the same tag name you use in the `tags` parameter in your *path operations* and `APIRouter`s. +* `description`: a `str` with a short description for the tag. It can have Markdown and will be shown in the docs UI. +* `externalDocs`: a `dict` describing external documentation with: + * `description`: a `str` with a short description for the external docs. + * `url` (**required**): a `str` with the URL for the external documentation. + +### Create metadata for tags + +Let's try that in an example with tags for `users` and `items`. + +Create metadata for your tags and pass it to the `openapi_tags` parameter: + +```Python hl_lines="3 4 5 6 7 8 9 10 11 12 13 14 15 16 18" +{!../../../docs_src/metadata/tutorial004.py!} +``` + +Notice that you can use Markdown inside of the descriptions, for example "login" will be shown in bold (**login**) and "fancy" will be shown in italics (_fancy_). + +!!! tip + You don't have to add metadata for all the tags that you use. + +### Use your tags + +Use the `tags` parameter with your *path operations* (and `APIRouter`s) to assign them to different tags: + +```Python hl_lines="21 26" +{!../../../docs_src/metadata/tutorial004.py!} +``` + +!!! info + Read more about tags in [Path Operation Configuration](../path-operation-configuration/#tags){.internal-link target=_blank}. + +### Check the docs + +Now, if you check the docs, they will show all the additional metadata: + + + +### Order of tags + +The order of each tag metadata dictionary also defines the order shown in the docs UI. + +For example, even though `users` would go after `items` in alphabetical order, it is shown before them, because we added their metadata as the first dictionary in the list. + ## OpenAPI URL By default, the OpenAPI schema is served at `/openapi.json`. diff --git a/docs_src/metadata/tutorial004.py b/docs_src/metadata/tutorial004.py new file mode 100644 index 0000000000..465bd659d5 --- /dev/null +++ b/docs_src/metadata/tutorial004.py @@ -0,0 +1,28 @@ +from fastapi import FastAPI + +tags_metadata = [ + { + "name": "users", + "description": "Operations with users. The **login** logic is also here.", + }, + { + "name": "items", + "description": "Manage items. So _fancy_ they have their own docs.", + "externalDocs": { + "description": "Items external docs", + "url": "https://fastapi.tiangolo.com/", + }, + }, +] + +app = FastAPI(openapi_tags=tags_metadata) + + +@app.get("/users/", tags=["users"]) +async def get_users(): + return [{"name": "Harry"}, {"name": "Ron"}] + + +@app.get("/items/", tags=["items"]) +async def get_items(): + return [{"name": "wand"}, {"name": "flying broom"}] diff --git a/fastapi/applications.py b/fastapi/applications.py index 39e694fae9..3306aab3d9 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -37,6 +37,7 @@ class FastAPI(Starlette): description: str = "", version: str = "0.1.0", openapi_url: Optional[str] = "/openapi.json", + openapi_tags: Optional[List[Dict[str, Any]]] = None, default_response_class: Type[Response] = JSONResponse, docs_url: Optional[str] = "/docs", redoc_url: Optional[str] = "/redoc", @@ -70,6 +71,7 @@ class FastAPI(Starlette): self.description = description self.version = version self.openapi_url = openapi_url + self.openapi_tags = openapi_tags # TODO: remove when discarding the openapi_prefix parameter if openapi_prefix: logger.warning( @@ -103,6 +105,7 @@ class FastAPI(Starlette): description=self.description, routes=self.routes, openapi_prefix=openapi_prefix, + tags=self.openapi_tags, ) return self.openapi_schema diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index bb2e7dff74..b6221ca202 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -317,12 +317,13 @@ def get_openapi( openapi_version: str = "3.0.2", description: str = None, routes: Sequence[BaseRoute], - openapi_prefix: str = "" + openapi_prefix: str = "", + tags: Optional[List[Dict[str, Any]]] = None ) -> Dict: info = {"title": title, "version": version} if description: info["description"] = description - output = {"openapi": openapi_version, "info": info} + output: Dict[str, Any] = {"openapi": openapi_version, "info": info} components: Dict[str, Dict] = {} paths: Dict[str, Dict] = {} flat_models = get_flat_models_from_routes(routes) @@ -352,4 +353,6 @@ def get_openapi( if components: output["components"] = components output["paths"] = paths + if tags: + output["tags"] = tags return jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True) diff --git a/fastapi/routing.py b/fastapi/routing.py index 71a2b4d04e..b4560a8a4c 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -12,7 +12,6 @@ from fastapi.dependencies.utils import ( ) from fastapi.encoders import DictIntStrAny, SetIntStr, jsonable_encoder from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError -from fastapi.logger import logger from fastapi.openapi.constants import STATUS_CODES_WITH_NO_BODY from fastapi.utils import ( PYDANTIC_1, diff --git a/tests/test_tutorial/test_metadata/test_tutorial004.py b/tests/test_tutorial/test_metadata/test_tutorial004.py new file mode 100644 index 0000000000..1ec59d3fe7 --- /dev/null +++ b/tests/test_tutorial/test_metadata/test_tutorial004.py @@ -0,0 +1,65 @@ +from fastapi.testclient import TestClient + +from metadata.tutorial004 import app + +client = TestClient(app) + +openapi_schema = { + "openapi": "3.0.2", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "tags": ["users"], + "summary": "Get Users", + "operationId": "get_users_users__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/items/": { + "get": { + "tags": ["items"], + "summary": "Get Items", + "operationId": "get_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + }, + "tags": [ + { + "name": "users", + "description": "Operations with users. The **login** logic is also here.", + }, + { + "name": "items", + "description": "Manage items. So _fancy_ they have their own docs.", + "externalDocs": { + "description": "Items external docs", + "url": "https://fastapi.tiangolo.com/", + }, + }, + ], +} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == openapi_schema + + +def test_path_operations(): + response = client.get("/items/") + assert response.status_code == 200, response.text + response = client.get("/users/") + assert response.status_code == 200, response.text -- 2.47.3