# Дополнительные модели В продолжение прошлого примера будет уже обычным делом иметь несколько связанных между собой моделей. Это особенно применимо в случае моделей пользователя, потому что: * **Модель для ввода** должна иметь возможность содержать пароль. * **Модель для вывода** не должна содержать пароль. * **Модель для базы данных**, возможно, должна содержать хэшированный пароль. !!! danger "Внимание" Никогда не храните пароли пользователей в чистом виде. Всегда храните "безопасный хэш", который вы затем сможете проверить. Если вам это не знакомо, вы можете узнать про "хэш пароля" в [главах о безопасности](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}. ## Множественные модели Ниже изложена основная идея того, как могут выглядеть эти модели с полями для паролей, а также описаны места, где они используются: === "Python 3.10+" ```Python hl_lines="7 9 14 20 22 27-28 31-33 38-39" {!> ../../../docs_src/extra_models/tutorial001_py310.py!} ``` === "Python 3.8+" ```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41" {!> ../../../docs_src/extra_models/tutorial001.py!} ``` ### Про `**user_in.dict()` #### `.dict()` из Pydantic `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` Если мы возьмём `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-модель из содержимого другой модели Как в примере выше мы получили `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` и дополнительные именованные аргументы И затем, если мы добавим дополнительный именованный аргумент `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 "Предупреждение" Цель использованных в примере вспомогательных функций - не более чем демонстрация возможных операций с данными, но, конечно, они не обеспечивают настоящую безопасность. ## Сократите дублирование Сокращение дублирования кода - это одна из главных идей **FastAPI**. Поскольку дублирование кода повышает риск появления багов, проблем с безопасностью, проблем десинхронизации кода (когда вы обновляете код в одном месте, но не обновляете в другом), и т.д. А все описанные выше модели используют много общих данных и дублируют названия атрибутов и типов. Мы можем это улучшить. Мы можем определить модель `UserBase`, которая будет базовой для остальных моделей. И затем мы можем создать подклассы этой модели, которые будут наследовать её атрибуты (объявления типов, валидацию, и т.п.). Все операции конвертации, валидации, документации, и т.п. будут по-прежнему работать нормально. В этом случае мы можем определить только различия между моделями (с `password` в чистом виде, с `hashed_password` и без пароля): === "Python 3.10+" ```Python hl_lines="7 13-14 17-18 21-22" {!> ../../../docs_src/extra_models/tutorial002_py310.py!} ``` === "Python 3.8+" ```Python hl_lines="9 15-16 19-20 23-24" {!> ../../../docs_src/extra_models/tutorial002.py!} ``` ## `Union` или `anyOf` Вы можете определить ответ как `Union` из двух типов. Это означает, что ответ должен соответствовать одному из них. Он будет определён в OpenAPI как `anyOf`. Для этого используйте стандартные аннотации типов в Python `typing.Union`: !!! note "Примечание" При объявлении `Union`, сначала указывайте наиболее детальные типы, затем менее детальные. В примере ниже более детальный `PlaneItem` стоит перед `CarItem` в `Union[PlaneItem, CarItem]`. === "Python 3.10+" ```Python hl_lines="1 14-15 18-20 33" {!> ../../../docs_src/extra_models/tutorial003_py310.py!} ``` === "Python 3.8+" ```Python hl_lines="1 14-15 18-20 33" {!> ../../../docs_src/extra_models/tutorial003.py!} ``` ### `Union` в Python 3.10 В этом примере мы передаём `Union[PlaneItem, CarItem]` в качестве значения аргумента `response_model`. Поскольку мы передаём его как **значение аргумента** вместо того, чтобы поместить его в **аннотацию типа**, нам придётся использовать `Union` даже в Python 3.10. Если оно было бы указано в аннотации типа, то мы могли бы использовать вертикальную черту как в примере: ```Python some_variable: PlaneItem | CarItem ``` Но если мы помещаем его в `response_model=PlaneItem | CarItem` мы получим ошибку, потому что Python попытается произвести **некорректную операцию** между `PlaneItem` и `CarItem` вместо того, чтобы интерпретировать это как аннотацию типа. ## Список моделей Таким же образом вы можете определять ответы как списки объектов. Для этого используйте `typing.List` из стандартной библиотеки Python (или просто `list` в Python 3.9 и выше): === "Python 3.9+" ```Python hl_lines="18" {!> ../../../docs_src/extra_models/tutorial004_py39.py!} ``` === "Python 3.8+" ```Python hl_lines="1 20" {!> ../../../docs_src/extra_models/tutorial004.py!} ``` ## Ответ с произвольным `dict` Вы также можете определить ответ, используя произвольный одноуровневый `dict` и определяя только типы ключей и значений без использования Pydantic-моделей. Это полезно, если вы заранее не знаете корректных названий полей/атрибутов (которые будут нужны при использовании Pydantic-модели). В этом случае вы можете использовать `typing.Dict` (или просто `dict` в Python 3.9 и выше): === "Python 3.9+" ```Python hl_lines="6" {!> ../../../docs_src/extra_models/tutorial005_py39.py!} ``` === "Python 3.8+" ```Python hl_lines="1 8" {!> ../../../docs_src/extra_models/tutorial005.py!} ``` ## Резюме Используйте несколько Pydantic-моделей и свободно применяйте наследование для каждой из них. Вам не обязательно иметь единственную модель данных для каждой сущности, если эта сущность должна иметь возможность быть в разных "состояниях". Как в случае с "сущностью" пользователя, у которого есть состояния с полями `password`, `password_hash` и без пароля.