Ось твій урок у стилі CS50. Приготуй каву, відкривай IDE, і поїхали розбиратися з хаосом! 🚀
🏛 Тема уроку: Архітектура та структура FastAPI-проєкту
Привіт, друзі! Радий бачити вас знову.
Уявіть, що ви вирішили приготувати вечерю. Якщо це бутерброд — вам потрібен лише ніж і стіл. Все просто, все під рукою.
Але уявіть, що ви — шеф-кухар величезного ресторану, який готує банкет на 500 осіб. Чи будете ви різати овочі, смажити м'ясо, мити посуд і приймати замовлення на одному й тому ж маленькому столику одночасно? Звісно, ні! Це був би хаос. У вас є цех заготівель, гарячий цех, зона видачі, склад.
1. 🔥 Вступ: Проблема «Божественного файлу»
Коли ми тільки починаємо вчити FastAPI, ми пишемо все в одному файлі main.py.
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root(): ...
@app.get("/users")
def get_users(): ...
@app.post("/items")
def create_item(): ...
# І ще 5000 рядків коду...
Спочатку це зручно. Але минає тиждень, ваш проєкт росте, і файл main.py перетворюється на монстра з 2000 рядків.
Питання до вас: 1. Як швидко ви знайдете помилку в логіці реєстрації користувача, якщо вам доведеться гортати сотні рядків коду про "товари" та "замовлення"? 2. Що станеться, якщо над цим файлом одночасно працюватимуть три розробники? (Спойлер: кривавий конфлікт злиття в Git 🩸).
Чому без цієї теми не обійтись? Тому що структура — це не про красу. Це про виживання проєкту. Якщо ви не розділите код на логічні частини зараз, через місяць ви захочете переписати проєкт з нуля, аніж розбиратися в ньому.
Сьогодні ми перетворимо наш "кіоск з шаурмою" (main.py) на професійну "ресторанну кухню".
2. 🧠 Теоретична база: Розділяй і володарюй
В інформатиці є золоте правило: Separation of Concerns (Розділення відповідальності). Кожен модуль має відповідати за щось одне.
У світі FastAPI ми зазвичай ділимо проєкт на такі "цехи":
main.py— Це вхідні двері та адміністратор. Він лише запускає додаток і підключає інші частини. Він не повинен містити бізнес-логіки.routers(абоapi) — Це ваші офіціанти. Вони приймають запит від клієнта (URL) і передають його на кухню. Тут живуть ваші@router.get(...).schemas(Pydantic) — Це контроль якості на вході та виході. Як виглядають дані? Чи є у користувача email? Чи валідний пароль?models(SQLAlchemy / DB) — Це схема вашого складу (бази даних). Як дані зберігаються на полицях.crud(Create, Read, Update, Delete) — Це кухарі. Чиста логіка роботи з базою даних. Жодних перевірок HTTP тут бути не повинно.
Як це працює "під капотом"?
Головний герой сьогоднішнього уроку — клас APIRouter.
Уявіть, що app = FastAPI() — це головний подовжувач (розетка). А APIRouter — це менші подовжувачі, які ви вмикаєте в головний.
Ви можете створити окремий файл для користувачів (users.py), налаштувати там усі шляхи, а потім просто сказати головному додатку: "Ей, підключи ці маршрути теж".
Обов'язково запам'ятати:
* main.py має бути максимально чистим.
* Використовуємо APIRouter, щоб групувати ендпоінти.
* Схеми (Pydantic) і Моделі (DB) — це різні речі (хоч і схожі). Схеми — для спілкування з клієнтом, Моделі — для спілкування з базою.
3. 🧪 Приклади: Від спагеті до лазаньї
Рівень 1: Спагеті-код (Як ми робили раніше)
Усе в купу. Жахливо для підтримки.
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/")
def get_users():
return [{"name": "David"}]
@app.get("/items/")
def get_items():
return [{"item": "Laptop"}]
Рівень 2: Використання APIRouter (Перший крок до порядку)
Давайте винесемо користувачів в окремий файл.
Створіть файл users.py:
# users.py
from fastapi import APIRouter
# Створюємо "міні-додаток"
router = APIRouter()
@router.get("/users/")
def get_users():
return [{"name": "David"}, {"name": "Alice"}]
А тепер підключимо це в main.py:
# main.py
from fastapi import FastAPI
from users import router as users_router # Імпортуємо роутер
app = FastAPI()
# Вмикаємо "подовжувач" у розетку
app.include_router(users_router)
@app.get("/")
def root():
return {"message": "Hello World"}
Питання до вас: Що станеться, якщо я тепер звернуся за адресою /users/?
Відповідь: FastAPI "склеїть" ці частини, і все запрацює так, ніби код був в одному файлі.
Рівень 3: Реальна структура (Industry Standard)
Ось так виглядає типовий проєкт на роботі. Погляньте на структуру тек:
my_project/
├── app/
│ ├── __init__.py
│ ├── main.py <-- Точка входу
│ ├── api/ <-- Папка з роутерами
│ │ ├── __init__.py
│ │ ├── users.py <-- Ендпоінти для юзерів
│ │ └── items.py <-- Ендпоінти для товарів
│ ├── core/ <-- Налаштування (конфіги, безпека)
│ ├── db/ <-- База даних
│ └── schemas/ <-- Pydantic моделі
└── requirements.txt
У файлі app/api/users.py:
from fastapi import APIRouter
# prefix="/users" означає, що всі шляхи тут починатимуться з /users
# tags=["Users"] додасть красиву групу в документації Swagger
router = APIRouter(prefix="/users", tags=["Users"])
@router.get("/") # Це буде доступно як GET /users/
def read_users():
return [{"username": "Malan"}]
@router.get("/me") # Це буде доступно як GET /users/me
def read_user_me():
return {"username": "current_user"}
У файлі app/main.py:
from fastapi import FastAPI
from app.api import users, items # Припустимо, items теж існує
app = FastAPI(title="My Super Structure")
app.include_router(users.router)
app.include_router(items.router)
Чому це круто?
1. Ви бачите prefix="/users". Вам не треба писати слово "users" у кожному декораторі.
2. В документації (/docs) все буде розкладено по поличках (тегах).
4. 🛠 Практична частина
Час забруднити руки кодом! Відкривайте редактор.
Завдання 1: "Велике переселення"
Створіть файл main.py з двома ендпоінтами: /products і /orders.
Перевірте, що працює.
Тепер створіть папку routers, у ній файли products.py та orders.py.
Перенесіть логіку туди, використовуючи APIRouter. Підключіть їх назад у main.py.
Завдання 2: "Лінивий програміст" (Префікси)
У файлі products.py використайте аргумент prefix="/products" при створенні APIRouter.
Змініть шляхи в декораторах так, щоб замість @router.get("/products/list") було просто @router.get("/list").
Перевірте, чи URL залишився тим самим.
Завдання 3: Виправлення помилки 🐛
Студент написав такий код у main.py, але отримує помилку 404 при спробі зайти на /cats. Чому?
# cats.py
from fastapi import APIRouter
router = APIRouter()
@router.get("/cats")
def get_cats(): return "Meow"
# main.py
from fastapi import FastAPI
import cats
app = FastAPI()
# ... тут чогось не вистачає ...
Виправте код.
Завдання 4: Міні-кейс "Версійність"
Уявіть, що ви оновлюєте API, але старі клієнти (мобільні додатки) не можуть оновитися миттєво.
Вам потрібно підтримувати v1 і v2 одночасно.
Завдання: Створіть структуру папок api/v1/users.py та api/v2/users.py.
Підключіть їх у main.py так, щоб шляхи були /api/v1/users та /api/v2/users.
Питання "А що, якщо..."
А що, якщо у нас 50 роутерів? Чи маємо ми писати 50 разів app.include_router(...) у main.py?
Підказка: Можна створити один головний api_router у файлі api/__init__.py, підключити всі дрібні роутери туди, а в main підключити тільки цей один головний. Подумайте про це як про подовжувач, встромлений в інший подовжувач.
5. 💡 Мислення як у розробника
Як відрізнити новачка від профі, дивлячись на структуру?
1. Циклічні імпорти (Circular Imports)
Новачки часто роблять так: Файл А імпортує Файл Б, а Файл Б імпортує Файл А. Python вибухає 💥.
Як думає профі: "Я будуватиму дерево залежностей. main знає про всіх. router знає про crud. crud знає про models. Але models ніколи не повинні знати про router." Потік іде зверху вниз.
2. Назви файлів
Не називайте файли data.py або functions.py. Це ні про що не говорить.
Порада: Називайте файл за сутністю, з якою він працює: users.py, invoices.py, notifications.py. Заходячи в папку, я маю одразу розуміти, що вміє ваша система.
3. init.py
Не бійтеся використовувати файли __init__.py для того, щоб "збирати" експорти. Це дозволяє робити імпорти чистішими.
Замість: from app.crud.users import create_user
Можна зробити: from app.crud import create_user (якщо налаштувати __init__.py).
6. 🧩 Підсумок
Отже, що ми сьогодні зробили?
Ми взяли хаотичний, звалений в купу код і розклали його по поличках. Тепер:
1. APIRouter — ваш найкращий друг для масштабування.
2. Ваш main.py чистий і красивий.
3. Ви готові писати проєкти, які не соромно показати на GitHub або колегам.
Ви тепер вмієте: * Розбивати монолітний додаток на модулі. * Використовувати префікси та теги для організації API. * Будувати професійну архітектуру тек.
🕵️ Тизер наступної теми: Тепер, коли у нас є ідеальна структура, нам потрібно наповнити її даними. Але дані не можна просто так "кидати" у функції. Їх треба перевіряти. Наступного разу ми поговоримо про Pydantic: як змусити Python бути суворим і не дозволяти користувачам надсилати нісенітниці.
А поки що — розкладіть свій код по папках! Це ж CS50! (ну, майже 😉)