From a071ddf3cd4a8e9cfa89409a2273c5e284bc1e4b Mon Sep 17 00:00:00 2001 From: Thomas Maschler Date: Sat, 13 Jun 2020 07:58:06 -0400 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20tag=20metadat?= =?UTF-8?q?a=20in=20OpenAPI=20(#1348)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 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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 W{(iuVP6CP111LRLl`nf{9Qa=sR4w-a literal 0 HcmV?d00001 diff --git a/docs/en/docs/tutorial/metadata.md b/docs/en/docs/tutorial/metadata.md index 666fa7648..b9120c82e 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 000000000..465bd659d --- /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 39e694fae..3306aab3d 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 bb2e7dff7..b6221ca20 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 71a2b4d04..b4560a8a4 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 000000000..1ec59d3fe --- /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