# SQL (реляционные) базы данных

**FastAPI** не требует использования реляционной базы данных. Вы можете воспользоваться любой базой данных, которой хотите.

В этом разделе мы продемонстрируем, как работать с <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel</a>.

Библиотека **SQLModel** построена на основе <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> и Pydantic. Она была разработана автором **FastAPI** специально для приложений на основе FastAPI, которые используют **реляционные базы данных**.

/// tip | Подсказка

Вы можете воспользоваться любой библиотекой для работы с реляционными (SQL) или нереляционными (NoSQL) базами данных. (Их ещё называют <abbr title="ORM = Object Relational Mapper, этот термин для библиотеки, в которой классы представляют SQL-таблицы, а экземпляры классов представляют строки в этих таблицах.">**ORM**</abbr> библиотеками). FastAPI не принуждает вас к использованию чего-либо конкретного. 😎

///

В основе SQLModel лежит SQLAlchemy, поэтому вы спокойно можете использовать любую базу данных, поддерживаемую SQLAlchemy (и, соответственно, поддерживаемую SQLModel), например:

* PostgreSQL
* MySQL
* SQLite
* Oracle
* Microsoft SQL Server, и т.д.

В данном примере мы будем использовать базу данных **SQLite**, т.к. она состоит из единственного файла и поддерживается встроенными библиотеками Python. Таким образом, вы сможете скопировать данный пример и запустить его как он есть.

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

/// tip | Подсказка

Существует официальный генератор проектов на **FastAPI** и **PostgreSQL**, который также включает frontend и дополнительные инструменты <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">https://github.com/fastapi/full-stack-fastapi-template</a>

///

Это очень простое и короткое руководство, поэтому, если вы хотите узнать о базах данных в целом, об SQL, разобраться с более продвинутым функционалом, то воспользуйтесь <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">документацией SQLModel</a>.

## Установка `SQLModel`

Создайте виртуальное окружение [virtual environment](../virtual-environments.md){.internal-link target=_blank}, активируйте его и установите `sqlmodel`:

<div class="termy">

```console
$ pip install sqlmodel
---> 100%
```

</div>

## Создание приложения с единственной моделью

Мы начнем с создания наиболее простой первой версии нашего приложения с одной единственной моделью **SQLModel**.

В дальнейшем с помощью **дополнительных моделей** мы его улучшим и сделаем более безопасным и универсальным. 🤓

### Создание моделей

Импортируйте `SQLModel` и создайте модель базы данных:

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *}

Класс `Hero` очень напоминает модель Pydantic (фактически, под капотом, *это и есть модель Pydantic*).

Но есть и некоторые различия

* `table=True` для SQLModel означает, что это *модель-таблица*, которая должна представлять **таблицу** в реляционной базе данных. Это не просто *модель данных* (в отличие от обычного класса в Pydantic).

* `Field(primary_key=True)` для SQLModel означает, что поле `id` является первичным ключом в таблице базы данных (вы можете подробнее узнать о первичных ключах баз данных в документации по SQLModel).

    Тип `int | None` сигнализирует для SQLModel, что столбец таблицы базы данных должен иметь тип `INTEGER`, или иметь пустое значение  `NULL`.

* `Field(index=True)` для SQLModel означает, что нужно создать **SQL индекс** для данного столбца. Это обеспечит более быстрый поиск при чтении данных, отфильтрованных по данному столбцу.

    SQLModel будет знать, что данные типа `str`, будут представлены в базе данных как `TEXT` (или `VARCHAR`, в зависимости от типа базы данных).

### Создание соединения с базой данных (Engine)

В SQLModel объект соединения `engine` (по сути это `Engine` из SQLAlchemy) **содержит пул соединений** к базе данных.

Для обеспечения всех подключений приложения к одной базе данных нужен только один объект соединения `engine`.

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *}

Использование настройки `check_same_thread=False` позволяет FastAPI использовать одну и ту же SQLite базу данных в различных потоках (threads). Это необходимо, когда **один запрос** использует **более одного потока** (например, в зависимостях).

Не беспокойтесь, учитывая структуру кода, мы позже позаботимся о том, чтобы использовать **отдельную SQLModel-сессию на каждый отдельный запрос**, это как раз то, что пытается обеспечить `check_same_thread`.

### Создание таблиц

Далее мы добавляем функцию, использующую `SQLModel.metadata.create_all(engine)`, для того, чтобы создать **таблицы** для каждой из **моделей таблицы**.

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *}

### Создание зависимости Session

Сессия базы данных (**`Session`**) хранит **объекты в памяти** и отслеживает любые необходимые изменения в данных, а затем **использует `engine`** для коммуникации с базой данных.

Мы создадим FastAPI-**зависимость** с помощью `yield`, которая будет создавать новую сессию (Session) для каждого запроса. Это как раз и обеспечит использование отдельной сессии на каждый отдельный запрос. 🤓

