Перейти к содержанию

Модель ответа — Возвращаемый тип

Вы можете объявить тип, используемый для ответа, указав аннотацию возвращаемого значения для функции-обработчика пути.

Вы можете использовать аннотации типов так же, как и для входных данных в параметрах функции: Pydantic-модели, списки, словари, скалярные значения (целые числа, булевы и т.д.).

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: list[str] = []


@app.post("/items/")
async def create_item(item: Item) -> Item:
    return item


@app.get("/items/")
async def read_items() -> list[Item]:
    return [
        Item(name="Portal Gun", price=42.0),
        Item(name="Plumbus", price=32.0),
    ]
🤓 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: list[str] = []


@app.post("/items/")
async def create_item(item: Item) -> Item:
    return item


@app.get("/items/")
async def read_items() -> list[Item]:
    return [
        Item(name="Portal Gun", price=42.0),
        Item(name="Plumbus", price=32.0),
    ]
from typing import List, 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: List[str] = []


@app.post("/items/")
async def create_item(item: Item) -> Item:
    return item


@app.get("/items/")
async def read_items() -> List[Item]:
    return [
        Item(name="Portal Gun", price=42.0),
        Item(name="Plumbus", price=32.0),
    ]

FastAPI будет использовать этот тип ответа для:

  • Валидации возвращаемых данных.
    • Если данные невалидны (например, отсутствует поле), это означает, что код вашего приложения работает некорректно и возвращает не то, что должен. В таком случае будет возвращена ошибка сервера вместо неправильных данных. Так вы и ваши клиенты можете быть уверены, что получите ожидаемые данные и ожидаемую структуру.
  • Добавления JSON Schema для ответа в OpenAPI операции пути.
    • Это будет использовано автоматической документацией.
    • Это также будет использовано инструментами автоматической генерации клиентского кода.

Но самое главное:

  • Выходные данные будут ограничены и отфильтрованы в соответствии с тем, что определено в возвращаемом типе.
    • Это особенно важно для безопасности, ниже мы рассмотрим это подробнее.

Параметр response_model

Бывают случаи, когда вам нужно или хочется возвращать данные, которые не в точности соответствуют объявленному типу.

Например, вы можете хотеть возвращать словарь (dict) или объект из базы данных, но объявить его как Pydantic-модель. Тогда Pydantic-модель выполнит документирование данных, валидацию и т.п. для объекта, который вы вернули (например, словаря или объекта из базы данных).

Если вы добавите аннотацию возвращаемого типа, инструменты и редакторы кода начнут жаловаться (и будут правы), что функция возвращает тип (например, dict), отличный от объявленного (например, Pydantic-модель).

В таких случаях вместо аннотации возвращаемого типа можно использовать параметр response_model у декоратора операции пути.

Параметр response_model можно указать у любой операции пути:

  • @app.get()
  • @app.post()
  • @app.put()
  • @app.delete()
  • и т.д.
from typing import Any

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: list[str] = []


@app.post("/items/", response_model=Item)
async def create_item(item: Item) -> Any:
    return item


@app.get("/items/", response_model=list[Item])
async def read_items() -> Any:
    return [
        {"name": "Portal Gun", "price": 42.0},
        {"name": "Plumbus", "price": 32.0},
    ]
🤓 Other versions and variants
from typing import Any, 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: list[str] = []


@app.post("/items/", response_model=Item)
async def create_item(item: Item) -> Any:
    return item


@app.get("/items/", response_model=list[Item])
async def read_items() -> Any:
    return [
        {"name": "Portal Gun", "price": 42.0},
        {"name": "Plumbus", "price": 32.0},
    ]
from typing import Any, List, 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: List[str] = []


@app.post("/items/", response_model=Item)
async def create_item(item: Item) -> Any:
    return item


@app.get("/items/", response_model=List[Item])
async def read_items() -> Any:
    return [
        {"name": "Portal Gun", "price": 42.0},
        {"name": "Plumbus", "price": 32.0},
    ]

Примечание

Обратите внимание, что response_model — это параметр метода «декоратора» (get, post и т.д.), а не вашей функции-обработчика пути, в которой указываются параметры и тело запроса.

