Fortgeschrittene Konfiguration der Pfadoperation¶
OpenAPI operationId¶
Achtung
Wenn Sie kein „Experte“ für OpenAPI sind, brauchen Sie dies wahrscheinlich nicht.
Mit dem Parameter operation_id
können Sie die OpenAPI operationId
festlegen, die in Ihrer Pfadoperation verwendet werden soll.
Sie müssten sicherstellen, dass sie für jede Operation eindeutig ist.
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/", operation_id="some_specific_id_you_define")
async def read_items():
return [{"item_id": "Foo"}]
Verwendung des Namens der Pfadoperation-Funktion als operationId¶
Wenn Sie die Funktionsnamen Ihrer API als operationId
s verwenden möchten, können Sie über alle iterieren und die operation_id
jeder Pfadoperation mit deren APIRoute.name
überschreiben.
Sie sollten dies tun, nachdem Sie alle Ihre Pfadoperationen hinzugefügt haben.
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)
Tipp
Wenn Sie app.openapi()
manuell aufrufen, sollten Sie vorher die operationId
s aktualisiert haben.
Achtung
Wenn Sie dies tun, müssen Sie sicherstellen, dass jede Ihrer Pfadoperation-Funktionen einen eindeutigen Namen hat.
Auch wenn diese sich in unterschiedlichen Modulen (Python-Dateien) befinden.
Von OpenAPI ausschließen¶
Um eine Pfadoperation aus dem generierten OpenAPI-Schema (und damit aus den automatischen Dokumentationssystemen) auszuschließen, verwenden Sie den Parameter include_in_schema
und setzen Sie ihn auf False
:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/", include_in_schema=False)
async def read_items():
return [{"item_id": "Foo"}]
Fortgeschrittene Beschreibung mittels Docstring¶
Sie können die verwendeten Zeilen aus dem Docstring einer Pfadoperation-Funktion einschränken, die für OpenAPI verwendet werden.
Das Hinzufügen eines \f
(ein maskiertes „Form Feed“-Zeichen) führt dazu, dass FastAPI die für OpenAPI verwendete Ausgabe an dieser Stelle abschneidet.
Sie wird nicht in der Dokumentation angezeigt, aber andere Tools (z. B. Sphinx) können den Rest verwenden.
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
Zusätzliche Responses¶
Sie haben wahrscheinlich gesehen, wie man das response_model
und den status_code
für eine Pfadoperation deklariert.
Das definiert die Metadaten der Haupt-Response einer Pfadoperation.
Sie können auch zusätzliche Responses mit deren Modellen, Statuscodes usw. deklarieren.
Es gibt hier in der Dokumentation ein ganzes Kapitel darüber, Sie können es unter Zusätzliche Responses in OpenAPI lesen.
OpenAPI-Extra¶
Wenn Sie in Ihrer Anwendung eine Pfadoperation deklarieren, generiert FastAPI automatisch die relevanten Metadaten dieser Pfadoperation, die in das OpenAPI-Schema aufgenommen werden sollen.
Technische Details
In der OpenAPI-Spezifikation wird das Operationsobjekt genannt.
Es hat alle Informationen zur Pfadoperation und wird zur Erstellung der automatischen Dokumentation verwendet.
Es enthält tags
, parameters
, requestBody
, responses
, usw.
Dieses Pfadoperation-spezifische OpenAPI-Schema wird normalerweise automatisch von FastAPI generiert, Sie können es aber auch erweitern.
Tipp
Dies ist ein Low-Level Erweiterungspunkt.
Wenn Sie nur zusätzliche Responses deklarieren müssen, können Sie dies bequemer mit Zusätzliche Responses in OpenAPI tun.
Sie können das OpenAPI-Schema für eine Pfadoperation erweitern, indem Sie den Parameter openapi_extra
verwenden.
OpenAPI-Erweiterungen¶
Dieses openapi_extra
kann beispielsweise hilfreich sein, um OpenAPI-Erweiterungen zu deklarieren:
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"}]
Wenn Sie die automatische API-Dokumentation öffnen, wird Ihre Erweiterung am Ende der spezifischen Pfadoperation angezeigt.
Und wenn Sie die resultierende OpenAPI sehen (unter /openapi.json
in Ihrer API), sehen Sie Ihre Erweiterung auch als Teil der spezifischen Pfadoperation:
{
"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"
}
}
}
}
Benutzerdefiniertes OpenAPI-Pfadoperation-Schema¶
Das Dictionary in openapi_extra
wird mit dem automatisch generierten OpenAPI-Schema für die Pfadoperation zusammengeführt (mittels Deep Merge).
Sie können dem automatisch generierten Schema also zusätzliche Daten hinzufügen.
Sie könnten sich beispielsweise dafür entscheiden, den Request mit Ihrem eigenen Code zu lesen und zu validieren, ohne die automatischen Funktionen von FastAPI mit Pydantic zu verwenden, aber Sie könnten den Request trotzdem im OpenAPI-Schema definieren wollen.
Das könnte man mit openapi_extra
machen:
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
In diesem Beispiel haben wir kein Pydantic-Modell deklariert. Tatsächlich wird der Requestbody nicht einmal als JSON geparst, sondern direkt als bytes
gelesen und die Funktion magic_data_reader ()
wäre dafür verantwortlich, ihn in irgendeiner Weise zu parsen.
Dennoch können wir das zu erwartende Schema für den Requestbody deklarieren.
Benutzerdefinierter OpenAPI-Content-Type¶
Mit demselben Trick könnten Sie ein Pydantic-Modell verwenden, um das JSON-Schema zu definieren, das dann im benutzerdefinierten Abschnitt des OpenAPI-Schemas für die Pfadoperation enthalten ist.
Und Sie könnten dies auch tun, wenn der Datentyp in der Anfrage nicht JSON ist.
In der folgenden Anwendung verwenden wir beispielsweise weder die integrierte Funktionalität von FastAPI zum Extrahieren des JSON-Schemas aus Pydantic-Modellen noch die automatische Validierung für JSON. Tatsächlich deklarieren wir den Request-Content-Type als YAML und nicht als 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
Info
In Pydantic Version 1 hieß die Methode zum Abrufen des JSON-Schemas für ein Modell Item.schema()
, in Pydantic Version 2 heißt die Methode Item.model_json_schema()
.
Obwohl wir nicht die standardmäßig integrierte Funktionalität verwenden, verwenden wir dennoch ein Pydantic-Modell, um das JSON-Schema für die Daten, die wir in YAML empfangen möchten, manuell zu generieren.
Dann verwenden wir den Request direkt und extrahieren den Body als bytes
. Das bedeutet, dass FastAPI nicht einmal versucht, den Request-Payload als JSON zu parsen.
Und dann parsen wir in unserem Code diesen YAML-Inhalt direkt und verwenden dann wieder dasselbe Pydantic-Modell, um den YAML-Inhalt zu validieren:
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
Info
In Pydantic Version 1 war die Methode zum Parsen und Validieren eines Objekts Item.parse_obj()
, in Pydantic Version 2 heißt die Methode Item.model_validate()
.
Tipp
Hier verwenden wir dasselbe Pydantic-Modell wieder.
Aber genauso hätten wir es auch auf andere Weise validieren können.