# SQL (реляционные) базы данных { #sql-relational-databases }

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

Здесь мы рассмотрим пример с использованием <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, которым нужны **SQL базы данных**.

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

Вы можете использовать любую другую библиотеку для работы с SQL или NoSQL базами данных (иногда их называют <abbr title="Object Relational Mapper – Объектно-реляционный маппер: термин для библиотеки, где некоторые классы представляют SQL-таблицы, а экземпляры представляют строки в этих таблицах">"ORMs"</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` { #install-sqlmodel }

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

<div class="termy">

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

</div>

## Создание приложения с единственной моделью { #create-the-app-with-a-single-model }

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

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

### Создание моделей { #create-models }

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

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

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

Есть несколько отличий:

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

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

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

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

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

### Создание Engine { #create-an-engine }

Объект `engine` в SQLModel (под капотом это `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 в разных потоках. Это необходимо, так как **один запрос** может использовать **больше одного потока** (например, в зависимостях).

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

### Создание таблиц { #create-the-tables }

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

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

### Создание зависимости Session { #create-a-session-dependency }

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

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

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

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

### Создание таблиц базы данных при старте { #create-database-tables-on-startup }

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

{* ../../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) { #create-a-hero }

Так как каждая модель SQLModel также является моделью Pydantic, вы можете использовать её в тех же **аннотациях типов**, в которых используете модели Pydantic.

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

Аналогично вы можете объявить её как **тип возвращаемого значения** функции, и тогда форма данных отобразится в автоматически сгенерированном UI документации API.

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

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

### Чтение героев { #read-heroes }

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

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

### Чтение одного героя { #read-one-hero }

Мы можем **прочитать** одного `Hero`.

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

### Удаление героя { #delete-a-hero }

Мы также можем **удалить** `Hero`.

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

### Запуск приложения { #run-the-app }

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

<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>

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

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

## Обновление приложения с несколькими моделями { #update-the-app-with-multiple-models }

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

Если вы посмотрите на предыдущую версию, в UI видно, что до сих пор клиент мог сам задавать `id` создаваемого `Hero`. 😱

Так делать нельзя, иначе они могли бы перезаписать `id`, который уже присвоен в БД. Решение по `id` должно приниматься **бэкендом** или **базой данных**, а **не клиентом**.

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

Мы исправим это, добавив несколько **дополнительных моделей**. Здесь SQLModel раскроется во всей красе. ✨

### Создание нескольких моделей { #create-multiple-models }

В **SQLModel** любая модель с `table=True` — это **модель-таблица**.

Любая модель без `table=True` — это **модель данных**, по сути обычная модель Pydantic (с парой небольших дополнений). 🤓

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

#### `HeroBase` — базовый класс { #herobase-the-base-class }

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

* `name`
* `age`

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

#### `Hero` — *модель-таблица* { #hero-the-table-model }

Далее создадим `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-the-public-data-model }

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

У неё те же поля, что и у `HeroBase`, поэтому она не включает `secret_name`.

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

Также здесь заново объявляется `id: int`. Тем самым мы заключаем **контракт** с клиентами API: они всегда могут рассчитывать, что поле `id` присутствует и это `int` (никогда не `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-the-data-model-to-create-a-hero }

Теперь создадим модель `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-the-data-model-to-update-a-hero }

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

*Модель данных* `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` { #create-with-herocreate-and-return-a-heropublic }

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

Мы получаем в запросе *модель данных* `HeroCreate` и на её основе создаём *модель-таблицу* `Hero`.

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

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

{* ../../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` { #read-heroes-with-heropublic }

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

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

### Чтение одного героя с `HeroPublic` { #read-one-hero-with-heropublic }

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

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

### Обновление героя с `HeroUpdate` { #update-a-hero-with-heroupdate }

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

В коде мы получаем `dict` со всеми данными, отправленными клиентом — **только с данными, отправленными клиентом**, исключая любые значения, которые были бы там лишь как значения по умолчанию. Для этого мы используем `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] *}

### Снова удаление героя { #delete-a-hero-again }

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

Желание *«отрефакторить всё»* на этот раз останется неудовлетворённым. 😅

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

### Снова запустим приложение { #run-the-app-again }

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

<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>

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

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

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

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

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