Zum Inhalt

Zusätzliche Responses in OpenAPI

Achtung

Dies ist ein eher fortgeschrittenes Thema.

Wenn Sie mit FastAPI beginnen, benötigen Sie dies möglicherweise nicht.

Sie können zusätzliche Responses mit zusätzlichen Statuscodes, Medientypen, Beschreibungen, usw. deklarieren.

Diese zusätzlichen Responses werden in das OpenAPI-Schema aufgenommen, sodass sie auch in der API-Dokumentation erscheinen.

Für diese zusätzlichen Responses müssen Sie jedoch sicherstellen, dass Sie eine Response, wie etwa JSONResponse, direkt zurückgeben, mit Ihrem Statuscode und Inhalt.

Zusätzliche Response mit model

Sie können Ihren Pfadoperation-Dekoratoren einen Parameter responses übergeben.

Der nimmt ein dict entgegen, die Schlüssel sind Statuscodes für jede Response, wie etwa 200, und die Werte sind andere dicts mit den Informationen für jede Response.

Jedes dieser Response-dicts kann einen Schlüssel model haben, welcher ein Pydantic-Modell enthält, genau wie response_model.

FastAPI nimmt dieses Modell, generiert dessen JSON-Schema und fügt es an der richtigen Stelle in OpenAPI ein.

Um beispielsweise eine weitere Response mit dem Statuscode 404 und einem Pydantic-Modell Message zu deklarieren, können Sie schreiben:

from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel


class Item(BaseModel):
    id: str
    value: str


class Message(BaseModel):
    message: str


app = FastAPI()


@app.get("/items/{item_id}", response_model=Item, responses={404: {"model": Message}})
async def read_item(item_id: str):
    if item_id == "foo":
        return {"id": "foo", "value": "there goes my hero"}
    return JSONResponse(status_code=404, content={"message": "Item not found"})

Hinweis

Beachten Sie, dass Sie die JSONResponse direkt zurückgeben müssen.

Info

Der model-Schlüssel ist nicht Teil von OpenAPI.

FastAPI nimmt das Pydantic-Modell von dort, generiert das JSON-Schema und fügt es an der richtigen Stelle ein.

Die richtige Stelle ist:

  • Im Schlüssel content, der als Wert ein weiteres JSON-Objekt (dict) hat, welches Folgendes enthält:
    • Ein Schlüssel mit dem Medientyp, z. B. application/json, der als Wert ein weiteres JSON-Objekt hat, welches Folgendes enthält:
      • Ein Schlüssel schema, der als Wert das JSON-Schema aus dem Modell hat, hier ist die richtige Stelle.
        • FastAPI fügt hier eine Referenz auf die globalen JSON-Schemas an einer anderen Stelle in Ihrer OpenAPI hinzu, anstatt es direkt einzubinden. Auf diese Weise können andere Anwendungen und Clients diese JSON-Schemas direkt verwenden, bessere Tools zur Codegenerierung bereitstellen, usw.

Die generierten Responses in der OpenAPI für diese Pfadoperation lauten:

{
    "responses": {
        "404": {
            "description": "Additional Response",
            "content": {
                "application/json": {
                    "schema": {
                        "$ref": "#/components/schemas/Message"
                    }
                }
            }
        },
        "200": {
            "description": "Successful Response",
            "content": {
                "application/json": {
                    "schema": {
                        "$ref": "#/components/schemas/Item"
                    }
                }
            }
        },
        "422": {
            "description": "Validation Error",
            "content": {
                "application/json": {
                    "schema": {
                        "$ref": "#/components/schemas/HTTPValidationError"
                    }
                }
            }
        }
    }
}

Die Schemas werden von einer anderen Stelle innerhalb des OpenAPI-Schemas referenziert:

{
    "components": {
        "schemas": {
            "Message": {
                "title": "Message",
                "required": [
                    "message"
                ],
                "type": "object",
                "properties": {
                    "message": {
                        "title": "Message",
                        "type": "string"
                    }
                }
            },
            "Item": {
                "title": "Item",
                "required": [
                    "id",
                    "value"
                ],
                "type": "object",
                "properties": {
                    "id": {
                        "title": "Id",
                        "type": "string"
                    },
                    "value": {
                        "title": "Value",
                        "type": "string"
                    }
                }
            },
            "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"
                        }
                    }
                }
            }
        }
    }
}

Zusätzliche Medientypen für die Haupt-Response

Sie können denselben responses-Parameter verwenden, um verschiedene Medientypen für dieselbe Haupt-Response hinzuzufügen.

Sie können beispielsweise einen zusätzlichen Medientyp image/png hinzufügen und damit deklarieren, dass Ihre Pfadoperation ein JSON-Objekt (mit dem Medientyp application/json) oder ein PNG-Bild zurückgeben kann:

from typing import Union

from fastapi import FastAPI
from fastapi.responses import FileResponse
from pydantic import BaseModel