Затем мы создадим объявленную (`Annotated`) зависимость `SessionDep`. Мы сделаем это для того, чтобы упростить остальной код, который будет использовать эту зависимость.

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30]  hl[25:27,30] *}

### Создание таблиц базы данных при запуске приложения

Мы будем создавать таблицы базы данных при запуске приложения.

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *}

В данном примере мы создаем таблицы при наступлении события запуска приложения.

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

/// tip | Подсказка

В SQLModel будут включены утилиты миграции, входящие в состав Alembic, но на данный момент вы просто можете использовать
<a href="https://alembic.sqlalchemy.org/en/latest/" class="external-link" target="_blank">Alembic</a> напрямую.

///

### Создание героя (Hero)

Каждая модель в SQLModel является также моделью Pydantic, поэтому вы можете использовать её при **объявлении типов**, точно также, как и модели Pydantic.

Например, при объявлении параметра типа `Hero`, она будет считана из **тела JSON**.

Точно также, вы можете использовать её при объявлении типа значения, возвращаемого функцией, и тогда структурированные данные будут отображены через пользовательский интерфейс автоматически сгенерированной документации FastAPI.

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *}

Мы используем зависимость `SessionDep` (сессию базы данных) для того, чтобы добавить нового героя `Hero` в объект сессии (`Session`), сохранить изменения в базе данных, обновить данные героя и затем вернуть их.

### Чтение данных о героях

Мы можем **читать** данные героев из базы данных с помощью `select()`. Мы можем включить `limit` и `offset` для постраничного считывания результатов.

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *}

### Чтение данных отдельного героя

Мы можем прочитать данные отдельного героя (`Hero`).

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *}

### Удаление данных героя

Мы также можем удалить героя `Hero` из базы данных.

{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *}

### Запуск приложения

Вы можете запустить приложение следующим образом:

<div class="termy">

```console
$ fastapi dev main.py

<span style="color: green;">INFO</span>:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```

</div>

Далее перейдите в пользовательский интерфейс API `/docs`. Вы увидите, что **FastAPI** использует модели для создания документации API. Эти же модели используются для сериализации и проверки данных.

<div class="screenshot">
<img src="/img/tutorial/sql-databases/image01.png">
</div>

## Добавление в приложение дополнительных (вспомогательных) моделей

Теперь давайте проведём **рефакторинг** нашего приложения, чтобы сделать его более безопасным и более универсальным.

Обратите внимание, что на данном этапе наше приложение позволяет на уровне клиента определять `id` создаваемого героя (`Hero`). 😱

Мы не можем этого допустить, т.к. существует риск переписать уже присвоенные `id` в базе данных. Присвоение `id` должно происходить **на уровне бэкэнда (backend)** или **на уровне базы данных**, но никак **не на уровне клиента**.

Кроме того, мы создаем секретное имя `secret_name` для героя, но пока что, мы возвращаем его повсеместно, и это слабо напоминает **секретность**... 😅

Мы поправим это с помощью нескольких дополнительных (вспомогательных) моделей. Вот где SQLModel по-настоящему покажет себя. ✨

### Создание дополнительных моделей

В **SQLModel**, любая модель с параметром `table=True` является **моделью таблицы**.

Любая модель, не содержащая `table=True` является **моделью данных**, это по сути обычные модели Pydantic (с несколько расширенным функционалом). 🤓

С помощью SQLModel мы можем использовать **наследование**, что поможет нам **избежать дублирования** всех полей.

#### Базовый класс `HeroBase`

Давайте начнём с модели `HeroBase`, которая содержит поля, общие для всех моделей:

* `name`
* `age`

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *}

#### Модель таблицы `Hero`

Далее давайте создадим **модель таблицы** `Hero` с дополнительными полями, которых может не быть в других моделях:

* `id`
* `secret_name`

Модель `Hero` наследует от `HeroBase`, и поэтому включает также поля из `HeroBase`. Таким образом, все поля, содержащиеся в `Hero`, будут следующими:

* `id`
* `name`
* `age`
* `secret_name`

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *}

#### Публичная модель данных `HeroPublic`

Далее мы создадим модель `HeroPublic`. Мы будем возвращать её клиентам API.

Она включает в себя те же поля, что и `HeroBase`, и, соответственно, поле `secret_name` в ней отсутствует.

Наконец-то личность наших героев защищена! 🥷

В модели `HeroPublic` также объявляется поле `id: int`. Мы как бы заключаем договоренность с API клиентом, на то, что передаваемые данные всегда должны содержать поле `id`, и это поле должно содержать целое число (и никогда не содержать `None`).

/// tip | Подсказка

Модель ответа, гарантирующая наличие поля со значением типа `int` (не `None`), очень полезна при разработке API клиентов. Определенность в передаваемых данных может обеспечить написание более простого кода.

Также **автоматически генерируемые клиенты** будут иметь более простой интерфейс. И в результате жизнь разработчиков, использующих ваш API, станет значительно легче. 😎

///

