Перейти до змісту

Додаткові відповіді в OpenAPI

🌐 Переклад ШІ та людьми

Цей переклад виконано ШІ під керівництвом людей. 🤝

Можливі помилки через неправильне розуміння початкового змісту або неприродні формулювання тощо. 🤖

Ви можете покращити цей переклад, допомігши нам краще спрямовувати AI LLM.

Англійська версія

Попередження

Це доволі просунута тема.

Якщо ви лише починаєте з FastAPI, ймовірно, вам це не потрібно.

Ви можете оголосити додаткові відповіді з додатковими кодами статусу, типами медіа, описами тощо.

Ці додаткові відповіді буде включено до схеми OpenAPI, тож вони з'являться і в документації API.

Але для таких додаткових відповідей потрібно повертати Response на кшталт JSONResponse безпосередньо, із потрібним кодом статусу та вмістом.

Додаткова відповідь з model

Ви можете передати вашим декораторам операцій шляху параметр responses.

Він приймає dict: ключі - це коди статусу для кожної відповіді (наприклад, 200), а значення - інші dict з інформацією для кожної з них.

Кожен із цих словників відповіді може мати ключ model, що містить Pydantic-модель, подібно до response_model.

FastAPI візьме цю модель, згенерує її Схему JSON і додасть у відповідне місце в OpenAPI.

Наприклад, щоб оголосити іншу відповідь з кодом статусу 404 і Pydantic-моделлю Message, ви можете написати:

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"})
🤓 Other versions and variants
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"})

Примітка

Майте на увазі, що потрібно повертати JSONResponse безпосередньо.

Інформація

Ключ model не є частиною OpenAPI.

FastAPI візьме звідти Pydantic-модель, згенерує Схему JSON і помістить у відповідне місце.

Відповідне місце це:

  • У ключі content, значенням якого є інший JSON-об'єкт (dict), що містить:
    • Ключ із типом медіа, напр. application/json, значенням якого є інший JSON-об'єкт, що містить:
      • Ключ schema, значенням якого є Схема JSON з моделі - ось це і є правильне місце.
        • FastAPI додає тут посилання на глобальні Схеми JSON в іншому місці вашого OpenAPI замість прямого включення. Так інші застосунки та клієнти можуть напряму використовувати ці Схеми JSON, надавати кращі інструменти генерації коду тощо.

Згенеровані відповіді в OpenAPI для цієї операції шляху будуть такими:

{
    "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"
                    }
                }
            }
        }
    }
}

Схеми посилаються на інше місце всередині схеми OpenAPI:

{
    "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"
                        }
                    }
                }
            }
        }
    }
}

Додаткові типи медіа для основної відповіді

Можна використати цей самий параметр responses, щоб додати різні типи медіа для тієї ж основної відповіді.

Наприклад, можна додати додатковий тип медіа image/png, оголосивши, що ваша операція шляху може повертати JSON-об'єкт (з типом медіа application/json) або PNG-зображення:

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: bool | None = None):
    if img:
        return FileResponse("image.png", media_type="image/png")
    else:
        return {"id": "foo", "value": "there goes my hero"}

Примітка

Зверніть увагу, що потрібно повертати зображення безпосередньо за допомогою FileResponse.

Інформація

Поки ви явно не вкажете інший тип медіа в параметрі responses, FastAPI вважатиме, що відповідь має той самий тип медіа, що й основний клас відповіді (типово application/json).

Але якщо ви вказали власний клас відповіді з None як типом медіа, FastAPI використає application/json для будь-якої додаткової відповіді, що має пов'язану модель.

Комбінування інформації

Ви також можете поєднувати інформацію про відповіді з кількох місць, зокрема з параметрів response_model, status_code і responses.

Ви можете оголосити response_model, використовуючи типовий код статусу 200 (або власний за потреби), а потім оголосити додаткову інформацію для цієї ж відповіді в responses, безпосередньо в схемі OpenAPI.

FastAPI збереже додаткову інформацію з responses і поєднає її зі Схемою JSON з вашої моделі.

Наприклад, ви можете оголосити відповідь з кодом статусу 404, яка використовує Pydantic-модель і має власний description.

І відповідь з кодом статусу 200, яка використовує ваш response_model, але містить власний example:

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"})
🤓 Other versions and variants
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"})

Усе це буде поєднано та включено до вашого OpenAPI і показано в документації API:

Комбінуйте попередньо визначені та власні відповіді

Можливо, ви захочете мати кілька попередньо визначених відповідей, що застосовуються до багатьох операцій шляху, але поєднувати їх із власними відповідями, потрібними для кожної операції шляху.

Для таких випадків можна скористатися прийомом Python «розпакування» dict за допомогою **dict_to_unpack:

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

Тут new_dict міститиме всі пари ключ-значення з old_dict плюс нову пару ключ-значення:

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

Цей прийом можна використати, щоб перевикористовувати деякі попередньо визначені відповіді у ваших операціях шляху та поєднувати їх із додатковими власними.

Наприклад:

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: bool | None = None):
    if img:
        return FileResponse("image.png", media_type="image/png")
    else:
        return {"id": "foo", "value": "there goes my hero"}

Докладніше про відповіді OpenAPI

Щоб побачити, що саме можна включати у відповіді, ознайомтеся з цими розділами специфікації OpenAPI:

  • Об'єкт відповідей OpenAPI, він включає Response Object.
  • Об'єкт відповіді OpenAPI, ви можете включити будь-що з цього безпосередньо в кожну відповідь у параметрі responses. Зокрема description, headers, content (усередині нього ви оголошуєте різні типи медіа та Схеми JSON) і links.