class Item(BaseModel):
    id: str
    value: str


app = FastAPI()


@app.get(
    "/items/{item_id}",
    response_model=Item,
    responses={
        200: {
            "content": {"image/png": {}},
            "description": "Return the JSON item or an image.",
        }
    },
)
async def read_item(item_id: str, img: Union[bool, None] = None):
    if img:
        return FileResponse("image.png", media_type="image/png")
    else:
        return {"id": "foo", "value": "there goes my hero"}

Hinweis

Beachten Sie, dass Sie das Bild direkt mit einer FileResponse zurückgeben müssen.

Info

Sofern Sie in Ihrem Parameter responses nicht explizit einen anderen Medientyp angeben, geht FastAPI davon aus, dass die Response denselben Medientyp wie die Haupt-Response-Klasse hat (Standardmäßig application/json).

Wenn Sie jedoch eine benutzerdefinierte Response-Klasse mit None als Medientyp angegeben haben, verwendet FastAPI application/json für jede zusätzliche Response, die über ein zugehöriges Modell verfügt.

Informationen kombinieren

Sie können auch Response-Informationen von mehreren Stellen kombinieren, einschließlich der Parameter response_model, status_code und responses.

Sie können ein response_model deklarieren, indem Sie den Standardstatuscode 200 (oder bei Bedarf einen benutzerdefinierten) verwenden und dann zusätzliche Informationen für dieselbe Response in responses direkt im OpenAPI-Schema deklarieren.

FastAPI behält die zusätzlichen Informationen aus responses und kombiniert sie mit dem JSON-Schema aus Ihrem Modell.

Sie können beispielsweise eine Response mit dem Statuscode 404 deklarieren, die ein Pydantic-Modell verwendet und über eine benutzerdefinierte Beschreibung (description) verfügt.

Und eine Response mit dem Statuscode 200, die Ihr response_model verwendet, aber ein benutzerdefiniertes Beispiel (example) enthält:

from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel


class Item(BaseModel):
    id: str
    value: str


class Message(BaseModel):
    message: str


app = FastAPI()


@app.get(
    "/items/{item_id}",
    response_model=Item,
    responses={
        404: {"model": Message, "description": "The item was not found"},
        200: {
            "description": "Item requested by ID",
            "content": {
                "application/json": {
                    "example": {"id": "bar", "value": "The bar tenders"}
                }
            },
        },
    },
)
async def read_item(item_id: str):
    if item_id == "foo":
        return {"id": "foo", "value": "there goes my hero"}
    else:
        return JSONResponse(status_code=404, content={"message": "Item not found"})

Es wird alles kombiniert und in Ihre OpenAPI eingebunden und in der API-Dokumentation angezeigt:

Vordefinierte und benutzerdefinierte Responses kombinieren

Möglicherweise möchten Sie einige vordefinierte Responses haben, die für viele Pfadoperationen gelten, Sie möchten diese jedoch mit benutzerdefinierten Responses kombinieren, die für jede Pfadoperation erforderlich sind.

In diesen Fällen können Sie die Python-Technik zum „Entpacken“ eines dicts mit **dict_to_unpack verwenden:

old_dict = {
    "old key": "old value",
    "second old key": "second old value",
}
new_dict = {**old_dict, "new key": "new value"}

Hier wird new_dict alle Schlüssel-Wert-Paare von old_dict plus das neue Schlüssel-Wert-Paar enthalten:

{
    "old key": "old value",
    "second old key": "second old value",
    "new key": "new value",
}

Mit dieser Technik können Sie einige vordefinierte Responses in Ihren Pfadoperationen wiederverwenden und sie mit zusätzlichen benutzerdefinierten Responses kombinieren.

Zum Beispiel:

from typing import Union

from fastapi import FastAPI
from fastapi.responses import FileResponse
from pydantic import BaseModel


class Item(BaseModel):
    id: str
    value: str


responses = {
    404: {"description": "Item not found"},
    302: {"description": "The item was moved"},
    403: {"description": "Not enough privileges"},
}


app = FastAPI()


@app.get(
    "/items/{item_id}",
    response_model=Item,
    responses={**responses, 200: {"content": {"image/png": {}}}},
)
async def read_item(item_id: str, img: Union[bool, None] = None):
    if img:
        return FileResponse("image.png", media_type="image/png")
    else:
        return {"id": "foo", "value": "there goes my hero"}

Weitere Informationen zu OpenAPI-Responses

Um zu sehen, was genau Sie in die Responses aufnehmen können, können Sie die folgenden Abschnitte in der OpenAPI-Spezifikation überprüfen:

  • OpenAPI Responses Object, enthält das Response Object.
  • OpenAPI Response Object, Sie können alles davon direkt in jede Response innerhalb Ihres responses-Parameter einfügen. Einschließlich description, headers, content (darin deklarieren Sie verschiedene Medientypen und JSON-Schemas) und links.