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

**FastAPI** не вимагає від вас використовувати SQL (реляційну) базу даних. Але ви можете скористатися будь-якою базою даних, яку забажаєте.

Тут ми розглянемо приклад з [SQLModel](https://sqlmodel.tiangolo.com/).

**SQLModel** побудовано поверх [SQLAlchemy](https://www.sqlalchemy.org/) та 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**, включно з фронтендом та іншими інструментами: [https://github.com/fastapi/full-stack-fastapi-template](https://github.com/fastapi/full-stack-fastapi-template)

///

Це дуже простий і короткий навчальний посібник. Якщо ви хочете вивчити бази даних загалом, SQL або більш просунуті можливості, зверніться до [документації SQLModel](https://sqlmodel.tiangolo.com/).

## Встановіть `SQLModel` { #install-sqlmodel }

Спочатку переконайтеся, що ви створили [віртуальне оточення](../virtual-environments.md), активували його та встановили `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 базі даних (більше про первинні ключі в SQL див. у документації SQLModel).

    Примітка: Ми використовуємо `int | None` для поля первинного ключа, щоб у Python-коді можна було створити об’єкт без `id` (`id=None`), припускаючи, що база даних згенерує його під час збереження. SQLModel розуміє, що `id` надасть база даних, і визначає стовпець як ненульовий `INTEGER` у схемі бази даних. Докладніше див. [документацію SQLModel про первинні ключі](https://sqlmodel.tiangolo.com/tutorial/create-db-and-table/#primary-key-id).

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

    SQLModel знатиме, що оголошене як `str` стане SQL-стовпцем типу `TEXT` (або `VARCHAR`, залежно від бази).

### Створіть рушій { #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] *}

### Створіть залежність сесії { #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](https://alembic.sqlalchemy.org/en/latest/) напряму.

///

### Створіть героя { #create-a-hero }

Оскільки кожна модель SQLModel також є моделлю Pydantic, ви можете використовувати її в тих самих **анотаціях типів**, що і моделі Pydantic.

Наприклад, якщо ви оголосите параметр типу `Hero`, він буде прочитаний з **JSON-тіла**.

Так само ви можете оголосити її як **тип, що повертається** функції, і форма даних з’явиться в автоматичному 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

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

</div>

Потім перейдіть до інтерфейсу `/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

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

</div>

Якщо ви перейдете до UI `/docs`, побачите, що він оновився і більше не очікуватиме отримати `id` від клієнта під час створення героя тощо.

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

## Підсумок { #recap }

Ви можете використовувати [**SQLModel**](https://sqlmodel.tiangolo.com/) для взаємодії з SQL базою даних і спростити код за допомогою «моделей даних» та «табличних моделей».

Багато чого ще можна дізнатися в документації **SQLModel**, там є розширений міні-[навчальний посібник з використання SQLModel з **FastAPI**](https://sqlmodel.tiangolo.com/tutorial/fastapi/). 🚀
