Ось готовий урок, створений спеціально для тебе у стилі David Malan — з енергією, аналогіями та фокусом на розумінні суті.
🎓 Урок CS50: ORM у FastAPI та SQLAlchemy
Привіт, друзі! Радий бачити вас. Сьогодні ми зазирнемо під капот сучасної веб-розробки та розберемося з магією, яка дозволяє Python спілкуватися з базами даних.
Тема нашого уроку: ORM (Object-Relational Mapping), і як ми це використовуємо у зв'язці FastAPI + SQLAlchemy.
1. 🔥 Вступ: проблема та мотивація
Уявіть, що ви приїхали в країну, мови якої абсолютно не знаєте. Скажімо, ви розмовляєте лише українською (це наш Python), а місцеві жителі — лише, наприклад, клінгонською (це наш SQL, мова баз даних).
Ви хочете замовити каву (отримати дані). У вас є два шляхи:
1. Вчити клінгонську. Вивчити всі ці SELECT, JOIN, WHERE, писати довгі рядки коду, де одна пропущена кома ламає все.
2. Найняти перекладача. Ви кажете перекладачеві українською: «Дай мені каву», а він сам розбирається, як це гаркнути клінгонською до офіціанта.
Риторичне запитання: Якщо ви хочете будувати складні архітектурні додатки, чи хочете ви витрачати 50% часу на написання нудних SQL-запитів вручну, ризикуючи зробити помилку в кожному рядку?
Звісно, ні. Ось тут на сцену виходить ORM.
Чому без цього не обійтись? Коли ми пишемо на FastAPI, ми оперуємо об'єктами (користувач, товар, замовлення). А база даних оперує таблицями та рядками. Це два різні світи. ORM — це міст, який дозволяє вам працювати з базою даних так, наче ви просто працюєте зі звичайними змінними Python.
2. 🧠 Теоретична база (без сухої академічності)
Давайте розкладемо це на пальцях.
Що таке ORM?
ORM (Object-Relational Mapping) — це технологія, яка пов'язує: * Клас у Python ➡️ Таблиця в БД * Екземпляр класу (об'єкт) ➡️ Рядок у таблиці * Атрибут класу ➡️ Колонка таблиці
Гравці на полі:
- FastAPI: Це наш веб-сервер. Він приймає запит від клієнта (наприклад, "Створити юзера").
- SQLAlchemy: Це і є наш "перекладач" (ORM). Найпопулярніша бібліотека для Python.
- База Даних (SQLite/PostgreSQL): Це склад, де лежать дані.
Як це працює "під капотом"?
Коли ви пишете на Python session.add(user), SQLAlchemy тихо формує SQL-запит INSERT INTO users... і відправляє його базі. Вам не треба знати синтаксис SQL, щоб це спрацювало.
🔑 Що запам'ятати обов'язково:
- Engine (Двигун): Точка входу, підключення до бази.
- Session (Сесія): Це ваша "транзакція". Уявіть це як кошик у супермаркеті. Ви кладете туди товари (зміни), і вони не куплені (не записані в БД), поки ви не підійдете до каси (не зробите
commit). - Model (Модель): Клас Python, що описує таблицю.
Інтуїтивно: Модель — це креслення. База даних — це будівля. SQLAlchemy — це будівельна бригада.
3. 🧪 Приклади: Від "Hello World" до реальності
Зараз ми створимо міні-систему для зберігання нотаток.
Крок 1: Налаштування "Перекладача" (Database Setup)
Спершу нам треба сказати SQLAlchemy, де лежить база.
# database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# 1. Створюємо шлях до бази (для прикладу - простий файл SQLite)
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
# 2. Запускаємо двигун (Engine)
# connect_args потрібен лише для SQLite
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
# 3. Фабрика сесій (те саме місце, де видають "кошики" для покупок)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# 4. Базовий клас для наших моделей
Base = declarative_base()
Крок 2: Створення Моделі (Креслення)
Питання до вас: Як ви думаєте, якщо ми створимо клас Note у Python, чи з'явиться таблиця в базі автоматично просто так?
Відповідь: Ні! Нам треба явно попросити SQLAlchemy створити її на основі класу.
# models.py
from sqlalchemy import Column, Integer, String, Boolean
from database import Base
class Note(Base):
__tablename__ = "notes" # Як таблиця називатиметься в SQL
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
content = Column(String)
is_active = Column(Boolean, default=True)
Крок 3: FastAPI + Використання (Магія)
Тепер поєднаємо це в main.py.
# main.py
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from database import SessionLocal, engine, Base
from models import Note
# Створюємо таблиці в базі (ось тут магія перетворення класу в SQL)
Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency (Залежність): функція, яка видає нам сесію для роботи з БД
# і закриває її, коли запит завершено. Дуже важливо!
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/notes/")
def create_note(title: str, content: str, db: Session = Depends(get_db)):
# 1. Створюємо об'єкт Python
new_note = Note(title=title, content=content)
# 2. Додаємо в "кошик" (сесію)
db.add(new_note)
# 3. Йдемо на касу (фіксуємо зміни в БД)
db.commit()
# 4. Оновлюємо об'єкт даними з БД (наприклад, щоб отримати ID)
db.refresh(new_note)
return new_note
Що тут відбулося? Ми не написали жодного рядка SQL коду. Ми просто створили об'єкт Note, і він зберігся у файлі test.db.
4. 🛠 Практична частина
Час забруднити руки кодом! Виконайте ці завдання по черзі:
- 🔹 Copy-Paste-Run: Скопіюйте код з прикладів у три файли (
database.py,models.py,main.py), запустіть (uvicorn main:app --reload) і через Swagger UI (/docs) спробуйте створити нотатку. - 🔹 Читання даних: Напишіть новий
GETендпоінт@app.get("/notes/"), який повертає список усіх нотаток.- Підказка: Вам знадобиться метод
db.query(Note).all().
- Підказка: Вам знадобиться метод
- 🔹 Зміна умов: Додайте до моделі
Noteнове полеauthor(тип String).- Увага: Якщо ви використовуєте SQLite, просто видаліть файл
test.dbі перезапустіть сервер, щоб таблиця перестворилась (це найпростіший шлях для початку).
- Увага: Якщо ви використовуєте SQLite, просто видаліть файл
- 🔹 Реальний кейс (Пошук): Зробіть так, щоб можна було знайти нотатку за
id.- Підказка:
db.query(Note).filter(Note.id == note_id).first().
- Підказка:
- 🔹 А що, якщо... Що станеться, якщо у функції
create_noteвидалити рядокdb.commit()? Спробуйте. Чому сервер не видає помилку, але дані не з'являються при наступному запиті?
5. 💡 Мислення як у розробника
Як відрізнити новачка від профі в роботі з ORM?
❌ Помилка новачка: "Змішування понять"
Новачки часто плутають Pydantic-моделі (схеми для валідації вхідних даних FastAPI) і SQLAlchemy-моделі (відображення таблиць БД).
* Pydantic — це охоронець на вході в клуб (перевіряє фейс-контроль даних).
* SQLAlchemy — це архіваріус (кладе дані на полиці).
Не намагайтеся запхати одне в інше. Використовуйте Pydantic для request/response, а ORM — для роботи всередині.
❌ Помилка новачка: N+1 проблема
Це коли ви робите цикл по об'єктах і для кожного робите окремий запит в базу.
* Погано: Запитати список юзерів, а потім у циклі для кожного окремо питати його адресу.
* Добре: Попросити ORM дістати юзерів ОДРАЗУ з їхніми адресами (через joinedload).
🧠 Як думає профі:
"Я не буду писати SQL вручну, поки це не стане критично для продуктивності". Профі знає: читабельність коду на Python важливіша за мікро-оптимізацію на старті.
6. 🧩 Підсумок
Отже, що ми сьогодні зробили:
1. Зрозуміли, що ORM — це наш особистий перекладач з Python на SQL.
2. Налаштували Session (кошик) та Engine (портал).
3. Створили таблицю, просто описавши Class.
4. Навчилися зберігати дані в базу, не написавши жодного INSERT.
Тепер ви вмієте: створювати бекенд на FastAPI, який має довготривалу пам'ять, а не забуває все після перезавантаження.
🚀 Тизер наступного уроку:
Але що робити, якщо у нас є User, а у нього багато Notes? Як пов'язати ці дві таблиці між собою, щоб знати, чия це нотатка? На наступному уроці ми розберемо Relationships (Відносини) та Foreign Keys.
А поки що — практикуйтеся і не забувайте робити commit()! 😉