Ось готовий урок, створений спеціально для тебе у стилі CS50. Ми розберемо тему Depends (на прикладі фреймворку FastAPI, де ця концепція реалізована найяскравіше), але принципи будуть універсальні для розуміння Dependency Injection.
🎓 Урок: Depends — Мистецтво не робити одне й те саме двічі
Привіт, друзі! Я радий бачити вас на цьому уроці. Сьогодні ми поговоримо про річ, яка відділяє код новачка від коду професійного інженера.
Ми розберемо Depends (Залежності).
1. 🔥 Вступ: проблема та мотивація
Уявіть собі, що ви відкриваєте найкрутіший нічний клуб у Києві. У вас є три зали: 1. Танцпол (для всіх). 2. VIP-кімната (тільки для тих, хто купив дорогий квиток). 3. Кабінет директора (тільки для вас).
Що станеться, якщо біля кожних дверей ви поставите окремого охоронця, і кожному з них дасте одну й ту саму інструкцію: "Перевір паспорт, перевір квиток, подивись у базу даних..."?
Це дорого. Це незручно. А що, якщо правила зміняться? Вам доведеться бігати до кожного охоронця і перевчати його.
У програмуванні це виглядає так: Ви пишете API. У вас є 50 функцій (ендпоінтів). І в кожній з них вам потрібно: * Підключитися до бази даних. * Перевірити, чи залогінений користувач. * Перевірити, чи має він права доступу.
Невже ви будете копіювати ці 10 рядків коду в кожну функцію? (Риторична пауза) Звісно, ні. Це порушує "золоте правило" розробки — DRY (Don't Repeat Yourself).
Без Depends ваш код перетвориться на спагеті, яке неможливо підтримувати. З Depends ви пишете логіку один раз і просто "вставляєте" її туди, де вона потрібна.
2. 🧠 Теоретична база (без нудних лекцій)
Що ж таке Depends "під капотом"?
У світі комп'ютерних наук це називається Dependency Injection (Впровадження залежностей). Звучить страшно, але ідея проста як двері.
Уявіть, що ваша функція — це шеф-кухар. Шеф-кухар хоче смажити стейк. Йому потрібне м'ясо. * Без Dependency Injection: Шеф кидає пательню, біжить на ферму, ловить бика, обробляє його і тільки потім смажить. * З Dependency Injection (Depends): Шеф каже: "Мені потрібне м'ясо". І спеціальний помічник (фреймворк) мовчки кладе йому на стіл вже готовий шматок м'яса.
🔑 Що треба запам'ятати: 1. Depends — це спосіб сказати фреймворку: "Перед тим, як запустити мою функцію, виконай ось цей код, а результат дай мені". 2. Це дозволяє винести спільну логіку (підключення до БД, валідацію, пошук юзера) в окремі функції. 3. Це робить код чистим і тестованим.
Як це працює технічно:
Ви створюєте звичайну функцію (назвемо її provider). А потім у вашій основній функції в параметрах пишете: data = Depends(provider).
Фреймворк бачить це, запускає provider, бере його return і кладе в змінну data. Магія? Ні, просто розумна архітектура.
3. 🧪 Приклади (від простого до реального)
Приклад 1: "Пагінація для ледачих"
Уявіть, що у вас є список товарів, новин і користувачів. Скрізь потрібна пагінація (сторінки): skip (скільки пропустити) і limit (скільки показати).
Без Depends (Погано ❌):
@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10):
return fake_items_db[skip : skip + limit]
@app.get("/users/")
async def read_users(skip: int = 0, limit: int = 10): # Знову те саме?!
return fake_users_db[skip : skip + limit]
З Depends (Добре ✅):
Спочатку пишемо спільну логіку:
from fastapi import Depends
# Це наша залежність
async def pagination_params(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
# А тепер використовуємо її!
@app.get("/items/")
async def read_items(commons: dict = Depends(pagination_params)):
return fake_items_db[commons["skip"] : commons["skip"] + commons["limit"]]
@app.get("/users/")
async def read_users(commons: dict = Depends(pagination_params)):
return fake_users_db[commons["skip"] : commons["skip"] + commons["limit"]]
Бачите? Якщо ви захочете змінити дефолтний ліміт з 10 на 20, ви зміните це лише в одному місці!
Приклад 2: Реальний світ — База Даних
Це класика. Вам треба відкрити сесію бази даних, зробити запит і обов'язково закрити її, навіть якщо сталася помилка.
Використаємо yield (це як return, але функція не завершується повністю, а чекає, поки ви закінчите роботу).
# Залежність для отримання БД
def get_db():
db = MyDatabase() # Відкрили з'єднання
try:
yield db # Віддали з'єднання у функцію
finally:
db.close() # Закрили з'єднання (виконається ПІСЛЯ завершення запиту)
@app.post("/users/")
def create_user(user: UserCreate, db = Depends(get_db)):
# Тут ми просто користуємося db, не думаючи про відкриття/закриття
return db.save(user)
Запитання до вас: Чому ми використали try...finally?
Відповідь: Щоб не "покласти" базу даних купою незакритих з'єднань, якщо у вашому коді станеться помилка. Depends із yield гарантує безпеку.
4. 🛠 Практична частина
Час закотити рукави. Виконайте ці завдання подумки або в редакторі коду.
🔹 Завдання 1: Повторення
Напишіть функцію-залежність verify_token, яка приймає параметр token: str. Якщо токен дорівнює "supersecret", вона повертає True. Якщо ні — викидає помилку (exception).
🔹 Завдання 2: Зміна умов
Створіть ендпоінт /secret-data, який використовує Depends(verify_token). Що станеться, якщо викличете його без токена?
🔹 Завдання 3: Виправ помилку Студент написав код:
def get_current_user():
return "User123"
@app.get("/me")
def read_me(user = get_current_user()): # <--- Помилка тут
return user
Чому це не спрацює як Depends у FastAPI, і як це виправити?
🔹 Завдання 4: Міні-кейс
Вам потрібно, щоб кожен запит до API перевіряв, чи надіслав клієнт спеціальний заголовок X-Key. Напишіть залежність, яка читає цей заголовок.
🔹 Завдання 5: А що, якщо...
У вас є залежність get_user, яка дістає юзера з БД. І є інша залежність check_admin, яка має перевірити, чи цей юзер адмін.
Підказка: Одна залежність може використовувати іншу! Спробуйте уявити ланцюжок: check_admin -> Depends(get_user).
5. 💡 Мислення як у розробника
Як відрізнити новачка від сеньйора, дивлячись на Depends?
⚠️ Типові помилки новачків:
1. Забувають Depends: Пишуть просто user = get_user. Python виконає це один раз при запуску програми, а не для кожного запиту. Це катастрофа.
2. Забагато логіки в контролері: Вони пишуть "товсті" функції обробки шляхів замість того, щоб винести перевірки в залежності.
3. Не використовують кешування: За замовчуванням FastAPI виконує одну й ту саму залежність один раз за запит. Якщо ви 5 разів викличете Depends(get_current_user) в одному запиті — база даних потурбується лише раз. Новачки часто намагаються це "оптимізувати" вручну, ламаючи логіку.
🚀 Як думає досвідчений спеціаліст:
"Я пишу цей код вдруге? Стоп. Чи це спільна логіка? Так. Виносимо в Depends."
"Мені потрібно протестувати цей ендпоінт. Якщо я "зашив" підключення до реальної БД всередину функції, я не зможу підсунути тестову БД. Якщо я використовую Depends — я можу підмінити залежність одним рядком коду."
6. 🧩 Підсумок
Отож, що ми сьогодні зробили? Ми взяли хаос і перетворили його на порядок.
Тепер ви вмієте: 1. Використовувати повторно код без Copy-Paste. 2. Керувати з'єднаннями (наприклад, з БД) елегантно і безпечно. 3. Будувати ланцюжки залежностей (одна залежить від іншої).
Depends — це як конструктор LEGO. Ви створюєте маленькі, зрозумілі кубики (функції), а потім збираєте з них величезний, надійний замок (вашу програму).
🔜 У наступній серії: Ми розібралися, як передавати дані. Але як захистити наш "замок"? Наступного разу ми поговоримо про OAuth2 та хешування паролів. Готуйтеся, будемо зламувати (легально)!
А поки що — щасти вам у коді!