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 fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: 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
đŸ€“ Other versions and variants
from typing import 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
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 im Request 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:

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
đŸ€“ Other versions and variants
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
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
đŸ€“ Other versions and variants
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:

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
đŸ€“ Other versions and variants
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
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
đŸ€“ Other versions and variants
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.