Configuración Avanzada de Path Operation¶
operationId de OpenAPI¶
Advertencia
Si no eres un "experto" en OpenAPI, probablemente no necesites esto.
Puedes establecer el operationId
de OpenAPI para ser usado en tu path operation con el parámetro operation_id
.
Tienes que asegurarte de que sea único para cada operación.
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/", operation_id="some_specific_id_you_define")
async def read_items():
return [{"item_id": "Foo"}]
Usar el nombre de la función de path operation como el operationId¶
Si quieres usar los nombres de las funciones de tus APIs como operationId
s, puedes iterar sobre todas ellas y sobrescribir el operation_id
de cada path operation usando su APIRoute.name
.
Deberías hacerlo después de agregar todas tus path operations.
from fastapi import FastAPI
from fastapi.routing import APIRoute
app = FastAPI()
@app.get("/items/")
async def read_items():
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)
Consejo
Si llamas manualmente a app.openapi()
, deberías actualizar los operationId
s antes de eso.
Advertencia
Si haces esto, tienes que asegurarte de que cada una de tus funciones de path operation tenga un nombre único.
Incluso si están en diferentes módulos (archivos de Python).
Excluir de OpenAPI¶
Para excluir una path operation del esquema OpenAPI generado (y por lo tanto, de los sistemas de documentación automática), utiliza el parámetro include_in_schema
y configúralo en False
:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/", include_in_schema=False)
async def read_items():
return [{"item_id": "Foo"}]
Descripción avanzada desde el docstring¶
Puedes limitar las líneas usadas del docstring de una función de path operation para OpenAPI.
Añadir un \f
(un carácter de separación de página escapado) hace que FastAPI trunque la salida usada para OpenAPI en este punto.
No aparecerá en la documentación, pero otras herramientas (como Sphinx) podrán usar el resto.
from typing import Set, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
@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
Responses Adicionales¶
Probablemente has visto cómo declarar el response_model
y el status_code
para una path operation.
Eso define los metadatos sobre el response principal de una path operation.
También puedes declarar responses adicionales con sus modelos, códigos de estado, etc.
Hay un capítulo entero en la documentación sobre ello, puedes leerlo en Responses Adicionales en OpenAPI.
OpenAPI Extra¶
Cuando declaras una path operation en tu aplicación, FastAPI genera automáticamente los metadatos relevantes sobre esa path operation para incluirlos en el esquema de OpenAPI.
Nota
En la especificación de OpenAPI se llama el Objeto de Operación.
Tiene toda la información sobre la path operation y se usa para generar la documentación automática.
Incluye los tags
, parameters
, requestBody
, responses
, etc.
Este esquema de OpenAPI específico de path operation normalmente se genera automáticamente por FastAPI, pero también puedes extenderlo.
Consejo
Este es un punto de extensión de bajo nivel.
Si solo necesitas declarar responses adicionales, una forma más conveniente de hacerlo es con Responses Adicionales en OpenAPI.
Puedes extender el esquema de OpenAPI para una path operation usando el parámetro openapi_extra
.
Extensiones de OpenAPI¶
Este openapi_extra
puede ser útil, por ejemplo, para declarar Extensiones de OpenAPI:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/", openapi_extra={"x-aperture-labs-portal": "blue"})
async def read_items():
return [{"item_id": "portal-gun"}]
Si abres la documentación automática de la API, tu extensión aparecerá en la parte inferior de la path operation específica.
Y si ves el OpenAPI resultante (en /openapi.json
en tu API), verás tu extensión como parte de la path operation específica también:
{
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/items/": {
"get": {
"summary": "Read Items",
"operationId": "read_items_items__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"x-aperture-labs-portal": "blue"
}
}
}
}
Esquema de path operation personalizada de OpenAPI¶
El diccionario en openapi_extra
se combinará profundamente con el esquema de OpenAPI generado automáticamente para la path operation.
Por lo tanto, podrías añadir datos adicionales al esquema generado automáticamente.
Por ejemplo, podrías decidir leer y validar el request con tu propio código, sin usar las funcionalidades automáticas de FastAPI con Pydantic, pero aún podrías querer definir el request en el esquema de OpenAPI.
Podrías hacer eso con openapi_extra
:
from fastapi import FastAPI, Request
app = FastAPI()
def magic_data_reader(raw_body: bytes):
return {
"size": len(raw_body),
"content": {
"name": "Maaaagic",
"price": 42,
"description": "Just kiddin', no magic here. ✨",
},
}
@app.post(
"/items/",
openapi_extra={
"requestBody": {
"content": {
"application/json": {
"schema": {
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"type": "string"},
"price": {"type": "number"},
"description": {"type": "string"},
},
}
}
},
"required": True,
},
},
)
async def create_item(request: Request):
raw_body = await request.body()
data = magic_data_reader(raw_body)
return data
En este ejemplo, no declaramos ningún modelo Pydantic. De hecho, el cuerpo del request ni siquiera se parse como JSON, se lee directamente como bytes
, y la función magic_data_reader()
sería la encargada de parsearlo de alguna manera.
Sin embargo, podemos declarar el esquema esperado para el cuerpo del request.
Tipo de contenido personalizado de OpenAPI¶
Usando este mismo truco, podrías usar un modelo Pydantic para definir el esquema JSON que luego se incluye en la sección personalizada del esquema OpenAPI para la path operation.
Y podrías hacer esto incluso si el tipo de datos en el request no es JSON.
Por ejemplo, en esta aplicación no usamos la funcionalidad integrada de FastAPI para extraer el esquema JSON de los modelos Pydantic ni la validación automática para JSON. De hecho, estamos declarando el tipo de contenido del request como YAML, no JSON:
from typing import List
import yaml
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, ValidationError
app = FastAPI()
class Item(BaseModel):
name: str
tags: List[str]
@app.post(
"/items/",
openapi_extra={
"requestBody": {
"content": {"application/x-yaml": {"schema": Item.model_json_schema()}},
"required": True,
},
},
)
async def create_item(request: Request):
raw_body = await request.body()
try:
data = yaml.safe_load(raw_body)
except yaml.YAMLError:
raise HTTPException(status_code=422, detail="Invalid YAML")
try:
item = Item.model_validate(data)
except ValidationError as e:
raise HTTPException(status_code=422, detail=e.errors(include_url=False))
return item
from typing import List
import yaml
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, ValidationError
app = FastAPI()
class Item(BaseModel):
name: str
tags: List[str]
@app.post(
"/items/",
openapi_extra={
"requestBody": {
"content": {"application/x-yaml": {"schema": Item.schema()}},
"required": True,
},
},
)
async def create_item(request: Request):
raw_body = await request.body()
try:
data = yaml.safe_load(raw_body)
except yaml.YAMLError:
raise HTTPException(status_code=422, detail="Invalid YAML")
try:
item = Item.parse_obj(data)
except ValidationError as e:
raise HTTPException(status_code=422, detail=e.errors())
return item
Información
En la versión 1 de Pydantic el método para obtener el esquema JSON para un modelo se llamaba Item.schema()
, en la versión 2 de Pydantic, el método se llama Item.model_json_schema()
.
Sin embargo, aunque no estamos usando la funcionalidad integrada por defecto, aún estamos usando un modelo Pydantic para generar manualmente el esquema JSON para los datos que queremos recibir en YAML.
Luego usamos el request directamente, y extraemos el cuerpo como bytes
. Esto significa que FastAPI ni siquiera intentará parsear la carga útil del request como JSON.
Y luego en nuestro código, parseamos ese contenido YAML directamente, y nuevamente estamos usando el mismo modelo Pydantic para validar el contenido YAML:
from typing import List
import yaml
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, ValidationError
app = FastAPI()
class Item(BaseModel):
name: str
tags: List[str]
@app.post(
"/items/",
openapi_extra={
"requestBody": {
"content": {"application/x-yaml": {"schema": Item.model_json_schema()}},
"required": True,
},
},
)
async def create_item(request: Request):
raw_body = await request.body()
try:
data = yaml.safe_load(raw_body)
except yaml.YAMLError:
raise HTTPException(status_code=422, detail="Invalid YAML")
try:
item = Item.model_validate(data)
except ValidationError as e:
raise HTTPException(status_code=422, detail=e.errors(include_url=False))
return item
from typing import List
import yaml
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, ValidationError
app = FastAPI()
class Item(BaseModel):
name: str
tags: List[str]
@app.post(
"/items/",
openapi_extra={
"requestBody": {
"content": {"application/x-yaml": {"schema": Item.schema()}},
"required": True,
},
},
)
async def create_item(request: Request):
raw_body = await request.body()
try:
data = yaml.safe_load(raw_body)
except yaml.YAMLError:
raise HTTPException(status_code=422, detail="Invalid YAML")
try:
item = Item.parse_obj(data)
except ValidationError as e:
raise HTTPException(status_code=422, detail=e.errors())
return item
Información
En la versión 1 de Pydantic el método para parsear y validar un objeto era Item.parse_obj()
, en la versión 2 de Pydantic, el método se llama Item.model_validate()
.
Consejo
Aquí reutilizamos el mismo modelo Pydantic.
Pero de la misma manera, podríamos haberlo validado de alguna otra forma.