mirror of
https://github.com/rjNemo/fastapi
synced 2026-06-11 21:16:45 +00:00
📝 Document overriding operationId for all path operations using their function names (#642)
This commit is contained in:
parent
e1fd6785aa
commit
bac2f587b7
7 changed files with 200 additions and 122 deletions
|
|
@ -1,8 +1,24 @@
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
from fastapi.routing import APIRoute
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
@app.get("/items/", include_in_schema=False)
|
@app.get("/items/")
|
||||||
async def read_items():
|
async def read_items():
|
||||||
return [{"item_id": "Foo"}]
|
return [{"item_id": "Foo"}]
|
||||||
|
|
||||||
|
|
||||||
|
def use_route_names_as_operation_ids(app: FastAPI) -> None:
|
||||||
|
"""
|
||||||
|
Simplify operation IDs so that generated API clients have simpler function
|
||||||
|
names.
|
||||||
|
|
||||||
|
Should be called only after all routes have been added.
|
||||||
|
"""
|
||||||
|
for route in app.routes:
|
||||||
|
if isinstance(route, APIRoute):
|
||||||
|
route.operation_id = route.name # in this case, 'read_items'
|
||||||
|
|
||||||
|
|
||||||
|
use_route_names_as_operation_ids(app)
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,8 @@
|
||||||
from typing import Set
|
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
class Item(BaseModel):
|
@app.get("/items/", include_in_schema=False)
|
||||||
name: str
|
async def read_items():
|
||||||
description: str = None
|
return [{"item_id": "Foo"}]
|
||||||
price: float
|
|
||||||
tax: float = None
|
|
||||||
tags: Set[str] = []
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/items/", response_model=Item, summary="Create an item")
|
|
||||||
async def create_item(*, item: Item):
|
|
||||||
"""
|
|
||||||
Create an item with all the information:
|
|
||||||
|
|
||||||
- **name**: each item must have a name
|
|
||||||
- **description**: a long description
|
|
||||||
- **price**: required
|
|
||||||
- **tax**: if the item doesn't have tax, you can omit this
|
|
||||||
- **tags**: a set of unique tag strings for this item
|
|
||||||
\f
|
|
||||||
:param item: User input.
|
|
||||||
"""
|
|
||||||
return item
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
class Item(BaseModel):
|
||||||
|
name: str
|
||||||
|
description: str = None
|
||||||
|
price: float
|
||||||
|
tax: float = None
|
||||||
|
tags: Set[str] = []
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/items/", response_model=Item, summary="Create an item")
|
||||||
|
async def create_item(*, item: Item):
|
||||||
|
"""
|
||||||
|
Create an item with all the information:
|
||||||
|
|
||||||
|
- **name**: each item must have a name
|
||||||
|
- **description**: a long description
|
||||||
|
- **price**: required
|
||||||
|
- **tax**: if the item doesn't have tax, you can omit this
|
||||||
|
- **tags**: a set of unique tag strings for this item
|
||||||
|
\f
|
||||||
|
:param item: User input.
|
||||||
|
"""
|
||||||
|
return item
|
||||||
|
|
@ -11,12 +11,30 @@ You would have to make sure that it is unique for each operation.
|
||||||
{!./src/path_operation_advanced_configuration/tutorial001.py!}
|
{!./src/path_operation_advanced_configuration/tutorial001.py!}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Using the *path operation function* name as the operationId
|
||||||
|
|
||||||
|
If you want to use your APIs' function names as `operationId`s, you can iterate over all of them and override each *path operation's* `operation_id` using their `APIRoute.name`.
|
||||||
|
|
||||||
|
You should do it after adding all your *path operations*.
|
||||||
|
|
||||||
|
```Python hl_lines="2 12 13 14 15 16 17 18 19 20 21 24"
|
||||||
|
{!./src/path_operation_advanced_configuration/tutorial002.py!}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! tip
|
||||||
|
If you manually call `app.openapi()`, you should update the `operationId`s before that.
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
If you do this, you have to make sure each one of your *path operation functions* has a unique name.
|
||||||
|
|
||||||
|
Even if they are in different modules (Python files).
|
||||||
|
|
||||||
## Exclude from OpenAPI
|
## Exclude from OpenAPI
|
||||||
|
|
||||||
To exclude a path operation from the generated OpenAPI schema (and thus, from the automatic documentation systems), use the parameter `include_in_schema` and set it to `False`;
|
To exclude a path operation from the generated OpenAPI schema (and thus, from the automatic documentation systems), use the parameter `include_in_schema` and set it to `False`;
|
||||||
|
|
||||||
```Python hl_lines="6"
|
```Python hl_lines="6"
|
||||||
{!./src/path_operation_advanced_configuration/tutorial002.py!}
|
{!./src/path_operation_advanced_configuration/tutorial003.py!}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Advanced description from docstring
|
## Advanced description from docstring
|
||||||
|
|
@ -28,5 +46,5 @@ Adding an `\f` (an escaped "form feed" character) causes **FastAPI** to truncate
|
||||||
It won't show up in the documentation, but other tools (such as Sphinx) will be able to use the rest.
|
It won't show up in the documentation, but other tools (such as Sphinx) will be able to use the rest.
|
||||||
|
|
||||||
```Python hl_lines="19 20 21 22 23 24 25 26 27 28 29"
|
```Python hl_lines="19 20 21 22 23 24 25 26 27 28 29"
|
||||||
{!./src/path_operation_advanced_configuration/tutorial003.py!}
|
{!./src/path_operation_advanced_configuration/tutorial004.py!}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,20 @@ client = TestClient(app)
|
||||||
openapi_schema = {
|
openapi_schema = {
|
||||||
"openapi": "3.0.2",
|
"openapi": "3.0.2",
|
||||||
"info": {"title": "Fast API", "version": "0.1.0"},
|
"info": {"title": "Fast API", "version": "0.1.0"},
|
||||||
"paths": {},
|
"paths": {
|
||||||
|
"/items/": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {"application/json": {"schema": {}}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"summary": "Read Items",
|
||||||
|
"operationId": "read_items",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,90 +7,7 @@ client = TestClient(app)
|
||||||
openapi_schema = {
|
openapi_schema = {
|
||||||
"openapi": "3.0.2",
|
"openapi": "3.0.2",
|
||||||
"info": {"title": "Fast API", "version": "0.1.0"},
|
"info": {"title": "Fast API", "version": "0.1.0"},
|
||||||
"paths": {
|
"paths": {},
|
||||||
"/items/": {
|
|
||||||
"post": {
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successful Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {"$ref": "#/components/schemas/Item"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"422": {
|
|
||||||
"description": "Validation Error",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/HTTPValidationError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"summary": "Create an item",
|
|
||||||
"description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item\n",
|
|
||||||
"operationId": "create_item_items__post",
|
|
||||||
"requestBody": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {"$ref": "#/components/schemas/Item"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": True,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"components": {
|
|
||||||
"schemas": {
|
|
||||||
"Item": {
|
|
||||||
"title": "Item",
|
|
||||||
"required": ["name", "price"],
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"name": {"title": "Name", "type": "string"},
|
|
||||||
"price": {"title": "Price", "type": "number"},
|
|
||||||
"description": {"title": "Description", "type": "string"},
|
|
||||||
"tax": {"title": "Tax", "type": "number"},
|
|
||||||
"tags": {
|
|
||||||
"title": "Tags",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"type": "array",
|
|
||||||
"items": {"type": "string"},
|
|
||||||
"default": [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"ValidationError": {
|
|
||||||
"title": "ValidationError",
|
|
||||||
"required": ["loc", "msg", "type"],
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"loc": {
|
|
||||||
"title": "Location",
|
|
||||||
"type": "array",
|
|
||||||
"items": {"type": "string"},
|
|
||||||
},
|
|
||||||
"msg": {"title": "Message", "type": "string"},
|
|
||||||
"type": {"title": "Error Type", "type": "string"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"HTTPValidationError": {
|
|
||||||
"title": "HTTPValidationError",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"detail": {
|
|
||||||
"title": "Detail",
|
|
||||||
"type": "array",
|
|
||||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -100,13 +17,7 @@ def test_openapi_schema():
|
||||||
assert response.json() == openapi_schema
|
assert response.json() == openapi_schema
|
||||||
|
|
||||||
|
|
||||||
def test_query_params_str_validations():
|
def test_get():
|
||||||
response = client.post("/items/", json={"name": "Foo", "price": 42})
|
response = client.get("/items/")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {
|
assert response.json() == [{"item_id": "Foo"}]
|
||||||
"name": "Foo",
|
|
||||||
"price": 42,
|
|
||||||
"description": None,
|
|
||||||
"tax": None,
|
|
||||||
"tags": [],
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
from starlette.testclient import TestClient
|
||||||
|
|
||||||
|
from path_operation_advanced_configuration.tutorial004 import app
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
openapi_schema = {
|
||||||
|
"openapi": "3.0.2",
|
||||||
|
"info": {"title": "Fast API", "version": "0.1.0"},
|
||||||
|
"paths": {
|
||||||
|
"/items/": {
|
||||||
|
"post": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {"$ref": "#/components/schemas/Item"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"summary": "Create an item",
|
||||||
|
"description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item\n",
|
||||||
|
"operationId": "create_item_items__post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {"$ref": "#/components/schemas/Item"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"Item": {
|
||||||
|
"title": "Item",
|
||||||
|
"required": ["name", "price"],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {"title": "Name", "type": "string"},
|
||||||
|
"price": {"title": "Price", "type": "number"},
|
||||||
|
"description": {"title": "Description", "type": "string"},
|
||||||
|
"tax": {"title": "Tax", "type": "number"},
|
||||||
|
"tags": {
|
||||||
|
"title": "Tags",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "string"},
|
||||||
|
"default": [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ValidationError": {
|
||||||
|
"title": "ValidationError",
|
||||||
|
"required": ["loc", "msg", "type"],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"loc": {
|
||||||
|
"title": "Location",
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "string"},
|
||||||
|
},
|
||||||
|
"msg": {"title": "Message", "type": "string"},
|
||||||
|
"type": {"title": "Error Type", "type": "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"HTTPValidationError": {
|
||||||
|
"title": "HTTPValidationError",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"detail": {
|
||||||
|
"title": "Detail",
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_openapi_schema():
|
||||||
|
response = client.get("/openapi.json")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == openapi_schema
|
||||||
|
|
||||||
|
|
||||||
|
def test_query_params_str_validations():
|
||||||
|
response = client.post("/items/", json={"name": "Foo", "price": 42})
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {
|
||||||
|
"name": "Foo",
|
||||||
|
"price": 42,
|
||||||
|
"description": None,
|
||||||
|
"tax": None,
|
||||||
|
"tags": [],
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue