Issue Manager / issue-manager (push) Has been cancelled
Build Docs / changes (push) Has been cancelled
Build Docs / langs (push) Has been cancelled
Build Docs / build-docs (push) Has been cancelled
Build Docs / docs-all-green (push) Has been cancelled
Conflict detector / main (push) Has been cancelled
Test Redistribute / test-redistribute (fastapi) (push) Has been cancelled
Test Redistribute / test-redistribute (fastapi-slim) (push) Has been cancelled
Test Redistribute / test-redistribute-alls-green (push) Has been cancelled
Test / lint (push) Has been cancelled
Test / test (pydantic-v1, 3.10) (push) Has been cancelled
Test / test (pydantic-v1, 3.11) (push) Has been cancelled
Test / test (pydantic-v1, 3.13) (push) Has been cancelled
Test / test (pydantic-v1, 3.8) (push) Has been cancelled
Test / test (pydantic-v1, 3.9) (push) Has been cancelled
Test / test (pydantic-v2, 3.10) (push) Has been cancelled
Test / test (pydantic-v2, 3.11) (push) Has been cancelled
Test / test (pydantic-v2, 3.12) (push) Has been cancelled
Test / test (pydantic-v2, 3.13) (push) Has been cancelled
Test / test (pydantic-v2, 3.14) (push) Has been cancelled
Test / test (pydantic-v2, 3.8) (push) Has been cancelled
Test / test (pydantic-v2, 3.9) (push) Has been cancelled
Test / coverage-combine (push) Has been cancelled
Test / check (push) Has been cancelled
Label Approved / label-approved (push) Has been cancelled
FastAPI People Contributors / job (push) Has been cancelled
FastAPI People Sponsors / job (push) Has been cancelled
Update Topic Repos / topic-repos (push) Has been cancelled
FastAPI People / job (push) Has been cancelled
Test / test (pydantic-v1, 3.12) (push) Has been cancelled
220 lines
12 KiB
Markdown
220 lines
12 KiB
Markdown
# Дополнительные модели { #extra-models }
|
||
|
||
В продолжение прошлого примера будет уже обычным делом иметь несколько связанных между собой моделей.
|
||
|
||
Это особенно применимо в случае моделей пользователя, потому что:
|
||
|
||
* **Модель для ввода** должна иметь возможность содержать пароль.
|
||
* **Модель для вывода** не должна содержать пароль.
|
||
* **Модель для базы данных**, возможно, должна содержать хэшированный пароль.
|
||
|
||
/// danger | Внимание
|
||
|
||
Никогда не храните пароли пользователей в чистом виде. Всегда храните "безопасный хэш", который вы затем сможете проверить.
|
||
|
||
Если вам это не знакомо, вы можете узнать про "хэш пароля" в [главах о безопасности](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}.
|
||
|
||
///
|
||
|
||
## Множественные модели { #multiple-models }
|
||
|
||
Ниже изложена основная идея того, как могут выглядеть эти модели с полями для паролей, а также описаны места, где они используются:
|
||
|
||
{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}
|
||
|
||
/// info | Информация
|
||
|
||
В Pydantic v1 метод назывался `.dict()`, в Pydantic v2 он помечен как устаревший (но всё ещё поддерживается) и переименован в `.model_dump()`.
|
||
|
||
В примерах здесь используется `.dict()` для совместимости с Pydantic v1, но если вы используете Pydantic v2, следует использовать `.model_dump()`.
|
||
|
||
///
|
||
|
||
### Про `**user_in.dict()` { #about-user-in-dict }
|
||
|
||
#### `.dict()` из Pydantic { #pydantics-dict }
|
||
|
||
`user_in` - это Pydantic-модель класса `UserIn`.
|
||
|
||
У Pydantic-моделей есть метод `.dict()`, который возвращает `dict` с данными модели.
|
||
|
||
Поэтому, если мы создадим Pydantic-объект `user_in` таким способом:
|
||
|
||
```Python
|
||
user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
|
||
```
|
||
|
||
и затем вызовем:
|
||
|
||
```Python
|
||
user_dict = user_in.dict()
|
||
```
|
||
|
||
то теперь у нас есть `dict` с данными модели в переменной `user_dict` (это `dict` вместо объекта Pydantic-модели).
|
||
|
||
И если мы вызовем:
|
||
|
||
```Python
|
||
print(user_dict)
|
||
```
|
||
|
||
мы можем получить `dict` с такими данными:
|
||
|
||
```Python
|
||
{
|
||
'username': 'john',
|
||
'password': 'secret',
|
||
'email': 'john.doe@example.com',
|
||
'full_name': None,
|
||
}
|
||
```
|
||
|
||
#### Распаковка `dict` { #unpacking-a-dict }
|
||
|
||
Если мы возьмём `dict` наподобие `user_dict` и передадим его в функцию (или класс), используя `**user_dict`, Python распакует его. Он передаст ключи и значения `user_dict` напрямую как аргументы типа ключ-значение.
|
||
|
||
Поэтому, продолжая описанный выше пример с `user_dict`, написание такого кода:
|
||
|
||
```Python
|
||
UserInDB(**user_dict)
|
||
```
|
||
|
||
Будет работать так же, как примерно такой код:
|
||
|
||
```Python
|
||
UserInDB(
|
||
username="john",
|
||
password="secret",
|
||
email="john.doe@example.com",
|
||
full_name=None,
|
||
)
|
||
```
|
||
|
||
Или, если для большей точности мы напрямую используем `user_dict` с любым потенциальным содержимым, то этот пример будет выглядеть так:
|
||
|
||
```Python
|
||
UserInDB(
|
||
username = user_dict["username"],
|
||
password = user_dict["password"],
|
||
email = user_dict["email"],
|
||
full_name = user_dict["full_name"],
|
||
)
|
||
```
|
||
|
||
#### Pydantic-модель из содержимого другой модели { #a-pydantic-model-from-the-contents-of-another }
|
||
|
||
Как в примере выше мы получили `user_dict` из `user_in.dict()`, этот код:
|
||
|
||
```Python
|
||
user_dict = user_in.dict()
|
||
UserInDB(**user_dict)
|
||
```
|
||
|
||
будет равнозначен такому:
|
||
|
||
```Python
|
||
UserInDB(**user_in.dict())
|
||
```
|
||
|
||
...потому что `user_in.dict()` - это `dict`, и затем мы указываем, чтобы Python его "распаковал", когда передаём его в `UserInDB` и ставим перед ним `**`.
|
||
|
||
Таким образом мы получаем Pydantic-модель на основе данных из другой Pydantic-модели.
|
||
|
||
#### Распаковка `dict` и дополнительные именованные аргументы { #unpacking-a-dict-and-extra-keywords }
|
||
|
||
И затем, если мы добавим дополнительный именованный аргумент `hashed_password=hashed_password` как здесь:
|
||
|
||
```Python
|
||
UserInDB(**user_in.dict(), hashed_password=hashed_password)
|
||
```
|
||
|
||
... то мы получим что-то подобное:
|
||
|
||
```Python
|
||
UserInDB(
|
||
username = user_dict["username"],
|
||
password = user_dict["password"],
|
||
email = user_dict["email"],
|
||
full_name = user_dict["full_name"],
|
||
hashed_password = hashed_password,
|
||
)
|
||
```
|
||
|
||
/// warning | Предупреждение
|
||
|
||
Вспомогательные функции `fake_password_hasher` и `fake_save_user` используются только для демонстрации возможного потока данных и, конечно, не обеспечивают настоящую безопасность.
|
||
|
||
///
|
||
|
||
## Сократите дублирование { #reduce-duplication }
|
||
|
||
Сокращение дублирования кода - это одна из главных идей **FastAPI**.
|
||
|
||
Поскольку дублирование кода повышает риск появления багов, проблем с безопасностью, проблем десинхронизации кода (когда вы обновляете код в одном месте, но не обновляете в другом), и т.д.
|
||
|
||
А все описанные выше модели используют много общих данных и дублируют названия атрибутов и типов.
|
||
|
||
Мы можем это улучшить.
|
||
|
||
Мы можем определить модель `UserBase`, которая будет базовой для остальных моделей. И затем мы можем создать подклассы этой модели, которые будут наследовать её атрибуты (объявления типов, валидацию, и т.п.).
|
||
|
||
Все операции конвертации, валидации, документации, и т.п. будут по-прежнему работать нормально.
|
||
|
||
В этом случае мы можем определить только различия между моделями (с `password` в чистом виде, с `hashed_password` и без пароля):
|
||
|
||
{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *}
|
||
|
||
## `Union` или `anyOf` { #union-or-anyof }
|
||
|
||
Вы можете определить ответ как `Union` из двух или более типов. Это означает, что ответ должен соответствовать одному из них.
|
||
|
||
Он будет определён в OpenAPI как `anyOf`.
|
||
|
||
Для этого используйте стандартную аннотацию типов в Python <a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>:
|
||
|
||
/// note | Примечание
|
||
|
||
При объявлении <a href="https://docs.pydantic.dev/latest/concepts/types/#unions" class="external-link" target="_blank">`Union`</a>, сначала указывайте наиболее детальные типы, затем менее детальные. В примере ниже более детальный `PlaneItem` стоит перед `CarItem` в `Union[PlaneItem, CarItem]`.
|
||
|
||
///
|
||
|
||
{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *}
|
||
|
||
### `Union` в Python 3.10 { #union-in-python-3-10 }
|
||
|
||
В этом примере мы передаём `Union[PlaneItem, CarItem]` в качестве значения аргумента `response_model`.
|
||
|
||
Поскольку мы передаём его как **значение аргумента** вместо того, чтобы поместить его в **аннотацию типа**, нам придётся использовать `Union` даже в Python 3.10.
|
||
|
||
Если оно было бы указано в аннотации типа, то мы могли бы использовать вертикальную черту как в примере:
|
||
|
||
```Python
|
||
some_variable: PlaneItem | CarItem
|
||
```
|
||
|
||
Но если мы помещаем его в `response_model=PlaneItem | CarItem` мы получим ошибку, потому что Python попытается произвести **некорректную операцию** между `PlaneItem` и `CarItem` вместо того, чтобы интерпретировать это как аннотацию типа.
|
||
|
||
## Список моделей { #list-of-models }
|
||
|
||
Таким же образом вы можете определять ответы как списки объектов.
|
||
|
||
Для этого используйте `typing.List` из стандартной библиотеки Python (или просто `list` в Python 3.9 и выше):
|
||
|
||
{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}
|
||
|
||
## Ответ с произвольным `dict` { #response-with-arbitrary-dict }
|
||
|
||
Вы также можете определить ответ, используя произвольный одноуровневый `dict` и определяя только типы ключей и значений без использования Pydantic-моделей.
|
||
|
||
Это полезно, если вы заранее не знаете корректных названий полей/атрибутов (которые будут нужны при использовании Pydantic-модели).
|
||
|
||
В этом случае вы можете использовать `typing.Dict` (или просто `dict` в Python 3.9 и выше):
|
||
|
||
{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}
|
||
|
||
## Резюме { #recap }
|
||
|
||
Используйте несколько Pydantic-моделей и свободно применяйте наследование для каждой из них.
|
||
|
||
Вам не обязательно иметь единственную модель данных для каждой сущности, если эта сущность должна иметь возможность быть в разных "состояниях". Как в случае с "сущностью" пользователя, у которого есть состояния с полями `password`, `password_hash` и без пароля.
|