Add plugin depends on and lifecycle ordering (#1153)

* feat: add priority support to LifespanManager.register

* feat: support lifespan stages and plugin dependencies

* Optimize implementations

* Fix dependency resolution

* Fix the depends on type

---------

Co-authored-by: Wu Clan <jianhengwu0407@gmail.com>
This commit is contained in:
Toby Wong
2026-04-17 16:54:48 +08:00
committed by GitHub
co-authored by Wu Clan
parent 0428a2b07d
commit 92f182cd09
7 changed files with 178 additions and 56 deletions
+10
View File
@@ -1,6 +1,7 @@
import dataclasses
from datetime import datetime
from typing import Any
from fastapi import Response
@@ -73,3 +74,12 @@ class SnowflakeInfo:
datacenter_id: int
worker_id: int
sequence: int
@dataclasses.dataclass(slots=True)
class PluginEntry:
name: str
depends_on: list[str] | None = None
extend: str | None = None
routers: list[str] | None = None
api: dict[str, Any] | None = None
+8
View File
@@ -145,3 +145,11 @@ class PrimaryKeyType(StrEnum):
autoincrement = 'autoincrement'
snowflake = 'snowflake'
class LifespanStage(IntEnum):
"""lifespan 执行阶段"""
core = 0
plugin = 1
tail = 2
+38 -11
View File
@@ -1,9 +1,11 @@
from collections.abc import Callable
from contextlib import AbstractAsyncContextManager, AsyncExitStack, asynccontextmanager
from typing import Any
from typing import Any, overload
from fastapi import FastAPI
from backend.common.enums import LifespanStage
LifespanFunc = Callable[[FastAPI], AbstractAsyncContextManager[dict[str, Any] | None]]
@@ -11,18 +13,42 @@ class LifespanManager:
"""FastAPI lifespan 管理器"""
def __init__(self) -> None:
self._lifespans: list[LifespanFunc] = []
self._lifespans: dict[LifespanStage, list[LifespanFunc]] = {
LifespanStage.core: [],
LifespanStage.plugin: [],
LifespanStage.tail: [],
}
def register(self, func: LifespanFunc) -> LifespanFunc:
@overload
def register(self, func: LifespanFunc) -> LifespanFunc: ...
@overload
def register(self, *, stage: LifespanStage) -> Callable[[LifespanFunc], LifespanFunc]: ...
def register(
self, func: LifespanFunc | None = None, *, stage: LifespanStage = LifespanStage.core
) -> LifespanFunc | Callable[[LifespanFunc], LifespanFunc]:
"""
注册 lifespan hook
:param func: lifespan hook
:param func: lifespan hook(直接装饰时使用)
:param stage: 执行阶段,控制粗粒度顺序,默认为 core
:return:
"""
if func not in self._lifespans:
self._lifespans.append(func)
return func
def decorator(f: LifespanFunc) -> LifespanFunc:
for hooks in self._lifespans.values():
for fn in hooks:
if fn is f:
return f
self._lifespans[stage].append(f)
return f
if func is not None:
return decorator(func)
return decorator
def build(self) -> LifespanFunc:
"""
@@ -35,10 +61,11 @@ class LifespanManager:
async def combined_lifespan(app: FastAPI): # noqa: ANN202
state: dict[str, Any] = {}
async with AsyncExitStack() as exit_stack:
for lifespan_fn in self._lifespans:
result = await exit_stack.enter_async_context(lifespan_fn(app))
if isinstance(result, dict):
state.update(result)
for stage in LifespanStage:
for lifespan_fn in self._lifespans[stage]:
result = await exit_stack.enter_async_context(lifespan_fn(app))
if isinstance(result, dict):
state.update(result)
for key, value in state.items():
setattr(app.state, key, value)