Zum Inhalt

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 operationIds 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 operationIds 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())
    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())
    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.