# Дополнительные модели { #extra-models }

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

Это особенно применимо в случае моделей пользователя, потому что:

* **Модель для ввода** должна иметь возможность содержать пароль.
* **Модель для вывода** не должна содержать пароль.
* **Модель для базы данных**, возможно, должна содержать хэшированный пароль.

/// danger | Внимание

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

Если вам это не знакомо, вы можете узнать про "хэш пароля" в [главах о безопасности](security/simple-oauth2.md#password-hashing).

///

## Множественные модели { #multiple-models }

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

{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}

### Про `**user_in.model_dump()` { #about-user-in-model-dump }

#### `.model_dump()` из Pydantic { #pydantics-model-dump }

`user_in` — это Pydantic-модель класса `UserIn`.

У Pydantic-моделей есть метод `.model_dump()`, который возвращает `dict` с данными модели.

Поэтому, если мы создадим Pydantic-объект `user_in` таким способом:

```Python
user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
```

и затем вызовем:

```Python
user_dict = user_in.model_dump()
```

то теперь у нас есть `dict` с данными в переменной `user_dict` (это `dict` вместо объекта Pydantic-модели).

И если мы вызовем:

```Python
print(user_dict)
```

мы получим Python `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.model_dump()`, этот код:

```Python
user_dict = user_in.model_dump()
UserInDB(**user_dict)
```

будет равнозначен такому:

```Python
UserInDB(**user_in.model_dump())
```

...потому что `user_in.model_dump()` — это `dict`, и затем мы указываем, чтобы Python его "распаковал", когда передаём его в `UserInDB` с префиксом `**`.

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

#### Распаковка `dict` и дополнительные именованные аргументы { #unpacking-a-dict-and-extra-keywords }

И затем, если мы добавим дополнительный именованный аргумент `hashed_password=hashed_password` как здесь:

```Python
UserInDB(**user_in.model_dump(), 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 }

Вы можете объявить HTTP-ответ как `Union` из двух или более типов. Это означает, что HTTP-ответ может быть любым из них.

Он будет определён в OpenAPI как `anyOf`.

Для этого используйте стандартную аннотацию типов в Python [`typing.Union`](https://docs.python.org/3/library/typing.html#typing.Union):

/// note | Примечание

При объявлении [`Union`](https://docs.pydantic.dev/latest/concepts/types/#unions) сначала указывайте наиболее специфичный тип, затем менее специфичный. В примере ниже более специфичный `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 }

Таким же образом вы можете объявлять HTTP-ответы, возвращающие списки объектов.

Для этого используйте стандартный `list` в Python:

{* ../../docs_src/extra_models/tutorial004_py310.py hl[18] *}

## Ответ с произвольным `dict` { #response-with-arbitrary-dict }

Вы также можете объявить HTTP-ответ, используя обычный произвольный `dict`, объявив только тип ключей и значений, без использования Pydantic-модели.

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

В этом случае вы можете использовать `dict`:

{* ../../docs_src/extra_models/tutorial005_py310.py hl[6] *}

## Резюме { #recap }

Используйте несколько Pydantic-моделей и свободно применяйте наследование для каждого случая.

Вам не обязательно иметь единственную модель данных для каждой сущности, если эта сущность должна иметь возможность быть в разных "состояниях". Как в случае с "сущностью" пользователя, у которого есть состояние, включающее `password`, `password_hash` и отсутствие пароля.
