26 KiB
Більші застосунки - кілька файлів
Якщо ви створюєте застосунок або веб-API, рідко вдається вмістити все в один файл.
FastAPI надає зручний інструмент для структурування вашого застосунку, зберігаючи всю гнучкість.
/// info | Інформація
Якщо ви прийшли з Flask, це еквівалент «Blueprints» у Flask.
///
Приклад структури файлів
Припустімо, у вас така структура файлів:
.
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── dependencies.py
│ └── routers
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── internal
│ ├── __init__.py
│ └── admin.py
/// tip | Порада
Тут кілька файлів __init__.py: по одному в кожному каталозі та підкаталозі.
Саме це дозволяє імпортувати код з одного файлу в інший.
Наприклад, у app/main.py ви можете мати рядок:
from app.routers import items
///
- Каталог
appмістить усе. І він має порожній файлapp/__init__.py, тож це «пакет Python» (збірка «модулів Python»):app. - Він містить файл
app/main.py. Оскільки він усередині пакета Python (каталог з файлом__init__.py), це «модуль» цього пакета:app.main. - Є також файл
app/dependencies.py, так само якapp/main.py, це «модуль»:app.dependencies. - Є підкаталог
app/routers/з іншим файлом__init__.py, отже це «підпакет Python»:app.routers. - Файл
app/routers/items.pyзнаходиться в пакетіapp/routers/, отже це підмодуль:app.routers.items. - Так само і
app/routers/users.py, це інший підмодуль:app.routers.users. - Є також підкаталог
app/internal/з іншим файлом__init__.py, отже це інший «підпакет Python»:app.internal. - І файл
app/internal/admin.py- ще один підмодуль:app.internal.admin.
Та сама структура файлів з коментарями:
.
├── app # «app» - це пакет Python
│ ├── __init__.py # цей файл робить «app» «пакетом Python»
│ ├── main.py # модуль «main», напр. import app.main
│ ├── dependencies.py # модуль «dependencies», напр. import app.dependencies
│ └── routers # «routers» - це «підпакет Python»
│ │ ├── __init__.py # робить «routers» «підпакетом Python»
│ │ ├── items.py # підмодуль «items», напр. import app.routers.items
│ │ └── users.py # підмодуль «users», напр. import app.routers.users
│ └── internal # «internal» - це «підпакет Python»
│ ├── __init__.py # робить «internal» «підпакетом Python»
│ └── admin.py # підмодуль «admin», напр. import app.internal.admin
APIRouter
Припустімо, файл, присвячений обробці лише користувачів, - це підмодуль у /app/routers/users.py.
Ви хочете мати операції шляху, пов'язані з користувачами, відокремлено від решти коду, щоб зберегти порядок.
Але це все одно частина того самого застосунку/веб-API FastAPI (це частина того самого «пакета Python»).
Ви можете створювати операції шляху для цього модуля, використовуючи APIRouter.
Імпортуйте APIRouter
Імпортуйте його та створіть «екземпляр» так само, як ви б робили з класом FastAPI:
{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *}
Операції шляху з APIRouter
Потім використовуйте його для оголошення операцій шляху.
Використовуйте його так само, як і клас FastAPI:
{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
Можете думати про APIRouter як про «міні FastAPI».
Підтримуються ті самі опції.
Ті самі parameters, responses, dependencies, tags тощо.
/// tip | Порада
У цьому прикладі змінна називається router, але ви можете назвати її як завгодно.
///
Ми включимо цей APIRouter у основний застосунок FastAPI, але спочатку розгляньмо залежності та інший APIRouter.
Залежності
Бачимо, що нам знадобляться кілька залежностей, які використовуються в різних місцях застосунку.
Тож помістимо їх у власний модуль dependencies (app/dependencies.py).
Тепер використаємо просту залежність для читання користувацького заголовка X-Token:
{* ../../docs_src/bigger_applications/app_an_py310/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}
/// tip | Порада
Ми використовуємо вигаданий заголовок, щоб спростити приклад.
Але в реальних випадках ви отримаєте кращі результати, використовуючи інтегровані засоби безпеки{.internal-link target=_blank}.
///
Інший модуль з APIRouter
Припустімо, у вас також є кінцеві точки для обробки «items» у модулі app/routers/items.py.
У вас є операції шляху для:
/items//items/{item_id}
Структура така сама, як у app/routers/users.py.
Але ми хочемо бути розумнішими й трохи спростити код.
Ми знаємо, що всі операції шляху в цьому модулі мають однакові:
- Префікс шляху
prefix:/items. tags: (лише одна мітка:items).- Додаткові
responses. dependencies: усім потрібна залежністьX-Token, яку ми створили.
Тож замість додавання цього до кожної операції шляху, ми можемо додати це до APIRouter.
{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}
Оскільки шлях кожної операції шляху має починатися з /, як у:
@router.get("/{item_id}")
async def read_item(item_id: str):
...
...префікс не має містити кінцевий /.
Отже, у цьому випадку префікс - це /items.
Ми також можемо додати список tags та додаткові responses, які застосовуватимуться до всіх операцій шляху, включених у цей router.
І ми можемо додати список dependencies, які буде додано до всіх операцій шляху у router і які виконуватимуться/вирішуватимуться для кожного запиту до них.
/// tip | Порада
Зверніть увагу, що так само як і для залежностей у декораторах операцій шляху{.internal-link target=_blank}, жодне значення не буде передано вашій функції операції шляху.
///
У підсумку шляхи предметів тепер:
/items//items/{item_id}
...як ми і планували.
- Вони будуть позначені списком міток, що містить один рядок
"items".- Ці «мітки» особливо корисні для автоматичної інтерактивної документації (за допомогою OpenAPI).
- Усі вони включатимуть наперед визначені
responses. - Для всіх цих операцій шляху список
dependenciesбуде оцінений/виконаний перед ними.- Якщо ви також оголосите залежності в конкретній операції шляху, вони також будуть виконані.
- Спочатку виконуються залежності router'а, потім
dependenciesу декораторі{.internal-link target=_blank}, а потім звичайні параметричні залежності. - Ви також можете додати
Securityзалежності зscopes{.internal-link target=_blank}.
/// tip | Порада
Наявність dependencies у APIRouter можна використати, наприклад, щоб вимагати автентифікацію для всієї групи операцій шляху. Навіть якщо залежності не додані до кожної з них окремо.
///
/// check | Перевірте
Параметри prefix, tags, responses і dependencies - це (як і в багатьох інших випадках) просто можливість FastAPI, яка допомагає уникати дублювання коду.
///
Імпортуйте залежності
Цей код живе в модулі app.routers.items, у файлі app/routers/items.py.
І нам потрібно отримати функцію залежності з модуля app.dependencies, файлу app/dependencies.py.
Тож ми використовуємо відносний імпорт з .. для залежностей:
{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[3] title["app/routers/items.py"] *}
Як працюють відносні імпорти
/// tip | Порада
Якщо ви досконало знаєте, як працюють імпорти, перейдіть до наступного розділу нижче.
///
Одна крапка ., як у:
from .dependencies import get_token_header
означає:
- Починаючи в тому самому пакеті, де знаходиться цей модуль (файл
app/routers/items.py) (каталогapp/routers/)... - знайти модуль
dependencies(уявний файлapp/routers/dependencies.py)... - і з нього імпортувати функцію
get_token_header.
Але такого файлу не існує, наші залежності у файлі app/dependencies.py.
Згадайте, як виглядає структура нашого застосунку/файлів:
Дві крапки .., як у:
from ..dependencies import get_token_header
означають:
- Починаючи в тому самому пакеті, де знаходиться цей модуль (файл
app/routers/items.py) (каталогapp/routers/)... - перейти до батьківського пакета (каталог
app/)... - і там знайти модуль
dependencies(файлapp/dependencies.py)... - і з нього імпортувати функцію
get_token_header.
Це працює правильно! 🎉
Так само, якби ми використали три крапки ..., як у:
from ...dependencies import get_token_header
це б означало:
- Починаючи в тому самому пакеті, де знаходиться цей модуль (файл
app/routers/items.py) (каталогapp/routers/)... - перейти до батьківського пакета (каталог
app/)... - потім перейти до батьківського пакета від того (немає батьківського пакета,
app- верхній рівень 😱)... - і там знайти модуль
dependencies(файлapp/dependencies.py)... - і з нього імпортувати функцію
get_token_header.
Це б посилалося на якийсь пакет вище за app/ з власним файлом __init__.py тощо. Але в нас такого немає. Тож у нашому прикладі це спричинить помилку. 🚨
Але тепер ви знаєте, як це працює, тож можете використовувати відносні імпорти у власних застосунках, незалежно від їхньої складності. 🤓
Додайте користувацькі tags, responses і dependencies
Ми не додаємо префікс /items ані tags=["items"] до кожної операції шляху, бо додали їх до APIRouter.
Але ми все ще можемо додати ще tags, які будуть застосовані до конкретної операції шляху, а також додаткові responses, специфічні для цієї операції шляху:
{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[30:31] title["app/routers/items.py"] *}
/// tip | Порада
Остання операція шляху матиме комбінацію міток: ["items", "custom"].
І вона також матиме в документації обидві відповіді: одну для 404 і одну для 403.
///
Основний FastAPI
Тепер розгляньмо модуль app/main.py.
Тут ви імпортуєте і використовуєте клас FastAPI.
Це буде головний файл вашого застосунку, який усе поєднує.
І оскільки більшість вашої логіки тепер житиме у власних модулях, головний файл буде досить простим.
Імпортуйте FastAPI
Імпортуйте та створіть клас FastAPI, як зазвичай.
І ми навіть можемо оголосити глобальні залежності{.internal-link target=_blank}, які будуть поєднані із залежностями кожного APIRouter:
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[1,3,7] title["app/main.py"] *}
Імпортуйте APIRouter
Тепер імпортуємо інші підмодулі, що мають APIRouter:
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[4:5] title["app/main.py"] *}
Оскільки файли app/routers/users.py та app/routers/items.py - це підмодулі, що є частиною того самого пакета Python app, ми можемо використати одну крапку . для «відносних імпортів».
Як працює імпорт
Розділ:
from .routers import items, users
означає:
- Починаючи в тому самому пакеті, де знаходиться цей модуль (файл
app/main.py) (каталогapp/)... - знайти підпакет
routers(каталогapp/routers/)... - і з нього імпортувати підмодулі
items(файлapp/routers/items.py) іusers(файлapp/routers/users.py)...
Модуль items матиме змінну router (items.router). Це та сама, що ми створили у файлі app/routers/items.py, це об’єкт APIRouter.
Потім ми робимо те саме для модуля users.
Ми також могли б імпортувати їх так:
from app.routers import items, users
/// info | Інформація
Перша версія - «відносний імпорт»:
from .routers import items, users
Друга версія - «абсолютний імпорт»:
from app.routers import items, users
Щоб дізнатися більше про пакети й модулі Python, прочитайте офіційну документацію Python про модулі.
///
Уникайте колізій імен
Ми імпортуємо підмодуль items напряму, замість імпорту лише його змінної router.
Це тому, що в підмодулі users також є змінна з назвою router.
Якби ми імпортували один за одним, як:
from .routers.items import router
from .routers.users import router
router з users перезаписав би той, що з items, і ми не змогли б використовувати їх одночасно.
Щоб мати змогу використовувати обидва в одному файлі, ми імпортуємо підмодулі напряму:
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[5] title["app/main.py"] *}
Додайте APIRouter для users і items
Тепер додаймо router з підмодулів users і items:
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[10:11] title["app/main.py"] *}
/// info | Інформація
users.router містить APIRouter у файлі app/routers/users.py.
А items.router містить APIRouter у файлі app/routers/items.py.
///
За допомогою app.include_router() ми можемо додати кожен APIRouter до основного застосунку FastAPI.
Це включить усі маршрути з цього router'а як частину застосунку.
/// note | Технічні деталі
Фактично, всередині для кожної операції шляху, оголошеної в APIRouter, буде створена окрема операція шляху.
Тобто за лаштунками все працюватиме так, ніби це один і той самий застосунок.
///
/// check | Перевірте
Вам не потрібно перейматися продуктивністю під час включення router'ів.
Це займе мікросекунди і відбуватиметься лише під час запуску.
Тож це не вплине на продуктивність. ⚡
///
Додайте APIRouter з власними prefix, tags, responses і dependencies
Уявімо, що ваша організація надала вам файл app/internal/admin.py.
Він містить APIRouter з кількома адміністративними операціями шляху, які організація спільно використовує між кількома проєктами.
Для цього прикладу він буде дуже простим. Але припустімо, що оскільки його спільно використовують з іншими проєктами організації, ми не можемо модифікувати його та додавати prefix, dependencies, tags тощо прямо до APIRouter:
{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *}
Але ми все одно хочемо встановити користувацький prefix під час включення APIRouter, щоб усі його операції шляху починалися з /admin, хочемо захистити його за допомогою dependencies, які вже є в цьому проєкті, і хочемо додати tags та responses.
Ми можемо оголосити все це, не змінюючи оригінальний APIRouter, передавши ці параметри до app.include_router():
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[14:17] title["app/main.py"] *}
Таким чином, вихідний APIRouter залишиться без змін, тож ми все ще зможемо спільно використовувати той самий файл app/internal/admin.py з іншими проєктами в організації.
У результаті в нашому застосунку кожна з операцій шляху з модуля admin матиме:
- Префікс
/admin. - Мітку
admin. - Залежність
get_token_header. - Відповідь
418. 🍵
Але це вплине лише на цей APIRouter у нашому застосунку, а не на будь-який інший код, який його використовує.
Наприклад, інші проєкти можуть використовувати той самий APIRouter з іншим методом автентифікації.
Додайте операцію шляху
Ми також можемо додавати операції шляху безпосередньо до застосунку FastAPI.
Тут ми це робимо... просто щоб показати, що так можна 🤷:
{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[21:23] title["app/main.py"] *}
і це працюватиме коректно разом з усіма іншими операціями шляху, доданими через app.include_router().
/// info | Дуже технічні деталі
Примітка: це дуже технічна деталь, яку ви, ймовірно, можете просто пропустити.
APIRouter не «монтуються», вони не ізольовані від решти застосунку.
Це тому, що ми хочемо включати їхні операції шляху в схему OpenAPI та інтерфейси користувача.
Оскільки ми не можемо просто ізолювати їх і «змонтувати» незалежно від решти, операції шляху «клонуються» (створюються заново), а не включаються безпосередньо.
///
Перевірте автоматичну документацію API
Тепер запустіть ваш застосунок:
$ fastapi dev app/main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
І відкрийте документацію за адресою http://127.0.0.1:8000/docs.
Ви побачите автоматичну документацію API, що включає шляхи з усіх підмодулів, з правильними шляхами (і префіксами) та правильними мітками:
Включайте той самий router кілька разів з різними prefix
Ви також можете використовувати .include_router() кілька разів з одним і тим самим router'ом, але з різними префіксами.
Це може бути корисно, наприклад, щоб публікувати той самий API під різними префіксами, наприклад /api/v1 і /api/latest.
Це просунуте використання, яке вам може й не знадобитися, але воно є на випадок, якщо потрібно.
Включіть один APIRouter до іншого
Так само як ви можете включити APIRouter у застосунок FastAPI, ви можете включити APIRouter в інший APIRouter, використовуючи:
router.include_router(other_router)
Переконайтеся, що ви робите це до включення router в застосунок FastAPI, щоб операції шляху з other_router також були включені.