Browse Source

Add support for tags with Enums (#4468)

pull/4470/head
Sebastián Ramírez 3 years ago
committed by GitHub
parent
commit
569afb4378
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      docs/en/docs/tutorial/path-operation-configuration.md
  2. 20
      docs_src/path_operation_configuration/tutorial002b.py
  3. 23
      fastapi/applications.py
  4. 32
      fastapi/routing.py
  5. 56
      tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py

12
docs/en/docs/tutorial/path-operation-configuration.md

@ -64,6 +64,18 @@ They will be added to the OpenAPI schema and used by the automatic documentation
<img src="/img/tutorial/path-operation-configuration/image01.png"> <img src="/img/tutorial/path-operation-configuration/image01.png">
### Tags with Enums
If you have a big application, you might end up accumulating **several tags**, and you would want to make sure you always use the **same tag** for related *path operations*.
In these cases, it could make sense to store the tags in an `Enum`.
**FastAPI** supports that the same way as with plain strings:
```Python hl_lines="1 8-10 13 18"
{!../../../docs_src/path_operation_configuration/tutorial002b.py!}
```
## Summary and description ## Summary and description
You can add a `summary` and `description`: You can add a `summary` and `description`:

20
docs_src/path_operation_configuration/tutorial002b.py

@ -0,0 +1,20 @@
from enum import Enum
from fastapi import FastAPI
app = FastAPI()
class Tags(Enum):
items = "items"
users = "users"
@app.get("/items/", tags=[Tags.items])
async def get_items():
return ["Portal gun", "Plumbus"]
@app.get("/users/", tags=[Tags.users])
async def read_users():
return ["Rick", "Morty"]

23
fastapi/applications.py

@ -1,3 +1,4 @@
from enum import Enum
from typing import Any, Callable, Coroutine, Dict, List, Optional, Sequence, Type, Union from typing import Any, Callable, Coroutine, Dict, List, Optional, Sequence, Type, Union
from fastapi import routing from fastapi import routing
@ -219,7 +220,7 @@ class FastAPI(Starlette):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None, dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -273,7 +274,7 @@ class FastAPI(Starlette):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None, dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -342,7 +343,7 @@ class FastAPI(Starlette):
router: routing.APIRouter, router: routing.APIRouter,
*, *,
prefix: str = "", prefix: str = "",
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None, dependencies: Optional[Sequence[Depends]] = None,
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None, responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None, deprecated: Optional[bool] = None,
@ -368,7 +369,7 @@ class FastAPI(Starlette):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None, dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -419,7 +420,7 @@ class FastAPI(Starlette):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None, dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -470,7 +471,7 @@ class FastAPI(Starlette):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None, dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -521,7 +522,7 @@ class FastAPI(Starlette):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None, dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -572,7 +573,7 @@ class FastAPI(Starlette):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None, dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -623,7 +624,7 @@ class FastAPI(Starlette):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None, dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -674,7 +675,7 @@ class FastAPI(Starlette):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None, dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -725,7 +726,7 @@ class FastAPI(Starlette):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[Depends]] = None, dependencies: Optional[Sequence[Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,

32
fastapi/routing.py

@ -1,9 +1,9 @@
import asyncio import asyncio
import dataclasses import dataclasses
import email.message import email.message
import enum
import inspect import inspect
import json import json
from enum import Enum, IntEnum
from typing import ( from typing import (
Any, Any,
Callable, Callable,
@ -305,7 +305,7 @@ class APIRoute(routing.Route):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None, dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -330,7 +330,7 @@ class APIRoute(routing.Route):
openapi_extra: Optional[Dict[str, Any]] = None, openapi_extra: Optional[Dict[str, Any]] = None,
) -> None: ) -> None:
# normalise enums e.g. http.HTTPStatus # normalise enums e.g. http.HTTPStatus
if isinstance(status_code, enum.IntEnum): if isinstance(status_code, IntEnum):
status_code = int(status_code) status_code = int(status_code)
self.path = path self.path = path
self.endpoint = endpoint self.endpoint = endpoint
@ -438,7 +438,7 @@ class APIRouter(routing.Router):
self, self,
*, *,
prefix: str = "", prefix: str = "",
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None, dependencies: Optional[Sequence[params.Depends]] = None,
default_response_class: Type[Response] = Default(JSONResponse), default_response_class: Type[Response] = Default(JSONResponse),
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None, responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
@ -466,7 +466,7 @@ class APIRouter(routing.Router):
"/" "/"
), "A path prefix must not end with '/', as the routes will start with '/'" ), "A path prefix must not end with '/', as the routes will start with '/'"
self.prefix = prefix self.prefix = prefix
self.tags: List[str] = tags or [] self.tags: List[Union[str, Enum]] = tags or []
self.dependencies = list(dependencies or []) or [] self.dependencies = list(dependencies or []) or []
self.deprecated = deprecated self.deprecated = deprecated
self.include_in_schema = include_in_schema self.include_in_schema = include_in_schema
@ -483,7 +483,7 @@ class APIRouter(routing.Router):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None, dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -557,7 +557,7 @@ class APIRouter(routing.Router):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None, dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -634,7 +634,7 @@ class APIRouter(routing.Router):
router: "APIRouter", router: "APIRouter",
*, *,
prefix: str = "", prefix: str = "",
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None, dependencies: Optional[Sequence[params.Depends]] = None,
default_response_class: Type[Response] = Default(JSONResponse), default_response_class: Type[Response] = Default(JSONResponse),
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None, responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
@ -738,7 +738,7 @@ class APIRouter(routing.Router):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None, dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -790,7 +790,7 @@ class APIRouter(routing.Router):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None, dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -842,7 +842,7 @@ class APIRouter(routing.Router):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None, dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -894,7 +894,7 @@ class APIRouter(routing.Router):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None, dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -946,7 +946,7 @@ class APIRouter(routing.Router):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None, dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -998,7 +998,7 @@ class APIRouter(routing.Router):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None, dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -1050,7 +1050,7 @@ class APIRouter(routing.Router):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None, dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
@ -1102,7 +1102,7 @@ class APIRouter(routing.Router):
*, *,
response_model: Optional[Type[Any]] = None, response_model: Optional[Type[Any]] = None,
status_code: Optional[int] = None, status_code: Optional[int] = None,
tags: Optional[List[str]] = None, tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None, dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None, summary: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,

56
tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py

@ -0,0 +1,56 @@
from fastapi.testclient import TestClient
from docs_src.path_operation_configuration.tutorial002b import app
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"tags": ["items"],
"summary": "Get Items",
"operationId": "get_items_items__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
}
},
"/users/": {
"get": {
"tags": ["users"],
"summary": "Read Users",
"operationId": "read_users_users__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
}
},
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_get_items():
response = client.get("/items/")
assert response.status_code == 200, response.text
assert response.json() == ["Portal gun", "Plumbus"]
def test_get_users():
response = client.get("/users/")
assert response.status_code == 200, response.text
assert response.json() == ["Rick", "Morty"]
Loading…
Cancel
Save