Життєвий цикл застосунку¶
Частина роботи виконується один раз на процес, а не один раз на запит: відкриття пулу з'єднань бази даних, створення спільного HTTP-клієнта, завантаження ML-моделі чи файлу конфігурації в пам'ять — і охайне згортання цього на виході. FastAPI кладе це у lifespan — асинхронний контекстний менеджер, прив'язаний до запуску та зупинки сервера.
Контекстний менеджер lifespan¶
Напишіть функцію з @asynccontextmanager, яка приймає застосунок. Усе до
yield виконується один раз при запуску, перш ніж обслужиться
будь-який запит; усе після yield виконується один раз при зупинці,
після останнього запиту. Передайте її у FastAPI(lifespan=...):
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from app.db import FakePool
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.pool = FakePool() # startup: open the resource
yield
app.state.pool.close() # shutdown: close it
app = FastAPI(lifespan=lifespan)
@app.get("/users")
def list_users(request: Request):
return {"users": request.app.state.pool.get_users()}
Ресурс живе на app.state — простому просторі імен для об'єктів рівня
застосунку — а ендпоінти дістаються до нього через request.app.state.
Запустіть застосунок, і Uvicorn запише в лог Application startup
complete, щойно виконається половина зі стартом; на Ctrl-C він виконує
половину із зупинкою перед виходом.
Оскільки це єдиний контекстний менеджер, налаштування та згортання стоять
поруч і мають спільну область видимості — форма «отримати, yield,
звільнити» (try/finally), для якої й створені контекстні менеджери
Python.
Note
Старіші декоратори @app.on_event("startup") /
@app.on_event("shutdown") ще існують, але вважаються застарілими. Для
нового коду використовуйте lifespan.
Доступ до ресурсу¶
Два способи скористатися тим, що створив lifespan:
request.app.state.pool— напряму, як вище.- Залежність (dependency), яка повертає
request.app.state.pool, щоб ендпоінти просилиpoolу своїй сигнатурі, а не діставалися крізь запит. Це ідіоматичний підхід, і йому присвячено окрему тему (Впровадження залежностей).
З досвіду Django: Найближче, що дає вам Django, — це
AppConfig.ready(): метод, який виконується один раз, коли реєстр застосунків завершує завантаження. Це місце для реєстрації сигналів, стартових перевірок чи прогріву кешу. Дві різниці мають значення:Він лише для запуску. У
ready()немає відповідника для зупинки — Django не має вбудованого хука завершення процесу для вашого коду. lifespan у FastAPI симетричний: та сама функція володіє обома кінцями, тож те, що ви відкрили, ви ж і закриваєте.Він виконується майже всюди.
ready()спрацьовує для кожної точки входу, яка завантажує реєстр застосунків —runserver, але також кожна командаmanage.py, міграції та shell. Тому вас застерігають не робити важку роботу (як-от відкриття реальних з'єднань) уready()без захисту.lifespanу FastAPI виконується лише тоді, коли ASGI-застосунок насправді обслуговується Uvicorn — а не коли ви імпортуєте модуль чи запускаєте разовий скрипт — тож «відкрити тут пул з'єднань» — це саме те, для чого він є.
Супровідні проєкти роблять асиметрію відчутною¶
Проєкт FastAPI відкриває пул при запуску й закриває його при зупинці, і
його тест перевіряє обидві половини. Проєкт Django відкриває той самий пул
у AppConfig.ready() і не має кроку зупинки — бо його нікуди подіти.
Той самий стартовий ресурс, але лише один фреймворк дає вам відповідне
згортання.
Зауваження щодо тестування¶
TestClient виконує lifespan лише коли використовується як контекстний
менеджер:
with TestClient(app) as client: # startup runs here
... # make requests
# shutdown runs here, on exit
client = TestClient(app) на рівні модуля повністю пропускає lifespan —
тож ендпоінт, що покладається на app.state.pool, впаде. Це збиває людей з
пантелику; коли ваші тести торкаються стану, створеного в lifespan,
використовуйте форму with.
Супровідні проєкти¶
У каталозі 05-demo/ цього розділу:
05-demo/fastapi/(завантажити) —lifespan, що відкриває й закриває пул; тест перевіряє «відкрито під час» і «закрито після».05-demo/django/(завантажити) —AppConfig.ready()відкриває пул; тест перевіряє, що він відкритий при запуску (зупинку тестувати нічим).
FastAPI: pytest (2 тести). Django: manage.py test (3 тести).