response_model принимает тот же тип, что вы бы объявили для поля Pydantic-модели, то есть это может быть одна Pydantic-модель, а может быть, например, list Pydantic-моделей, как List[Item].

FastAPI будет использовать response_model для документации, валидации и т. п., а также для конвертации и фильтрации выходных данных к объявленному типу.

Совет

Если у вас в редакторе кода, mypy и т. п. включены строгие проверки типов, вы можете объявить возвращаемый тип функции как Any.

Так вы сообщите редактору, что намеренно возвращаете что угодно. Но FastAPI всё равно выполнит документацию данных, валидацию, фильтрацию и т.д. с помощью response_model.

Приоритет response_model

Если вы объявите и возвращаемый тип, и response_model, приоритет будет у response_model, именно его использует FastAPI.

Так вы можете добавить корректные аннотации типов к своим функциям, даже если фактически возвращаете тип, отличный от модели ответа, чтобы ими пользовались редактор и инструменты вроде mypy. И при этом FastAPI продолжит выполнять валидацию данных, документацию и т.д. с использованием response_model.

Вы также можете указать response_model=None, чтобы отключить создание модели ответа для данной операции пути. Это может понадобиться, если вы добавляете аннотации типов для вещей, не являющихся валидными полями Pydantic. Пример вы увидите ниже.

Вернуть те же входные данные

Здесь мы объявляем модель UserIn, она будет содержать пароль в открытом виде:

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None


# Don't do this in production!
@app.post("/user/")
async def create_user(user: UserIn) -> UserIn:
    return user
🤓 Other versions and variants
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: Union[str, None] = None


# Don't do this in production!
@app.post("/user/")
async def create_user(user: UserIn) -> UserIn:
    return user

Информация

Чтобы использовать EmailStr, сначала установите email-validator.

Создайте виртуальное окружение, активируйте его и затем установите пакет, например:

$ pip install email-validator

или так:

$ pip install "pydantic[email]"

И мы используем эту модель для объявления входных данных, и ту же модель — для объявления выходных данных:

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None


# Don't do this in production!
@app.post("/user/")
async def create_user(user: UserIn) -> UserIn:
    return user
🤓 Other versions and variants
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: Union[str, None] = None


# Don't do this in production!
@app.post("/user/")
async def create_user(user: UserIn) -> UserIn:
    return user

Теперь, когда браузер создаёт пользователя с паролем, API вернёт тот же пароль в ответе.

В этом случае это может быть не проблемой, так как пароль отправляет тот же пользователь.

Но если мы используем ту же модель в другой операции пути, мы можем начать отправлять пароли пользователей каждому клиенту.

Осторожно

Никогда не храните пароль пользователя в открытом виде и не отправляйте его в ответе подобным образом, если только вы не понимаете всех рисков и точно знаете, что делаете.

Добавить модель для ответа

Вместо этого мы можем создать входную модель с паролем в открытом виде и выходную модель без него:

from typing import Any

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None


class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None


@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
    return user
🤓 Other versions and variants
from typing import Any, Union

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: Union[str, None] = None


class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: Union[str, None] = None


@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
    return user

Здесь, хотя функция-обработчик пути возвращает тот же входной объект пользователя, содержащий пароль:

from typing import Any

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None


class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None


@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
    return user
🤓 Other versions and variants
from typing import Any, Union

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: Union[str, None] = None


class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: Union[str, None] = None


@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
    return user

...мы объявили response_model как модель UserOut, в которой нет пароля:

from typing import Any

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None


class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None


@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
    return user
🤓 Other versions and variants
from typing import Any, Union

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: Union[str, None] = None


class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: Union[str, None] = None


@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
    return user

Таким образом, FastAPI позаботится о том, чтобы отфильтровать все данные, не объявленные в выходной модели (используя Pydantic).

response_model или возвращаемый тип

В этом случае, поскольку две модели различаются, если бы мы аннотировали возвращаемый тип функции как UserOut, редактор и инструменты пожаловались бы, что мы возвращаем неверный тип, так как это разные классы.

Поэтому в этом примере мы должны объявить тип ответа в параметре response_model.

