Ось готовий урок, створений у стилі CS50, спеціально для вивчення Dependency Injection у FastAPI.
🎓 CS50x: FastAPI & Dependency Injection (Впровадження залежностей)
Привіт, друзі! Ласкаво просимо! 👋
Сьогодні ми не просто пишемо код. Ми вчимося робити нашу архітектуру чистою, гнучкою і, що найважливіше, тестованою. Якщо ви коли-небудь копіювали й вставляли один і той самий код перевірки користувача в 10 різних функцій — цей урок змінить ваше життя.
Тема сьогоднішнього дня: Dependency Injection (DI) або Впровадження залежностей.
Звучить як складна інженерна термінологія? Можливо. Але насправді це одна з найелегантніших ідей у розробці ПЗ. І у FastAPI вона реалізована просто геніально. Поїхали! 🚀
1. 🔥 Вступ: Проблема та Мотивація
Уявіть собі ситуацію. Ви будуєте будинок. 🏠
Кожного разу, коли вам потрібен молоток, ви не йдете в магазин, не купуєте руду, не плавите метал і не виточуєте інструмент з нуля прямо на будмайданчику, правда? Це було б божевіллям!
Натомість, у вас є пояс з інструментами. Коли вам потрібен молоток, ви просто простягаєте руку і берете його. Вам все одно, хто його поклав туди або якої марки сталь. Вам просто потрібен інструмент, щоб забити цвях.
А тепер подивимось на код.
Уявіть, що у вас є 5 різних ендпоінтів (URL-адрес) у вашому API:
1. GET /users
2. POST /items
3. GET /orders
... і так далі.
Кожен із них потребує з’єднання з базою даних.
❌ Поганий підхід: У кожній функції ви пишете код: «Підключись до БД, введи пароль, створи курсор...». ❓ Питання: А що, якщо пароль зміниться? Або ви захочете замінити базу даних на тестову? Вам доведеться переписувати код у 5 (або 50) місцях!
✅ Підхід FastAPI (DI): Ви кажете функції: "Гей, мені потрібна база даних. Я не хочу знати, як ти її підключиш. Просто дай мені готове з'єднання, коли я почну працювати".
Це і є Dependency Injection. Ви не створюєте залежності всередині — ви їх отримуєте.
Чому без цього ніяк? Тому що без DI ваш код перетвориться на «спагеті», яке неможливо тестувати і страшно підтримувати.
2. 🧠 Теоретична база (Але без нудних лекцій)
Давайте заглянемо під капот. 🔧
У FastAPI є магічний інструмент — клас Depends.
Коли FastAPI бачить, що у параметрах вашої функції стоїть Depends(...), він робить наступне (уявіть його як турботливого асистента):
- Дивиться на функцію, яку ви передали в
Depends. - Виконує цю функцію десь "за лаштунками".
- Бере результат виконання.
- Вкладає цей результат у вашу функцію-обробник.
Це називається Inversion of Control (Інверсія управління). Ви не керуєте процесом створення об'єкта. Фреймворк керує цим за вас.
📝 Що треба запам'ятати (Hard Skills):
Depends— це спосіб сказати FastAPI: "Спочатку виконай ось це, а результат дай мені".- Залежність може бути функцією або класом.
- Залежності можуть мати свої власні залежності (ланцюжок! 🔗).
💡 Інтуїтивне розуміння:
Це як замовлення в ресторані. 🍝 Ви (функція ендпоінту) замовляєте "Пасту". Офіціант (FastAPI) йде на кухню, каже шефу (Dependency) приготувати пасту. Шеф готує, віддає офіціанту, офіціант приносить вам. Ви не йдете на кухню. Ви просто отримуєте готову страву.
3. 🧪 Приклади (Від простого до реального)
Давайте писати код! Відкривайте редактор.
Приклад 1: "Загальні параметри" (The Pagination Analogy)
Уявіть, що у нас є багато сторінок з даними. Нам постійно треба зчитувати параметри skip (скільки пропустити) і limit (скільки показати).
Без DI ми б писали це в кожній функції. З DI ми виносимо це в окрему логіку.
from fastapi import FastAPI, Depends
app = FastAPI()
# 1. Створюємо залежність (наш "Шеф-кухар")
def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
# 2. Використовуємо залежність (Клієнт)
@app.get("/items/")
# ❓ Що ми очікуємо тут побачити?
# Ми не викликаємо common_parameters() самі! FastAPI зробить це за нас.
def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
def read_users(commons: dict = Depends(common_parameters)):
return {"users_filter": commons}
Пояснення: Коли ви перейдете на /items/?skip=20&limit=10, FastAPI викличе common_parameters, розпарсить query-параметри, і передасть словник у змінну commons. Чисто і красиво!
Приклад 2: "Трохи реальності" (База даних і yield)
Це класика. Підключення до бази даних. Нам треба відкрити сесію, щось зробити, а потім обов'язково закрити, навіть якщо сталася помилка.
Тут ми використовуємо yield замість return.
# Уявна функція підключення до БД
def get_db():
db = "Session_Local_DB_Connection" # Уявіть, що це реальний об'єкт сесії
print("🔌 Opening Database connection...")
try:
yield db # Повертаємо сесію і ставимо виконання на паузу
finally:
print("🔒 Closing Database connection...")
# Тут сесія закриється після того, як запит завершиться
@app.get("/products/")
def get_products(db = Depends(get_db)):
# Ми просто користуємося db, не думаючи про закриття!
return {"message": "Using database", "db_status": db}
Чому результат саме такий?
Завдяки yield, FastAPI виконує код до yield, передає значення у вашу функцію, чекає, поки ваша функція закінчить роботу (поверне відповідь користувачу), а потім повертається і виконує код після yield (блок finally). Це гарантує, що база даних завжди закриється. Безпека понад усе! 🛡️
4. 🛠 Практична частина
Тепер ваша черга! Не бійтеся помилятися — це шлях до розуміння.
Завдання 1: "Фейс-контроль" 🕵️♂️
Створіть залежність verify_token, яка приймає параметр token: str.
* Якщо токен дорівнює "supersecret", поверніть True.
* Якщо ні — викиньте виняток HTTPException(status_code=400, detail="No entry!").
* Використайте цю залежність у новому роуті /secret-data.
Завдання 2: "Ланцюгова реакція" 🔗
* Створіть функцію dep_a, яка повертає число 10.
* Створіть функцію dep_b, яка залежить від dep_a (використовує Depends(dep_a)) і додає до результату 5.
* Створіть ендпоінт, який залежить від dep_b. Яке число ви отримаєте у підсумку? (Має бути 15).
Завдання 3: "Виправити помилку" 🐛 Студент написав код:
def get_user_agent(user_agent: str | None = Header(default=None)):
return user_agent
@app.get("/me")
def read_me(ua = get_user_agent): # <--- Помилка тут
return {"ua": ua}
Код не працює як треба. ua повертає саму функцію, а не рядок. Чого не вистачає?
Завдання 4: Міні-кейс "Конвертер валют" 💱
Напишіть клас CurrencyConverter, який має метод convert(amount, rate).
Створіть залежність, яка повертає екземпляр цього класу.
В ендпоінті /buy використайте цей клас, щоб порахувати суму.
5. 💡 Мислення як у розробника
Як відрізнити новачка від профі в FastAPI?
⚠️ Помилка новачка: Викликати залежність вручну всередині функції.
@app.get("/items")
def read_items():
params = common_parameters() # ❌ НІ! Ми втрачаємо магію FastAPI (валідацію, документацію)
...
🧠 Як думає сеньйор:
"Мені потрібно протестувати цей ендпоінт. Я не хочу підключатися до реальної бази даних під час тестів. О! Оскільки я використовую Depends(get_db), я можу під час тестів просто підмінити (override) цю залежність на фейкову базу даних, не змінюючи жодного рядка коду самого ендпоінту!"
Порада з практики:
Якщо ви бачите, що 3 і більше ендпоінтів використовують одну й ту ж логіку (перевірка адміна, пошук куки, форматування дати) — це сигнал! 🚨 Робіть з цього Depends.
6. 🧩 Підсумок
Фух! Ви чудово попрацювали. 🙌
Сьогодні ми розібрали Dependency Injection.
Давайте підсумуємо:
1. DI дозволяє нам писати модульний код.
2. Depends — це клей, який з'єднує логіку з вашими ендпоінтами.
3. Ми навчилися передавати дані, керувати з'єднаннями (через yield) і будувати ланцюжки.
Що ви тепер вмієте? Ви вмієте створювати API, які можна легко розширювати і тестувати. Ви більше не "хардкодите" логіку, ви її "впроваджуєте".
🔜 Тизер наступного уроку: Тепер, коли ми вміємо керувати залежностями, як щодо того, щоб підключити справжню SQL базу даних і змусити FastAPI автоматично створювати для нас таблиці? Готуйтеся, наступного разу будемо працювати з SQLAlchemy!
Це був CS50... тобто, урок про FastAPI. Побачимось! 👋