2024-12-01 12:31:11 +08:00
|
|
|
from collections.abc import Awaitable, Iterable
|
2024-10-26 15:36:01 +08:00
|
|
|
from types import TracebackType
|
2024-12-01 12:31:11 +08:00
|
|
|
from typing import Any, Callable, Optional, Union, cast
|
2023-06-24 14:47:35 +08:00
|
|
|
from typing_extensions import TypeAlias
|
2024-10-26 15:36:01 +08:00
|
|
|
|
|
|
|
import anyio
|
|
|
|
from anyio.abc import TaskGroup
|
|
|
|
from exceptiongroup import suppress
|
2023-03-29 15:59:54 +08:00
|
|
|
|
2024-12-01 12:31:11 +08:00
|
|
|
from nonebot.utils import is_coroutine_callable, run_sync
|
2023-03-29 15:59:54 +08:00
|
|
|
|
2023-06-24 14:47:35 +08:00
|
|
|
SYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Any]
|
|
|
|
ASYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Awaitable[Any]]
|
|
|
|
LIFESPAN_FUNC: TypeAlias = Union[SYNC_LIFESPAN_FUNC, ASYNC_LIFESPAN_FUNC]
|
2023-03-29 15:59:54 +08:00
|
|
|
|
|
|
|
|
|
|
|
class Lifespan:
|
|
|
|
def __init__(self) -> None:
|
2024-10-26 15:36:01 +08:00
|
|
|
self._task_group: Optional[TaskGroup] = None
|
|
|
|
|
2024-04-16 00:33:48 +08:00
|
|
|
self._startup_funcs: list[LIFESPAN_FUNC] = []
|
|
|
|
self._ready_funcs: list[LIFESPAN_FUNC] = []
|
|
|
|
self._shutdown_funcs: list[LIFESPAN_FUNC] = []
|
2023-03-29 15:59:54 +08:00
|
|
|
|
2024-10-26 15:36:01 +08:00
|
|
|
@property
|
|
|
|
def task_group(self) -> TaskGroup:
|
|
|
|
if self._task_group is None:
|
|
|
|
raise RuntimeError("Lifespan not started")
|
|
|
|
return self._task_group
|
|
|
|
|
|
|
|
@task_group.setter
|
|
|
|
def task_group(self, task_group: TaskGroup) -> None:
|
|
|
|
if self._task_group is not None:
|
|
|
|
raise RuntimeError("Lifespan already started")
|
|
|
|
self._task_group = task_group
|
|
|
|
|
2023-03-29 15:59:54 +08:00
|
|
|
def on_startup(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
|
|
|
self._startup_funcs.append(func)
|
|
|
|
return func
|
|
|
|
|
|
|
|
def on_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
|
|
|
self._shutdown_funcs.append(func)
|
|
|
|
return func
|
|
|
|
|
2023-12-10 18:12:10 +08:00
|
|
|
def on_ready(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
|
|
|
self._ready_funcs.append(func)
|
|
|
|
return func
|
|
|
|
|
2023-03-29 15:59:54 +08:00
|
|
|
@staticmethod
|
|
|
|
async def _run_lifespan_func(
|
2024-10-26 15:36:01 +08:00
|
|
|
funcs: Iterable[LIFESPAN_FUNC],
|
2023-03-29 15:59:54 +08:00
|
|
|
) -> None:
|
|
|
|
for func in funcs:
|
|
|
|
if is_coroutine_callable(func):
|
|
|
|
await cast(ASYNC_LIFESPAN_FUNC, func)()
|
|
|
|
else:
|
|
|
|
await run_sync(cast(SYNC_LIFESPAN_FUNC, func))()
|
|
|
|
|
|
|
|
async def startup(self) -> None:
|
2024-10-26 15:36:01 +08:00
|
|
|
# create background task group
|
|
|
|
self.task_group = anyio.create_task_group()
|
|
|
|
await self.task_group.__aenter__()
|
|
|
|
|
|
|
|
# run startup funcs
|
2023-03-29 15:59:54 +08:00
|
|
|
if self._startup_funcs:
|
|
|
|
await self._run_lifespan_func(self._startup_funcs)
|
|
|
|
|
2024-10-26 15:36:01 +08:00
|
|
|
# run ready funcs
|
2023-12-10 18:12:10 +08:00
|
|
|
if self._ready_funcs:
|
|
|
|
await self._run_lifespan_func(self._ready_funcs)
|
|
|
|
|
2024-10-26 15:36:01 +08:00
|
|
|
async def shutdown(
|
|
|
|
self,
|
|
|
|
*,
|
|
|
|
exc_type: Optional[type[BaseException]] = None,
|
|
|
|
exc_val: Optional[BaseException] = None,
|
|
|
|
exc_tb: Optional[TracebackType] = None,
|
|
|
|
) -> None:
|
2023-03-29 15:59:54 +08:00
|
|
|
if self._shutdown_funcs:
|
2024-10-26 15:36:01 +08:00
|
|
|
# reverse shutdown funcs to ensure stack order
|
|
|
|
await self._run_lifespan_func(reversed(self._shutdown_funcs))
|
|
|
|
|
|
|
|
# shutdown background task group
|
|
|
|
self.task_group.cancel_scope.cancel()
|
|
|
|
|
|
|
|
with suppress(Exception):
|
|
|
|
await self.task_group.__aexit__(exc_type, exc_val, exc_tb)
|
|
|
|
|
|
|
|
self._task_group = None
|
2023-03-29 15:59:54 +08:00
|
|
|
|
|
|
|
async def __aenter__(self) -> None:
|
|
|
|
await self.startup()
|
|
|
|
|
2024-10-26 15:36:01 +08:00
|
|
|
async def __aexit__(
|
|
|
|
self,
|
|
|
|
exc_type: Optional[type[BaseException]],
|
|
|
|
exc_val: Optional[BaseException],
|
|
|
|
exc_tb: Optional[TracebackType],
|
|
|
|
) -> None:
|
|
|
|
await self.shutdown(exc_type=exc_type, exc_val=exc_val, exc_tb=exc_tb)
|