# SQL (реляционные) базы данных { #sql-relational-databases } **FastAPI** не требует использовать SQL (реляционную) базу данных. Но вы можете использовать любую базу данных, которую хотите. Здесь мы рассмотрим пример с использованием SQLModel. **SQLModel** построен поверх SQLAlchemy и Pydantic. Его создал тот же автор, что и **FastAPI**, чтобы он идеально подходил для приложений FastAPI, которым нужны **SQL базы данных**. /// tip | Подсказка Вы можете использовать любую другую библиотеку для работы с SQL или NoSQL базами данных (иногда их называют "ORMs"), FastAPI ничего не навязывает. 😎 /// Так как SQLModel основан на SQLAlchemy, вы можете легко использовать **любую поддерживаемую** SQLAlchemy базу данных (а значит, и поддерживаемую SQLModel), например: * PostgreSQL * MySQL * SQLite * Oracle * Microsoft SQL Server, и т.д. В этом примере мы будем использовать **SQLite**, потому что она использует один файл и имеет встроенную поддержку в Python. Так что вы можете скопировать этот пример и запустить его как есть. Позже, для продакшн-приложения, возможно, вы захотите использовать серверную базу данных, например **PostgreSQL**. /// tip | Подсказка Существует официальный генератор проектов на **FastAPI** и **PostgreSQL**, включающий frontend и другие инструменты: https://github.com/fastapi/full-stack-fastapi-template /// Это очень простое и короткое руководство. Если вы хотите узнать больше о базах данных в целом, об SQL или о более продвинутых возможностях, обратитесь к документации SQLModel. ## Установка `SQLModel` { #install-sqlmodel } Сначала убедитесь, что вы создали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, активировали его и затем установили `sqlmodel`:
```console $ pip install sqlmodel ---> 100% ```
## Создание приложения с единственной моделью { #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, но пока вы можете использовать Alembic напрямую. /// ### Создание героя (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 } Вы можете запустить приложение:
```console $ fastapi dev main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
Затем перейдите в UI `/docs`. Вы увидите, что **FastAPI** использует эти **модели** для **документирования** API, а также для **сериализации** и **валидации** данных.
## Обновление приложения с несколькими моделями { #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 } Вы можете снова запустить приложение:
```console $ fastapi dev main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
Если вы перейдёте в UI API `/docs`, вы увидите, что он обновился: теперь при создании героя он не ожидает получить `id` от клиента и т. д.
## Резюме { #recap } Вы можете использовать **SQLModel** для взаимодействия с SQL базой данных и упростить код с помощью *моделей данных* и *моделей-таблиц*. Гораздо больше вы можете узнать в документации **SQLModel**, там есть более подробный мини-туториал по использованию SQLModel с **FastAPI**. 🚀