Исходный код raito.plugins.lifespan.decorator
from collections.abc import AsyncGenerator, Callable
from contextlib import AbstractAsyncContextManager, asynccontextmanager, suppress
from typing import TypeAlias
from aiogram import Bot, Router
from raito.utils.helpers.safe_partial import safe_partial
__all__ = ("lifespan",)
FuncType: TypeAlias = Callable[..., AsyncGenerator[None, None]]
AsyncCtx: TypeAlias = AbstractAsyncContextManager[None]
LifespanStacks: TypeAlias = dict[int, list[AsyncCtx]]
_LIFESPAN_STACKS = "__lifespan_stacks__"
def _get_stack(router: Router) -> LifespanStacks:
stacks = getattr(router, _LIFESPAN_STACKS, None)
if stacks is None:
stacks = {}
setattr(router, _LIFESPAN_STACKS, stacks)
return stacks
[документация]
def lifespan(router: Router) -> Callable[[FuncType], FuncType]:
"""
Register a lifespan function for a given router, similar to FastAPI's lifespan handler.
The function must be an async generator: it runs setup before `yield`, and cleanup after.
"""
def decorator(func: FuncType) -> FuncType:
@asynccontextmanager
async def context(**kwargs: dict[str, object]) -> AsyncGenerator[None, None]:
gen = safe_partial(func, **kwargs)()
await gen.__anext__()
try:
yield
finally:
with suppress(StopAsyncIteration):
await gen.__anext__()
async def on_startup(**kwargs: dict[str, object]) -> None:
bot = kwargs.get("bot")
assert isinstance(bot, Bot), "Missing or invalid 'bot' in lifespan context"
ctx = context(**kwargs)
await ctx.__aenter__()
_get_stack(router).setdefault(bot.id, []).append(ctx)
async def on_shutdown(**kwargs: dict[str, object]) -> None:
bot = kwargs.get("bot")
assert isinstance(bot, Bot), "Missing or invalid 'bot' in lifespan context"
stack = _get_stack(router).get(bot.id, [])
for ctx in reversed(stack):
await ctx.__aexit__(None, None, None)
stack.clear()
router.startup.register(on_startup)
router.shutdown.register(on_shutdown)
return func
return decorator