...но читайте дальше, чтобы узнать, как это обойти.

Возвращаемый тип и фильтрация данных

Продолжим предыдущий пример. Мы хотели аннотировать функцию одним типом, но при этом иметь возможность вернуть из функции что-то, что фактически включает больше данных.

Мы хотим, чтобы FastAPI продолжал фильтровать данные с помощью модели ответа. Так что, даже если функция возвращает больше данных, в ответ будут включены только поля, объявленные в модели ответа.

В предыдущем примере, поскольку классы были разными, нам пришлось использовать параметр response_model. Но это также означает, что мы теряем поддержку от редактора и инструментов, проверяющих возвращаемый тип функции.

Однако в большинстве таких случаев нам нужно лишь отфильтровать/убрать некоторые данные, как в этом примере.

И в этих случаях мы можем использовать классы и наследование, чтобы воспользоваться аннотациями типов функций для лучшей поддержки в редакторе и инструментах и при этом получить фильтрацию данных от FastAPI.

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class BaseUser(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None


class UserIn(BaseUser):
    password: str


@app.post("/user/")
async def create_user(user: UserIn) -> BaseUser:
    return user
🤓 Other versions and variants
from typing import Union

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class BaseUser(BaseModel):
    username: str
    email: EmailStr
    full_name: Union[str, None] = None


class UserIn(BaseUser):
    password: str


@app.post("/user/")
async def create_user(user: UserIn) -> BaseUser:
    return user

Так мы получаем поддержку инструментов (редакторы, mypy) — код корректен с точки зрения типов — и одновременно получаем фильтрацию данных от FastAPI.

Как это работает? Давайте разберёмся. 🤓

Аннотации типов и инструменты

Сначала посмотрим, как это увидят редакторы, mypy и другие инструменты.

BaseUser содержит базовые поля. Затем UserIn наследуется от BaseUser и добавляет поле password, то есть он включает все поля обеих моделей.

Мы аннотируем возвращаемый тип функции как BaseUser, но фактически возвращаем экземпляр UserIn.

Редактор, mypy и другие инструменты не будут возражать, потому что с точки зрения типов UserIn — подкласс BaseUser, что означает, что это валидный тип везде, где ожидается что-то, являющееся BaseUser.

Фильтрация данных FastAPI

Теперь, для FastAPI: он увидит возвращаемый тип и убедится, что то, что вы возвращаете, включает только поля, объявленные в этом типе.

FastAPI делает несколько вещей внутри вместе с Pydantic, чтобы гарантировать, что те же правила наследования классов не используются для фильтрации возвращаемых данных, иначе вы могли бы вернуть гораздо больше данных, чем ожидали.

Таким образом вы получаете лучшее из обоих миров: аннотации типов с поддержкой инструментов и фильтрацию данных.

Посмотреть в документации

В автоматической документации вы увидите, что у входной и выходной моделей есть свои JSON Schema:

И обе модели используются в интерактивной документации API:

Другие аннотации возвращаемых типов

Бывают случаи, когда вы возвращаете что-то, что не является валидным полем Pydantic, и аннотируете это в функции только ради поддержки инструментов (редактор, mypy и т. д.).

Возврат Response напрямую

Самый распространённый случай — возвращать Response напрямую, как описано далее в разделах для продвинутых.

from fastapi import FastAPI, Response
from fastapi.responses import JSONResponse, RedirectResponse

app = FastAPI()


@app.get("/portal")
async def get_portal(teleport: bool = False) -> Response:
    if teleport:
        return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
    return JSONResponse(content={"message": "Here's your interdimensional portal."})

Этот простой случай обрабатывается FastAPI автоматически, потому что аннотация возвращаемого типа — это класс (или подкласс) Response.

И инструменты тоже будут довольны, потому что и RedirectResponse, и JSONResponse являются подклассами Response, так что аннотация типа корректна.

Аннотировать подкласс Response

Вы также можете использовать подкласс Response в аннотации типа:

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/teleport")
async def get_teleport() -> RedirectResponse:
    return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")

Это тоже сработает, так как RedirectResponse — подкласс Response, и FastAPI автоматически обработает этот случай.

Некорректные аннотации возвращаемых типов

Но когда вы возвращаете произвольный объект, не являющийся валидным типом Pydantic (например, объект базы данных), и аннотируете его таким образом в функции, FastAPI попытается создать модель ответа Pydantic из этой аннотации типа и потерпит неудачу.

То же произойдёт, если у вас будет что-то вроде union разных типов, где один или несколько не являются валидными типами Pydantic, например, это приведёт к ошибке 💥:

from fastapi import FastAPI, Response
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/portal")
async def get_portal(teleport: bool = False) -> Response | dict:
    if teleport:
        return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
    return {"message": "Here's your interdimensional portal."}
🤓 Other versions and variants
from typing import Union

from fastapi import FastAPI, Response
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/portal")
async def get_portal(teleport: bool = False) -> Union[Response, dict]:
    if teleport:
        return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
    return {"message": "Here's your interdimensional portal."}

...это не сработает, потому что аннотация типа не является типом Pydantic и это не единственный класс Response или его подкласс, а объединение (union) из Response и dict.

Отключить модель ответа

Продолжая пример выше, вы можете не хотеть использовать стандартную валидацию данных, документацию, фильтрацию и т.д., выполняемые FastAPI.

Но при этом вы можете хотеть сохранить аннотацию возвращаемого типа в функции, чтобы пользоваться поддержкой инструментов (редакторы, проверки типов вроде mypy).

В этом случае вы можете отключить генерацию модели ответа, установив response_model=None:

from fastapi import FastAPI, Response
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/portal", response_model=None)
async def get_portal(teleport: bool = False) -> Response | dict:
    if teleport:
        return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
    return {"message": "Here's your interdimensional portal."}
🤓 Other versions and variants
from typing import Union

from fastapi import FastAPI, Response
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/portal", response_model=None)
async def get_portal(teleport: bool = False) -> Union[Response, dict]:
    if teleport:
        return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
    return {"message": "Here's your interdimensional portal."}

Так FastAPI пропустит генерацию модели ответа, и вы сможете использовать любые аннотации возвращаемых типов, не влияя на ваше приложение FastAPI. 🤓

Параметры кодирования модели ответа

У вашей модели ответа могут быть значения по умолчанию, например:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float = 10.5
    tags: list[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
    return items[item_id]
🤓 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: float = 10.5
    tags: list[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
    return items[item_id]
from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: float = 10.5
    tags: List[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
    return items[item_id]
  • description: Union[str, None] = None (или str | None = None в Python 3.10) имеет значение по умолчанию None.
  • tax: float = 10.5 имеет значение по умолчанию 10.5.
  • tags: List[str] = [] имеет значение по умолчанию пустого списка: [].

но вы можете захотеть опустить их в результате, если они фактически не были сохранены.

Например, если у вас есть модели с множеством необязательных атрибутов в NoSQL-базе данных, но вы не хотите отправлять очень длинные JSON-ответы, заполненные значениями по умолчанию.

Используйте параметр response_model_exclude_unset

Вы можете установить у декоратора операции пути параметр response_model_exclude_unset=True:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float = 10.5
    tags: list[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
    return items[item_id]
🤓 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: float = 10.5
    tags: list[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
    return items[item_id]
from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: Union[str, None] = None
    price: float
    tax: float = 10.5
    tags: List[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
    return items[item_id]

и эти значения по умолчанию не будут включены в ответ — только те значения, которые действительно были установлены.

Итак, если вы отправите запрос к этой операции пути для элемента с ID foo, ответ (без значений по умолчанию) будет таким:

{
    "name": "Foo",
    "price": 50.2
}

Информация

В Pydantic v1 метод назывался .dict(), в Pydantic v2 он был помечен как устаревший (но всё ещё поддерживается) и переименован в .model_dump().

Примеры здесь используют .dict() для совместимости с Pydantic v1, но если вы используете Pydantic v2, применяйте .model_dump().

Информация

FastAPI использует метод .dict() у Pydantic-моделей с параметром exclude_unset, чтобы добиться такого поведения.

Информация

Вы также можете использовать:

  • response_model_exclude_defaults=True
  • response_model_exclude_none=True

как описано в документации Pydantic для exclude_defaults и exclude_none.

Данные со значениями для полей, имеющих значения по умолчанию

Но если в ваших данных есть значения для полей модели, для которых указаны значения по умолчанию, как у элемента с ID bar:

{
    "name": "Bar",
    "description": "The bartenders",
    "price": 62,
    "tax": 20.2
}

они будут включены в ответ.

Данные с такими же значениями, как значения по умолчанию

Если данные имеют те же значения, что и значения по умолчанию, как у элемента с ID baz:

{
    "name": "Baz",
    "description": None,
    "price": 50.2,
    "tax": 10.5,
    "tags": []
}

FastAPI достаточно умен (на самом деле, это Pydantic), чтобы понять, что хотя description, tax и tags совпадают со значениями по умолчанию, они были установлены явно (а не взяты из значений по умолчанию).

Поэтому они тоже будут включены в JSON-ответ.

Совет

Обратите внимание, что значения по умолчанию могут быть любыми, не только None.

Это может быть список ([]), число с плавающей точкой 10.5 и т. д.

response_model_include и response_model_exclude

Вы также можете использовать параметры декоратора операции пути response_model_include и response_model_exclude.

Они принимают set из str с именами атрибутов, которые нужно включить (исключив остальные) или исключить (оставив остальные).

Это можно использовать как быстрый способ, если у вас только одна Pydantic-модель и вы хотите убрать часть данных из ответа.

Совет

Но всё же рекомендуется использовать подходы выше — несколько классов — вместо этих параметров.

Потому что JSON Schema, генерируемая в OpenAPI вашего приложения (и документации), всё равно будет соответствовать полной модели, даже если вы используете response_model_include или response_model_exclude, чтобы опустить некоторые атрибуты.

То же относится к response_model_by_alias, который работает аналогично.

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float = 10.5


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
    "baz": {
        "name": "Baz",
        "description": "There goes my baz",
        "price": 50.2,
        "tax": 10.5,
    },
}


@app.get(
    "/items/{item_id}/name",
    response_model=Item,
    response_model_include={"name", "description"},
)
async def read_item_name(item_id: str):
    return items[item_id]


@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"tax"})
async def read_item_public_data(item_id: str):
    return items[item_id]
🤓 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: float = 10.5


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
    "baz": {
        "name": "Baz",
        "description": "There goes my baz",
        "price": 50.2,
        "tax": 10.5,
    },
}


@app.get(
    "/items/{item_id}/name",
    response_model=Item,
    response_model_include={"name", "description"},
)
async def read_item_name(item_id: str):
    return items[item_id]


@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"tax"})
async def read_item_public_data(item_id: str):
    return items[item_id]

Совет

Синтаксис {"name", "description"} создаёт set с этими двумя значениями.

Это эквивалентно set(["name", "description"]).

Использование list вместо set

Если вы забыли использовать set и применили list или tuple, FastAPI всё равно преобразует это в set, и всё будет работать корректно:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float = 10.5


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
    "baz": {
        "name": "Baz",
        "description": "There goes my baz",
        "price": 50.2,
        "tax": 10.5,
    },
}


@app.get(
    "/items/{item_id}/name",
    response_model=Item,
    response_model_include=["name", "description"],
)
async def read_item_name(item_id: str):
    return items[item_id]


@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude=["tax"])
async def read_item_public_data(item_id: str):
    return items[item_id]
🤓 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: float = 10.5


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
    "baz": {
        "name": "Baz",
        "description": "There goes my baz",
        "price": 50.2,
        "tax": 10.5,
    },
}


@app.get(
    "/items/{item_id}/name",
    response_model=Item,
    response_model_include=["name", "description"],
)
async def read_item_name(item_id: str):
    return items[item_id]


@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude=["tax"])
async def read_item_public_data(item_id: str):
    return items[item_id]

Резюме

Используйте параметр response_model у декоратора операции пути, чтобы задавать модели ответа, и особенно — чтобы приватные данные отфильтровывались.

Используйте response_model_exclude_unset, чтобы возвращать только те значения, которые были установлены явно.