FAPI006 lifespan-events¶
Co robi¶
Opt-in. Zgłasza przestarzałe zdarzenia startu i zamknięcia aplikacji lub routera FastAPI:
- Przestarzałe (ostrzeżenie):
@app.on_event("startup"),@app.on_event("shutdown"),app.add_event_handler(...)iFastAPI(on_startup=..., on_shutdown=...)(to samo naAPIRouter). FastAPI zastępuje je menedżerem kontekstulifespan=. - Nigdy się nie wykona (błąd): ta sama rejestracja na aplikacji, która ustawia też
lifespan=, albo na routerze, który dołącza aplikacja zlifespan=. FastAPI uruchamia wtedy tylko lifespan, a handlery są po cichu ignorowane.
Zgłoszenie stoi na rejestracji: dekoratorze @app.on_event(...), wywołaniu add_event_handler albo argumencie on_startup= / on_shutdown=. Odbiorca może być w innym pliku: handler w app/events.py jest dopasowywany do aplikacji w app/main.py przez importy projektu.
Dlaczego to źle¶
Dokumentacja FastAPI mówi to wprost: "It's all lifespan or all events, not both." Agent, który dopisze @app.on_event("startup") do aplikacji z już ustawionym lifespan=, pisze handler, który nigdy się nie wykona. Nic nie zawodzi przy starcie, pula połączeń nigdy się nie otwiera, a dowiaduje się o tym dopiero pierwsze żądanie. Ruff nie ma na to reguły.
Przykład¶
[tool.inwards]
layers = [{ name = "app", modules = ["app"] }]
[tool.inwards.rules]
extend-select = ["FAPI006"]
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
yield
app = FastAPI(lifespan=lifespan)
from app.main import app
@app.on_event("startup")
async def connect() -> None:
print("connecting")
app/events.py:4:1: FAPI006 `@app.on_event("startup")` never runs: `app` (app/main.py:11) sets `lifespan=`, and FastAPI then ignores event handlers. It's all lifespan or all events, not both.
fix: Move the handler into the lifespan context manager.
1. Move what this handler does into the lifespan function that is already set.
2. Write one async lifespan function that takes the app, with the startup code before its `yield` and the shutdown code after it, wrap it with `contextlib.asynccontextmanager`, and pass it as `lifespan=`.
3. Delete the old handler only after its body has moved: don't remove it to make this finding go away, as its startup or shutdown work would be lost.
docs: https://sircypkowskyy.github.io/inwards/rules/FAPI006/
Poprawione: kod startu przechodzi do lifespan, przed yield.
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
print("connecting")
yield
app = FastAPI(lifespan=lifespan)
Jak działa¶
FAPI006 czyta wspólny model FastAPI (ADR-037): każdy dekorator on_event i wywołanie add_event_handler jest zapisywane razem z aplikacją lub routerem, na którym je wywołano, a odbiorca jest rozwiązywany do swojego wywołania FastAPI(...) lub APIRouter(...), w tym pliku albo w innym.
- Rejestracja na obiekcie, którego konstruktor przekazuje
lifespan=(cokolwiek pozaNone), to błąd. - Rejestracja na routerze to też błąd, gdy aplikacja albo router pod nią, który go dołącza, przekazuje
lifespan=. Dołączenia pochodzą z grafu aplikacji i routerów z FAPI003. - Każda inna rejestracja oraz każde
on_startup=/on_shutdown=, które nie jest pustą listą aniNone, to ostrzeżenie.
Gdzie działa:
inwards check,inwards baselinei Stop gate zgłaszają wszystkie trzy.- Hook PostToolUse i edytor nie czytają grafu routerów: handler routera jest tam ostrzeżeniem, a Stop gate podnosi go do błędu, gdy aplikacja nad nim ma lifespan.
Jak naprawić¶
- Napisz jedną funkcję
@asynccontextmanager, która przyjmuje aplikację: kod startu idzie przed jejyield, kod zamknięcia po nim. - Przekaż ją jako
FastAPI(lifespan=...)i usuń handleryon_eventoraz listyon_startup=/on_shutdown=, gdy ich treść już przeniesiono. - Nie usuwaj handlera, żeby uciszyć zgłoszenie: praca, którą wykonuje przy starcie lub zamknięciu, przepadłaby.
Bezpieczeństwo poprawki¶
Brak automatycznej poprawki (autofix: false). Scalenie handlerów w lifespan zmienia kolejność, w jakiej wykonuje się kod startu i zamknięcia, a to decyzja autora.
Konfiguracja¶
Waga: błąd, a zgłoszenia samej przestarzałości mają wagę ostrzeżenia. Opt-in: włącz przez extend-select. Nie ma własnych opcji; modules działa jak w każdej regule, a check-tests jak w każdej regule treści (tabele opcji).
Każde zgłoszenie można wyciszyć w kodzie przez # inwards: ignore[FAPI006] reason="..." w linii rejestracji.
Znane ograniczenia¶
Nieznane oznacza ciszę dla błędu: rejestracja zostaje ostrzeżeniem, gdy
- konstruktor aplikacji przekazuje
**kwargs, które mogą ukrywaćlifespan=; - któregoś
include_routerw projekcie nie da się śledzić, więc rodzice routera są nieznani.
Dalsze ograniczenia:
- Odbiorca, którego Inwards nie umie rozwiązać do wywołania
FastAPI(...)lubAPIRouter(...), na przykład aplikacja, którą funkcja dostaje jako parametr, w ogóle nie jest zgłaszany. - Aplikacje Starlette (
Starlette(on_startup=...)) nie są czytane, tylkoFastAPIiAPIRouter. - Lifespan ustawiony po konstrukcji (
app.router.lifespan_context = ...) nie jest widoczny. - Plik, który nie wspomina FastAPI, jest czytany tylko wtedy, gdy zawiera
on_eventlubadd_event_handler.
Źródła¶
- ADR-037: rodzina FastAPI, opt-in, i wspólny model.
- Dokumentacja FastAPI, Lifespan Events: "It's all lifespan or all events, not both."