Ось готовий урок, написаний у стилі CS50, адаптований під тему SQLAlchemy та FastAPI.
🎓 CS50-style: SQLAlchemy та FastAPI — Будуємо міст до даних
Вітаю, друзі! 👋
Сьогодні ми не просто пишемо код. Сьогодні ми вчимося "дружити" дві різні галактики. З одного боку у нас FastAPI — швидкий, сучасний Python-фреймворк. З іншого — База Даних (наприклад, PostgreSQL), яка розмовляє суворою мовою SQL.
Як змусити їх розуміти одне одного без болю? Відповідь — SQLAlchemy. Поїхали!
1. 🔥 Вступ: проблема та мотивація
Уявіть, що ви приїхали в країну, мови якої зовсім не знаєте (скажімо, Японію). Ви хочете замовити їжу в ресторані. У вас є два шляхи:
- Вчити японську (SQL) з нуля: Вивчати ієрогліфи, граматику, суфікси ввічливості, щоб сказати: "Будь ласка, принесіть мені суші з лососем, але без васабі". Якщо помилитесь в одному символі — отримаєте не суші, а рахунок за оренду ресторану.
- Використати ідеального перекладача (ORM): Ви кажете своєю рідною мовою (Python): "Хочу суші". Перекладач сам формулює ідеальну японську фразу і передає її шеф-кухарю.
Риторичне питання: Ви хочете витрачати час на написання довгих SQL-запитів вручну всередині Python-коду, ризикуючи помилками?
# Як це виглядає без ORM (Біль і страждання)
cursor.execute("INSERT INTO users (name, email) VALUES ('" + name + "', '" + email + "')")
# А якщо в змінній name буде лапка? Все зламається! 😱
Чому без цього не обійтись? SQLAlchemy — це ваш перекладач (ORM — Object-Relational Mapping). Вона дозволяє працювати з базами даних, використовуючи звичайні Python-класи та об'єкти. Ви змінюєте властивість об'єкта — SQLAlchemy оновлює запис у базі. Це безпечно, чисто і професійно.
2. 🧠 Теоретична база (без нудних лекцій)
Давайте розберемо, що відбувається "під капотом", використовуючи прості механічні аналогії.
Щоб підключити FastAPI до бази, нам потрібні три ключові компоненти:
1. Engine (Двигун) 🚂
Це серце нашої системи. Двигун знає, де знаходиться база (її адресу) і як до неї достукатися (логін/пароль). * Аналогія: Це головна труба водопостачання, яка входить у ваш будинок. Вона одна на весь застосунок.
2. Session (Сесія) 🛒
Двигун дає з'єднання, але ми не працюємо з ним напряму постійно. Ми створюємо "Сесії".
* Аналогія: Уявіть супермаркет. Двигун — це сама будівля магазину. Сесія — це ваш особистий візок.
* Ви ходите (робите запити), кладете товари (дані) у візок.
* Поки ви не підійдете до каси і не скажете "Сплатити" (команда commit), товари не стануть вашими остаточно.
* Важливо: Для кожного запиту користувача (request) ми створюємо нову, чисту сесію, а після завершення — закриваємо її.
3. Base (Основа / Креслення) 📜
Це спеціальний клас, від якого будуть успадковуватися всі наші моделі (таблиці). * Аналогія: Це шаблон документу. Якщо ви хочете створити нову таблицю, ви кажете: "Вона має бути такою, як Base".
Що треба запам'ятати залізно: * Engine створюємо один раз. * Session створюємо для кожного запиту окремо.
3. 🧪 Приклади (від простого до реального)
Ми будемо використовувати сучасний підхід: Asynchronous SQLAlchemy (асинхронність), адже FastAPI створений для швидкості!
Крок 0: Підготовка
Вам знадобляться бібліотеки. У терміналі:
pip install sqlalchemy asyncpg
(asyncpg — це найшвидший драйвер для PostgreSQL).
Крок 1: Файл database.py
Створимо окремий файл, щоб не засмічувати main.py.
# database.py
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker, declarative_base
# 1. Рядок підключення (Connection String)
# Формат: postgresql+asyncpg://user:password@localhost/dbname
DATABASE_URL = "postgresql+asyncpg://postgres:password@localhost/mydatabase"
# 2. Створюємо Двигун (Engine)
# echo=True змусить SQLAlchemy писати в консоль всі SQL-запити (корисно для навчання!)
engine = create_async_engine(DATABASE_URL, echo=True)
# 3. Фабрика сесій (Session Factory)
# Це "генератор візків" для супермаркету.
SessionLocal = sessionmaker(
bind=engine,
class_=AsyncSession,
expire_on_commit=False
)
# 4. Базовий клас для моделей
Base = declarative_base()
❓ Що ви очікуєте побачити? Поки що нічого не відбудеться. Ми просто налаштували "трубопровід".
Крок 2: Залежність для FastAPI (Dependency)
Як нам дати кожному запиту користувача його власну сесію?
# database.py (продовження)
from typing import AsyncGenerator
# Це функція-помічник
async def get_db() -> AsyncGenerator:
async with SessionLocal() as session:
try:
yield session # 1. Даємо сесію (відкриваємо візок)
finally:
await session.close() # 2. Забираємо і закриваємо (повертаємо візок на місце)
Пояснення: Ключове слово yield тут працює як пауза. FastAPI візьме сесію, виконає запит, а потім повернеться сюди і виконає блок finally, щоб закрити з'єднання. Це гарантує, що ми не "покладемо" базу тисячами незакритих з'єднань.
Крок 3: Створюємо модель (Таблицю)
# models.py
from sqlalchemy import Column, Integer, String
from database import Base
class User(Base):
__tablename__ = "users" # Назва таблиці в SQL
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
email = Column(String)
Дивіться, як це просто! Ми описали Python-клас, а SQLAlchemy буде сприймати це як SQL-таблицю.
Крок 4: Збираємо все в main.py
# main.py
from fastapi import FastAPI, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from database import engine, Base, get_db
from models import User
app = FastAPI()
# УВАГА: У реальному житті ми використовуємо Alembic для міграцій.
# Але для навчання ми попросимо двигун створити таблиці при запуску.
@app.on_event("startup")
async def startup():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
@app.post("/users/")
async def create_user(username: str, email: str, db: AsyncSession = Depends(get_db)):
# 1. Створюємо об'єкт
new_user = User(username=username, email=email)
# 2. Додаємо в сесію (кладемо у візок)
db.add(new_user)
# 3. Підтверджуємо зміни (йдемо на касу)
await db.commit()
# 4. Оновлюємо об'єкт даними з бази (наприклад, отримати ID)
await db.refresh(new_user)
return new_user
4. 🛠 Практична частина
Час забруднити руки кодом! Виконайте ці завдання:
- 🔹 Реплікація: Запустіть наведений вище код. Якщо у вас немає PostgreSQL, змініть
DATABASE_URLнаsqlite+aiosqlite:///./test.db(не забудьтеpip install aiosqlite). - 🔹 Помилка розвідника: Спробуйте змінити пароль у
DATABASE_URLна неправильний. Запустіть сервер і спробуйте зробити запит. Прочитайте лог помилки. Що каже сервер? (Це навчить вас розпізнавати проблеми з підключенням). - 🔹 Нова сутність: Створіть у
models.pyнову модельProduct(з полямиid,title,price). - 🔹 Читання даних: Напишіть GET-запит
/users/, який повертає список користувачів (вам знадобитьсяresult = await db.execute(select(User))іresult.scalars().all()). - 🔹 Міні-кейс: Уявіть, що ви робите блог. Додайте зв'язок: один
Userможе мати багатоPost. (Підказка: погуглітьSQLAlchemy relationship).
5. 💡 Мислення як у розробника
Як відрізнити новачка від профі в цій темі?
-
Безпека (Environment Variables):
- Новачок: Пише пароль від бази прямо в коді
DATABASE_URL = "admin:secret...". Потім заливає це на GitHub, і його базу хакають за 5 хвилин. - Профі: Використовує файл
.envі бібліотекуpython-dotenv. Паролі ніколи не потрапляють у код.
- Новачок: Пише пароль від бази прямо в коді
-
Життєвий цикл сесії:
- Новачок: Відкриває сесію вручну десь у середині функції і забуває її закрити.
- Профі: Завжди використовує
Depends(get_db). FastAPI сам потурбується про відкриття та закриття. Це надійно.
-
Асинхронність:
- Новачок: Блокує головний потік синхронними запитами, поки база думає.
- Профі: Використовує
await, дозволяючи серверу обробляти тисячі інших запитів, поки база даних шукає відповідь.
6. 🧩 Підсумок
Отже, що ми сьогодні зробили? Ми побудували надійний міст між кодом і даними. * Engine — наш тунель до бази. * Session — наш робочий простір. * Models — наші дані у вигляді об'єктів.
Тепер ви можете зберігати і читати дані, не написавши жодного рядка INSERT INTO...!
🚀 Що далі?
Зараз наші таблиці створюються автоматично при запуску (Base.metadata.create_all). Але що, як нам треба додати колонку в існуючу таблицю, де вже є живі дані? Видаляти таблицю не можна!
На наступному уроці ми познайомимося з Alembic — інструментом для міграцій, який дозволяє змінювати структуру бази даних "на льоту", як хірург.
А поки — кодуйте, помиляйтеся і виправляйте! Це і є шлях. 💻