`HeroPublic` содержит все поля `HeroBase`, а также поле `id`, объявленное как `int` (не `None`):

* `id`
* `name`
* `age`

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *}

#### Модель для создания героя `HeroCreate`

Сейчас мы создадим модель `HeroCreate`. Эта модель будет использоваться для проверки данных, переданных клиентом.

Она содержит те же поля, что и `HeroBase`, а также поле `secret_name`.

Теперь, при создании нового героя, клиенты будут передавать секретное имя `secret_name`, которое будет сохранено в базе данных, но не будет возвращено в ответе API клиентам.

/// tip | Подсказка

Вот как нужно работать с **паролями**: получайте их, но не возвращайте их через API.

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

///

Поля модели `HeroCreate`:

* `name`
* `age`
* `secret_name`

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *}

#### Модель для обновления данных героя `HeroUpdate`

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

Модель данных `HeroUpdate` в некотором смысле особенная. Она содержит все те же поля, что и модель создания героя, но все поля модели являются **необязательными**. (Все они имеют значение по умолчанию.) Таким образом, при обновлении данных героя, вам достаточно передать только те поля, которые требуют изменения.

Поскольку **все поля по сути меняются** (теперь тип каждого поля допускает значение `None` и значение по умолчанию `None`), мы должны их **объявить заново**.

Фактически, нам не нужно наследоваться от `HeroBase`, потому что мы будем заново объявлять все поля. Я оставлю наследование просто для поддержания общего стиля, но оно (наследование) здесь необязательно. 🤷

Поля `HeroUpdate`:

* `name`
* `age`
* `secret_name`

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *}

### Создание героя с помощью `HeroCreate` и возвращение результатов с помощью `HeroPublic`

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

Вместе c запросом на создание героя мы получаем объект данных `HeroCreate`, и создаем на его основе объект модели таблицы `Hero`.

Созданный объект *модели таблицы* `Hero` будет иметь все поля, переданные клиентом, а также поле `id`, сгенерированное базой данных.

Далее функция вернёт объект *модели таблицы* `Hero`. Но поскольку, мы объявили `HeroPublic` как модель ответа, то **FastAPI** будет использовать именно её для проверки и сериализации данных.

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *}

/// tip | Подсказка

Теперь мы используем модель ответа `response_model=HeroPublic`, вместо того, чтобы объявить тип возвращаемого значения как `-> HeroPublic`. Мы это делаем потому, что тип возвращаемого значения не относится к `HeroPublic`.

Если бы мы объявили тип возвращаемого значения как `-> HeroPublic`, то редактор и линтер начали бы ругаться (и вполне справедливо), что возвращаемое значение принадлежит типу  `Hero`, а совсем не `HeroPublic`.

Объявляя модель ответа в `response_model`, мы как бы говорим **FastAPI**: делай свое дело, не вмешиваясь в аннотацию типов и не полагаясь на помощь редактора или других инструментов.

///

### Чтение данных героев с помощью `HeroPublic`

Мы можем проделать то же самое **для чтения данных** героев. Мы применим модель ответа `response_model=list[HeroPublic]`, и тем самым обеспечим правильную проверку и сериализацию данных.

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *}

### Чтение данных отдельного героя с помощью `HeroPublic`

Мы можем **прочитать** данные отдельного героя:

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *}

### Обновление данных героя с помощью `HeroUpdate`

Мы можем **обновить данные героя**. Для этого мы воспользуемся HTTP методом `PATCH`.

В коде мы получаем объект словаря `dict` с данными, переданными клиентом (т.е. **только c данными, переданными  клиентом**, исключая любые значения, которые могли бы быть там только потому, что они являются значениями по умолчанию). Для того чтобы сделать это, мы воспользуемся опцией `exclude_unset=True`. В этом главная хитрость. 🪄

Затем мы применим `hero_db.sqlmodel_update(hero_data)`, и обновим `hero_db`, использовав данные `hero_data`.

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *}

### Удалим героя ещё раз

Операция **удаления** героя практически не меняется.

В данном случае желание *`отрефакторить всё`* остаётся неудовлетворенным. 😅

{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *}

### Снова запустим приложение

Вы можете снова запустить приложение:

<div class="termy">

```console
$ fastapi dev main.py

<span style="color: green;">INFO</span>:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```

</div>

Если вы перейдете в пользовательский интерфейс API `/docs`, то вы увидите, что он был обновлен, и больше не принимает параметра `id` от клиента при создании нового героя, и т.д.

<div class="screenshot">
<img src="/img/tutorial/sql-databases/image02.png">
</div>

## Резюме

Вы можете использовать <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">**SQLModel**</a> для взаимодействия с реляционными базами данных, а также для упрощения работы с **моделями данных** и **моделями таблиц**.

Вы можете узнать гораздо больше информации в документации по **SQLModel**. Там вы найдете более подробное <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">мини-руководство по использованию  SQLModel с **FastAPI**</a>. 🚀
