# Дополнительные модели

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

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

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

!!! 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 <a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>:

!!! note "Примечание"
    При объявлении <a href="https://pydantic-docs.helpmanual.io/usage/types/#unions" class="external-link" target="_blank">`Union`</a>, сначала указывайте наиболее детальные типы, затем менее детальные. В примере ниже более детальный `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` и без пароля.
