mirror of https://github.com/tiangolo/fastapi.git
344 lines
25 KiB
Markdown
344 lines
25 KiB
Markdown
# Модель ответа — Возвращаемый тип { #response-model-return-type }
|
||
|
||
Вы можете объявить тип, используемый для ответа, указав аннотацию **возвращаемого значения** для *функции-обработчика пути*.
|
||
|
||
Вы можете использовать **аннотации типов** так же, как и для входных данных в **параметрах** функции: Pydantic-модели, списки, словари, скалярные значения (целые числа, булевы и т.д.).
|
||
|
||
{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *}
|
||
|
||
FastAPI будет использовать этот возвращаемый тип, чтобы:
|
||
|
||
* **Валидировать** возвращаемые данные.
|
||
* Если данные невалидны (например, отсутствует поле), это означает, что код *вашего* приложения работает некорректно и возвращает не то, что должен. В таком случае будет возвращена ошибка сервера вместо неправильных данных. Так вы и ваши клиенты можете быть уверены, что получите ожидаемые данные и ожидаемую структуру данных.
|
||
* Добавить **JSON Schema** для ответа в OpenAPI *операции пути*.
|
||
* Это будет использовано **автоматической документацией**.
|
||
* Это также будет использовано инструментами автоматической генерации клиентского кода.
|
||
|
||
Но самое главное:
|
||
|
||
* Выходные данные будут **ограничены и отфильтрованы** в соответствии с тем, что определено в возвращаемом типе.
|
||
* Это особенно важно для **безопасности**, ниже мы рассмотрим это подробнее.
|
||
|
||
## Параметр `response_model` { #response-model-parameter }
|
||
|
||
Бывают случаи, когда вам нужно или хочется возвращать данные, которые не в точности соответствуют объявленному типу.
|
||
|
||
Например, вы можете хотеть **возвращать словарь** или объект из базы данных, но **объявить его как Pydantic-модель**. Тогда Pydantic-модель выполнит документирование данных, валидацию и т.п. для объекта, который вы вернули (например, словаря или объекта из базы данных).
|
||
|
||
Если вы добавите аннотацию возвращаемого типа, инструменты и редакторы кода начнут жаловаться (и будут правы), что функция возвращает тип (например, dict), отличный от объявленного (например, Pydantic-модель).
|
||
|
||
В таких случаях вместо аннотации возвращаемого типа можно использовать параметр `response_model` у *декоратора операции пути*.
|
||
|
||
Параметр `response_model` можно указать у любой *операции пути*:
|
||
|
||
* `@app.get()`
|
||
* `@app.post()`
|
||
* `@app.put()`
|
||
* `@app.delete()`
|
||
* и т.д.
|
||
|
||
{* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *}
|
||
|
||
/// note | Примечание
|
||
|
||
Обратите внимание, что `response_model` — это параметр метода «декоратора» (`get`, `post` и т.д.), а не вашей *функции-обработчика пути*, в которой указываются параметры и тело запроса.
|
||
|
||
///
|
||
|
||
`response_model` принимает тот же тип, что вы бы объявили для поля Pydantic-модели, то есть это может быть одна Pydantic-модель, а может быть, например, `list` Pydantic-моделей, как `List[Item]`.
|
||
|
||
FastAPI будет использовать этот `response_model` для документирования, валидации данных и т.п., а также для **конвертации и фильтрации выходных данных** к объявленному типу.
|
||
|
||
/// tip | Совет
|
||
|
||
Если у вас в редакторе кода, mypy и т.п. включены строгие проверки типов, вы можете объявить возвращаемый тип функции как `Any`.
|
||
|
||
Так вы сообщите редактору, что намеренно возвращаете что угодно. Но FastAPI всё равно выполнит документирование, валидацию, фильтрацию данных и т.д. с помощью `response_model`.
|
||
|
||
///
|
||
|
||
### Приоритет `response_model` { #response-model-priority }
|
||
|
||
Если вы объявите и возвращаемый тип, и `response_model`, приоритет будет у `response_model`, именно его использует FastAPI.
|
||
|
||
Так вы можете добавить корректные аннотации типов к своим функциям, даже если фактически возвращаете тип, отличный от модели ответа, чтобы ими пользовались редактор кода и инструменты вроде mypy. И при этом FastAPI продолжит выполнять валидацию данных, документацию и т.д. с использованием `response_model`.
|
||
|
||
Вы также можете указать `response_model=None`, чтобы отключить создание модели ответа для данной *операции пути*. Это может понадобиться, если вы добавляете аннотации типов для вещей, не являющихся валидными полями Pydantic. Пример вы увидите ниже.
|
||
|
||
## Вернуть те же входные данные { #return-the-same-input-data }
|
||
|
||
Здесь мы объявляем модель `UserIn`, она будет содержать пароль в открытом виде:
|
||
|
||
{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *}
|
||
|
||
/// info | Информация
|
||
|
||
Чтобы использовать `EmailStr`, сначала установите <a href="https://github.com/JoshData/python-email-validator" class="external-link" target="_blank">`email-validator`</a>.
|
||
|
||
Убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его, а затем установите пакет, например:
|
||
|
||
```console
|
||
$ pip install email-validator
|
||
```
|
||
|
||
или так:
|
||
|
||
```console
|
||
$ pip install "pydantic[email]"
|
||
```
|
||
|
||
///
|
||
|
||
И мы используем эту модель для объявления входных данных, и ту же модель — для объявления выходных данных:
|
||
|
||
{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *}
|
||
|
||
Теперь, когда браузер создаёт пользователя с паролем, API вернёт тот же пароль в ответе.
|
||
|
||
В этом случае это может быть не проблемой, так как пароль отправляет тот же пользователь.
|
||
|
||
Но если мы используем ту же модель в другой *операции пути*, мы можем начать отправлять пароли пользователей каждому клиенту.
|
||
|
||
/// danger | Осторожно
|
||
|
||
Никогда не храните пароль пользователя в открытом виде и не отправляйте его в ответе подобным образом, если только вы не понимаете всех рисков и точно знаете, что делаете.
|
||
|
||
///
|
||
|
||
## Добавить выходную модель { #add-an-output-model }
|
||
|
||
Вместо этого мы можем создать входную модель с паролем в открытом виде и выходную модель без него:
|
||
|
||
{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *}
|
||
|
||
Здесь, хотя *функция-обработчик пути* возвращает тот же входной объект пользователя, содержащий пароль:
|
||
|
||
{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *}
|
||
|
||
...мы объявили `response_model` как модель `UserOut`, в которой нет пароля:
|
||
|
||
{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *}
|
||
|
||
Таким образом, **FastAPI** позаботится о том, чтобы отфильтровать все данные, не объявленные в выходной модели (используя Pydantic).
|
||
|
||
### `response_model` или возвращаемый тип { #response-model-or-return-type }
|
||
|
||
В этом случае, поскольку две модели различаются, если бы мы аннотировали возвращаемый тип функции как `UserOut`, редактор кода и инструменты пожаловались бы, что мы возвращаем неверный тип, так как это разные классы.
|
||
|
||
Поэтому в этом примере мы должны объявить тип ответа в параметре `response_model`.
|
||
|
||
...но читайте дальше, чтобы узнать, как это обойти.
|
||
|
||
## Возвращаемый тип и фильтрация данных { #return-type-and-data-filtering }
|
||
|
||
Продолжим предыдущий пример. Мы хотели **аннотировать функцию одним типом**, но при этом иметь возможность вернуть из функции что-то, что фактически включает **больше данных**.
|
||
|
||
Мы хотим, чтобы FastAPI продолжал **фильтровать** данные с помощью модели ответа. Так что, даже если функция возвращает больше данных, в ответ будут включены только поля, объявленные в модели ответа.
|
||
|
||
В предыдущем примере, поскольку классы были разными, нам пришлось использовать параметр `response_model`. Но это также означает, что мы теряем поддержку от редактора кода и инструментов, проверяющих возвращаемый тип функции.
|
||
|
||
Однако в большинстве таких случаев нам нужно лишь **отфильтровать/убрать** некоторые данные, как в этом примере.
|
||
|
||
И в этих случаях мы можем использовать классы и наследование, чтобы воспользоваться **аннотациями типов** функций для лучшей поддержки в редакторе кода и инструментах и при этом получить **фильтрацию данных** от FastAPI.
|
||
|
||
{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *}
|
||
|
||
Так мы получаем поддержку инструментов — редакторов кода и mypy, так как этот код корректен с точки зрения типов — и одновременно получаем фильтрацию данных от FastAPI.
|
||
|
||
Как это работает? Давайте разберёмся. 🤓
|
||
|
||
### Аннотации типов и инструменты { #type-annotations-and-tooling }
|
||
|
||
Сначала посмотрим, как это увидят редактор кода, mypy и другие инструменты.
|
||
|
||
`BaseUser` содержит базовые поля. Затем `UserIn` наследуется от `BaseUser` и добавляет поле `password`, то есть он будет включать все поля обеих моделей.
|
||
|
||
Мы аннотируем возвращаемый тип функции как `BaseUser`, но фактически возвращаем экземпляр `UserIn`.
|
||
|
||
Редактор кода, mypy и другие инструменты не будут возражать, потому что с точки зрения типов `UserIn` — подкласс `BaseUser`, что означает, что это *валидный* тип везде, где ожидается что-то, являющееся `BaseUser`.
|
||
|
||
### Фильтрация данных FastAPI { #fastapi-data-filtering }
|
||
|
||
Теперь для FastAPI: он увидит возвращаемый тип и убедится, что то, что вы возвращаете, включает **только** поля, объявленные в этом типе.
|
||
|
||
FastAPI делает несколько вещей внутри вместе с Pydantic, чтобы гарантировать, что те же правила наследования классов не используются для фильтрации возвращаемых данных, иначе вы могли бы в итоге вернуть намного больше данных, чем ожидали.
|
||
|
||
Таким образом вы получаете лучшее из обоих миров: аннотации типов с **поддержкой инструментов** и **фильтрацию данных**.
|
||
|
||
## Посмотреть в документации { #see-it-in-the-docs }
|
||
|
||
В автоматической документации вы увидите, что у входной и выходной моделей есть свои JSON Schema:
|
||
|
||
<img src="/img/tutorial/response-model/image01.png">
|
||
|
||
И обе модели будут использоваться в интерактивной документации API:
|
||
|
||
<img src="/img/tutorial/response-model/image02.png">
|
||
|
||
## Другие аннотации возвращаемых типов { #other-return-type-annotations }
|
||
|
||
Бывают случаи, когда вы возвращаете что-то, что не является валидным полем Pydantic, и аннотируете это в функции только ради поддержки инструментов (редактор кода, mypy и т.д.).
|
||
|
||
### Возврат Response напрямую { #return-a-response-directly }
|
||
|
||
Самый распространённый случай — [возвращать Response напрямую, как описано далее в разделах документации для продвинутых](../advanced/response-directly.md){.internal-link target=_blank}.
|
||
|
||
{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *}
|
||
|
||
Этот простой случай обрабатывается FastAPI автоматически, потому что аннотация возвращаемого типа — это класс (или подкласс) `Response`.
|
||
|
||
И инструменты тоже будут довольны, потому что и `RedirectResponse`, и `JSONResponse` являются подклассами `Response`, так что аннотация типа корректна.
|
||
|
||
### Аннотировать подкласс Response { #annotate-a-response-subclass }
|
||
|
||
Вы также можете использовать подкласс `Response` в аннотации типа:
|
||
|
||
{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *}
|
||
|
||
Это тоже сработает, так как `RedirectResponse` — подкласс `Response`, и FastAPI автоматически обработает этот простой случай.
|
||
|
||
### Некорректные аннотации возвращаемых типов { #invalid-return-type-annotations }
|
||
|
||
Но когда вы возвращаете произвольный объект, не являющийся валидным типом Pydantic (например, объект базы данных), и аннотируете его таким образом в функции, FastAPI попытается создать модель ответа Pydantic из этой аннотации типа и потерпит неудачу.
|
||
|
||
То же произойдёт, если у вас будет что-то вроде <abbr title='Объединение нескольких типов означает «любой из этих типов».'>union</abbr> разных типов, где один или несколько не являются валидными типами Pydantic, например, это приведёт к ошибке 💥:
|
||
|
||
{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *}
|
||
|
||
...это не сработает, потому что аннотация типа не является типом Pydantic и это не единственный класс `Response` или его подкласс, а объединение (`union`) из `Response` и `dict`.
|
||
|
||
### Отключить модель ответа { #disable-response-model }
|
||
|
||
Продолжая пример выше, вы можете не хотеть использовать стандартные валидацию данных, документирование, фильтрацию и т.п., выполняемые FastAPI.
|
||
|
||
Но при этом вы можете хотеть сохранить аннотацию возвращаемого типа в функции, чтобы пользоваться поддержкой инструментов вроде редакторов кода и инструментов проверки типов (например, mypy).
|
||
|
||
В этом случае вы можете отключить генерацию модели ответа, установив `response_model=None`:
|
||
|
||
{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *}
|
||
|
||
Так FastAPI пропустит генерацию модели ответа, и вы сможете использовать любые аннотации возвращаемых типов, которые вам нужны, без влияния на ваше приложение FastAPI. 🤓
|
||
|
||
## Параметры кодирования модели ответа { #response-model-encoding-parameters }
|
||
|
||
У вашей модели ответа могут быть значения по умолчанию, например:
|
||
|
||
{* ../../docs_src/response_model/tutorial004_py310.py hl[9,11:12] *}
|
||
|
||
* `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` { #use-the-response-model-exclude-unset-parameter }
|
||
|
||
Вы можете установить у *декоратора операции пути* параметр `response_model_exclude_unset=True`:
|
||
|
||
{* ../../docs_src/response_model/tutorial004_py310.py hl[22] *}
|
||
|
||
и эти значения по умолчанию не будут включены в ответ — только те значения, которые действительно были установлены.
|
||
|
||
Итак, если вы отправите запрос к этой *операции пути* для элемента с ID `foo`, ответ (без значений по умолчанию) будет таким:
|
||
|
||
```JSON
|
||
{
|
||
"name": "Foo",
|
||
"price": 50.2
|
||
}
|
||
```
|
||
|
||
/// info | Информация
|
||
|
||
Вы также можете использовать:
|
||
|
||
* `response_model_exclude_defaults=True`
|
||
* `response_model_exclude_none=True`
|
||
|
||
как описано в <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">документации Pydantic</a> для `exclude_defaults` и `exclude_none`.
|
||
|
||
///
|
||
|
||
#### Данные со значениями для полей, имеющих значения по умолчанию { #data-with-values-for-fields-with-defaults }
|
||
|
||
Но если в ваших данных есть значения для полей модели, для которых указаны значения по умолчанию, как у элемента с ID `bar`:
|
||
|
||
```Python hl_lines="3 5"
|
||
{
|
||
"name": "Bar",
|
||
"description": "The bartenders",
|
||
"price": 62,
|
||
"tax": 20.2
|
||
}
|
||
```
|
||
|
||
они будут включены в ответ.
|
||
|
||
#### Данные с такими же значениями, как значения по умолчанию { #data-with-the-same-values-as-the-defaults }
|
||
|
||
Если данные имеют те же значения, что и значения по умолчанию, как у элемента с ID `baz`:
|
||
|
||
```Python hl_lines="3 5-6"
|
||
{
|
||
"name": "Baz",
|
||
"description": None,
|
||
"price": 50.2,
|
||
"tax": 10.5,
|
||
"tags": []
|
||
}
|
||
```
|
||
|
||
FastAPI достаточно умен (на самом деле, это Pydantic), чтобы понять, что хотя `description`, `tax` и `tags` совпадают со значениями по умолчанию, они были установлены явно (а не взяты из значений по умолчанию).
|
||
|
||
Поэтому они тоже будут включены в JSON-ответ.
|
||
|
||
/// tip | Совет
|
||
|
||
Обратите внимание, что значения по умолчанию могут быть любыми, не только `None`.
|
||
|
||
Это может быть список (`[]`), число с плавающей точкой `10.5` и т.д.
|
||
|
||
///
|
||
|
||
### `response_model_include` и `response_model_exclude` { #response-model-include-and-response-model-exclude }
|
||
|
||
Вы также можете использовать параметры *декоратора операции пути* `response_model_include` и `response_model_exclude`.
|
||
|
||
Они принимают `set` из `str` с именами атрибутов, которые нужно включить (исключив остальные) или исключить (оставив остальные).
|
||
|
||
Это можно использовать как быстрый способ, если у вас только одна Pydantic-модель и вы хотите убрать часть данных из ответа.
|
||
|
||
/// tip | Совет
|
||
|
||
Но всё же рекомендуется использовать подходы выше — несколько классов — вместо этих параметров.
|
||
|
||
Потому что JSON Schema, генерируемая в OpenAPI вашего приложения (и документации), всё равно будет соответствовать полной модели, даже если вы используете `response_model_include` или `response_model_exclude`, чтобы опустить некоторые атрибуты.
|
||
|
||
То же относится к `response_model_by_alias`, который работает аналогично.
|
||
|
||
///
|
||
|
||
{* ../../docs_src/response_model/tutorial005_py310.py hl[29,35] *}
|
||
|
||
/// tip | Совет
|
||
|
||
Синтаксис `{"name", "description"}` создаёт `set` с этими двумя значениями.
|
||
|
||
Это эквивалентно `set(["name", "description"])`.
|
||
|
||
///
|
||
|
||
#### Использование `list` вместо `set` { #using-lists-instead-of-sets }
|
||
|
||
Если вы забыли использовать `set` и применили `list` или `tuple` вместо него, FastAPI всё равно преобразует это в `set`, и всё будет работать корректно:
|
||
|
||
{* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *}
|
||
|
||
## Резюме { #recap }
|
||
|
||
Используйте параметр `response_model` у *декоратора операции пути*, чтобы задавать модели ответа, и особенно — чтобы приватные данные отфильтровывались.
|
||
|
||
Используйте `response_model_exclude_unset`, чтобы возвращать только те значения, которые были установлены явно.
|