diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index bb680846..ac727428 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -42,7 +42,8 @@ jobs: - name: 生成API markdown run: |- python -m pip install pydantic - python liteyuki/mkdoc.py + python -m litedoc liteyuki -o docs/dev/api -l zh-Hans -t vuepress + python -m litedoc liteyuki -o docs/en/dev/api -l en -t vuepress - name: 构建文档 diff --git a/docs/dev/api/README.md b/docs/dev/api/README.md index 181ec73d..5e57461c 100644 --- a/docs/dev/api/README.md +++ b/docs/dev/api/README.md @@ -1,7 +1,3 @@ --- title: liteyuki -index: true -icon: laptop-code -category: API --- - diff --git a/docs/dev/api/bot/README.md b/docs/dev/api/bot/README.md index 9f019444..5ca2bf1d 100644 --- a/docs/dev/api/bot/README.md +++ b/docs/dev/api/bot/README.md @@ -1,22 +1,18 @@ --- title: liteyuki.bot -index: true -icon: laptop-code -category: API --- - -### ***def*** `get_bot() -> LiteyukiBot` - -获取轻雪实例 +### *func* `get_bot() -> LiteyukiBot` -Returns: +**说明**: 获取轻雪实例 + + +**返回**: LiteyukiBot: 当前的轻雪实例 - LiteyukiBot: 当前的轻雪实例
-源代码 + 源代码 ```python def get_bot() -> LiteyukiBot: @@ -35,24 +31,21 @@ def get_bot() -> LiteyukiBot: ```
-### ***def*** `get_config(key: str, default: Any) -> Any` - -获取配置 - -Args: - - key: 配置键 - - default: 默认值 +### *func* `get_config(key: str = None) -> Any` -Returns: +**说明**: 获取配置 + +**参数**: +> - key: 配置键 +> - default: 默认值 + +**返回**: Any: 配置值 - Any: 配置值
-源代码 + 源代码 ```python def get_config(key: str, default: Any=None) -> Any: @@ -69,26 +62,22 @@ def get_config(key: str, default: Any=None) -> Any: ```
-### ***def*** `get_config_with_compat(key: str, compat_keys: tuple[str], default: Any) -> Any` - -获取配置,兼容旧版本 - -Args: - - key: 配置键 - - compat_keys: 兼容键 - - default: 默认值 +### *func* `get_config_with_compat(key: str = None) -> Any` -Returns: +**说明**: 获取配置,兼容旧版本 + +**参数**: +> - key: 配置键 +> - compat_keys: 兼容键 +> - default: 默认值 + +**返回**: Any: 配置值 - Any: 配置值
-源代码 + 源代码 ```python def get_config_with_compat(key: str, compat_keys: tuple[str], default: Any=None) -> Any: @@ -112,12 +101,11 @@ def get_config_with_compat(key: str, compat_keys: tuple[str], default: Any=None) ```
-### ***def*** `print_logo() -> None` - +### *func* `print_logo()`
-源代码 + 源代码 ```python def print_logo(): @@ -125,22 +113,20 @@ def print_logo(): ```
-### ***class*** `LiteyukiBot` +### **class** `LiteyukiBot` +### *method* `__init__(self) -> None` -###   ***def*** `__init__(self) -> None` +**说明**: 初始化轻雪实例 - 初始化轻雪实例 +**参数**: +> - *args: +> - **kwargs: 配置 -Args: - - *args: - - **kwargs: 配置
-源代码 + 源代码 ```python def __init__(self, *args, **kwargs) -> None: @@ -177,12 +163,15 @@ def __init__(self, *args, **kwargs) -> None: ```
-###   ***def*** `run(self) -> None` +### *method* `run(self)` + + + +**说明**: 启动逻辑 - 启动逻辑
-源代码 + 源代码 ```python def run(self): @@ -196,14 +185,15 @@ def run(self): ```
-###   ***def*** `keep_alive(self) -> None` +### *method* `keep_alive(self)` - 保持轻雪运行 -Returns: + +**说明**: 保持轻雪运行 +
-源代码 + 源代码 ```python def keep_alive(self): @@ -221,14 +211,46 @@ def keep_alive(self): ```
-###   ***def*** `restart(self, delay: int) -> None` +### *method* `_handle_exit(self, signum, frame)` - 重启轻雪本体 -Returns: + +**说明**: 信号处理 + +**参数**: +> - signum: +> - frame: +
-源代码 + 源代码 + +```python +def _handle_exit(self, signum, frame): + """ + 信号处理 + Args: + signum: + frame: + + Returns: + + """ + logger.info('Received signal, stopping all processes.') + self.stop() + sys.exit(0) +``` +
+ +### *method* `restart(self, delay: int = 0)` + + + +**说明**: 重启轻雪本体 + + +
+ 源代码 ```python def restart(self, delay: int=0): @@ -257,18 +279,18 @@ def restart(self, delay: int=0): ```
-###   ***def*** `restart_process(self, name: Optional[str]) -> None` +### *method* `restart_process(self, name: Optional[str] = None)` - 停止轻雪 -Args: - name: 进程名称, 默认为None, 所有进程 +**说明**: 停止轻雪 + +**参数**: +> - name: 进程名称, 默认为None, 所有进程 -Returns:
-源代码 + 源代码 ```python def restart_process(self, name: Optional[str]=None): @@ -290,14 +312,15 @@ def restart_process(self, name: Optional[str]=None): ```
-###   ***def*** `init(self) -> None` +### *method* `init(self)` - 初始化轻雪, 自动调用 -Returns: + +**说明**: 初始化轻雪, 自动调用 +
-源代码 + 源代码 ```python def init(self, *args, **kwargs): @@ -310,12 +333,11 @@ def init(self, *args, **kwargs): ```
-###   ***def*** `init_logger(self) -> None` +### *method* `init_logger(self)` - 
-源代码 + 源代码 ```python def init_logger(self): @@ -323,14 +345,15 @@ def init_logger(self): ```
-###   ***def*** `stop(self) -> None` +### *method* `stop(self)` - 停止轻雪 -Returns: + +**说明**: 停止轻雪 +
-源代码 + 源代码 ```python def stop(self): @@ -344,20 +367,18 @@ def stop(self): ```
-###   ***def*** `on_before_start(self, func: LIFESPAN_FUNC) -> None` - - 注册启动前的函数 - -Args: - - func: +### *method* `on_before_start(self, func: LIFESPAN_FUNC)` -Returns: +**说明**: 注册启动前的函数 + +**参数**: +> - func: +
-源代码 + 源代码 ```python def on_before_start(self, func: LIFESPAN_FUNC): @@ -373,20 +394,18 @@ def on_before_start(self, func: LIFESPAN_FUNC): ```
-###   ***def*** `on_after_start(self, func: LIFESPAN_FUNC) -> None` - - 注册启动后的函数 - -Args: - - func: +### *method* `on_after_start(self, func: LIFESPAN_FUNC)` -Returns: +**说明**: 注册启动后的函数 + +**参数**: +> - func: +
-源代码 + 源代码 ```python def on_after_start(self, func: LIFESPAN_FUNC): @@ -402,20 +421,18 @@ def on_after_start(self, func: LIFESPAN_FUNC): ```
-###   ***def*** `on_after_shutdown(self, func: LIFESPAN_FUNC) -> None` - - 注册停止后的函数:未实现 - -Args: - - func: +### *method* `on_after_shutdown(self, func: LIFESPAN_FUNC)` -Returns: +**说明**: 注册停止后的函数:未实现 + +**参数**: +> - func: +
-源代码 + 源代码 ```python def on_after_shutdown(self, func: LIFESPAN_FUNC): @@ -431,20 +448,18 @@ def on_after_shutdown(self, func: LIFESPAN_FUNC): ```
-###   ***def*** `on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> None` - - 注册进程停止前的函数,为子进程停止时调用 - -Args: - - func: +### *method* `on_before_process_shutdown(self, func: LIFESPAN_FUNC)` -Returns: +**说明**: 注册进程停止前的函数,为子进程停止时调用 + +**参数**: +> - func: +
-源代码 + 源代码 ```python def on_before_process_shutdown(self, func: LIFESPAN_FUNC): @@ -460,20 +475,18 @@ def on_before_process_shutdown(self, func: LIFESPAN_FUNC): ```
-###   ***def*** `on_before_process_restart(self, func: LIFESPAN_FUNC) -> None` - - 注册进程重启前的函数,为子进程重启时调用 - -Args: - - func: +### *method* `on_before_process_restart(self, func: LIFESPAN_FUNC)` -Returns: +**说明**: 注册进程重启前的函数,为子进程重启时调用 + +**参数**: +> - func: +
-源代码 + 源代码 ```python def on_before_process_restart(self, func: LIFESPAN_FUNC): @@ -489,20 +502,18 @@ def on_before_process_restart(self, func: LIFESPAN_FUNC): ```
-###   ***def*** `on_after_restart(self, func: LIFESPAN_FUNC) -> None` - - 注册重启后的函数:未实现 - -Args: - - func: +### *method* `on_after_restart(self, func: LIFESPAN_FUNC)` -Returns: +**说明**: 注册重启后的函数:未实现 + +**参数**: +> - func: +
-源代码 + 源代码 ```python def on_after_restart(self, func: LIFESPAN_FUNC): @@ -518,20 +529,18 @@ def on_after_restart(self, func: LIFESPAN_FUNC): ```
-###   ***def*** `on_after_nonebot_init(self, func: LIFESPAN_FUNC) -> None` - - 注册nonebot初始化后的函数 - -Args: - - func: +### *method* `on_after_nonebot_init(self, func: LIFESPAN_FUNC)` -Returns: +**说明**: 注册nonebot初始化后的函数 + +**参数**: +> - func: +
-源代码 + 源代码 ```python def on_after_nonebot_init(self, func: LIFESPAN_FUNC): @@ -547,35 +556,7 @@ def on_after_nonebot_init(self, func: LIFESPAN_FUNC): ```
-### ***var*** `executable = sys.executable` - - - -### ***var*** `args = sys.argv` - - - -### ***var*** `chan_active = get_channel(f'{name}-active')` - - - -### ***var*** `cmd = 'start'` - - - -### ***var*** `chan_active = get_channel(f'{process_name}-active')` - - - -### ***var*** `cmd = 'nohup'` - - - -### ***var*** `cmd = 'open'` - - - -### ***var*** `cmd = 'nohup'` - +### ***var*** `_BOT_INSTANCE = NO_DEFAULT` +- **类型**: `LiteyukiBot` diff --git a/docs/dev/api/bot/lifespan.md b/docs/dev/api/bot/lifespan.md index 6466bd52..0ac7793d 100644 --- a/docs/dev/api/bot/lifespan.md +++ b/docs/dev/api/bot/lifespan.md @@ -1,57 +1,16 @@ --- title: liteyuki.bot.lifespan -order: 1 -icon: laptop-code -category: API --- +### **class** `Lifespan` +### *method* `__init__(self) -> None` -### ***def*** `run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC]) -> None` -运行函数 -Args: +**说明**: 轻雪生命周期管理,启动、停止、重启 - funcs: - -Returns:
-源代码 - -```python -@staticmethod -def run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC], *args, **kwargs) -> None: - """ - 运行函数 - Args: - funcs: - Returns: - """ - try: - loop = asyncio.get_event_loop() - except RuntimeError: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - tasks = [] - for func in funcs: - if is_coroutine_callable(func): - tasks.append(func(*args, **kwargs)) - else: - tasks.append(async_wrapper(func)(*args, **kwargs)) - loop.run_until_complete(asyncio.gather(*tasks)) -``` -
- -### ***class*** `Lifespan` - - - -###   ***def*** `__init__(self) -> None` - - 轻雪生命周期管理,启动、停止、重启 - -
-源代码 + 源代码 ```python def __init__(self) -> None: @@ -69,19 +28,19 @@ def __init__(self) -> None: ```
-###   ***@staticmethod*** -###   ***def*** `run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC]) -> None` +### `@staticmethod` +### *method* `run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC]) -> None` - 运行函数 -Args: - funcs: +**说明**: 运行函数 + +**参数**: +> - funcs: -Returns:
-源代码 + 源代码 ```python @staticmethod @@ -107,20 +66,20 @@ def run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC], *args, **kwarg ```
-###   ***def*** `on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` +### *method* `on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` - 注册启动时的函数 -Args: - func: +**说明**: 注册启动时的函数 -Returns: +**参数**: +> - func: + +**返回**: LIFESPAN_FUNC: - LIFESPAN_FUNC:
-源代码 + 源代码 ```python def on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: @@ -136,20 +95,20 @@ def on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: ```
-###   ***def*** `on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` +### *method* `on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` - 注册启动时的函数 -Args: - func: +**说明**: 注册启动时的函数 -Returns: +**参数**: +> - func: + +**返回**: LIFESPAN_FUNC: - LIFESPAN_FUNC:
-源代码 + 源代码 ```python def on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: @@ -165,20 +124,20 @@ def on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: ```
-###   ***def*** `on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` +### *method* `on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` - 注册停止前的函数 -Args: - func: +**说明**: 注册停止前的函数 -Returns: +**参数**: +> - func: + +**返回**: LIFESPAN_FUNC: - LIFESPAN_FUNC:
-源代码 + 源代码 ```python def on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: @@ -194,22 +153,20 @@ def on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: ```
-###   ***def*** `on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` - - 注册停止后的函数 - -Args: - - func: +### *method* `on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` -Returns: +**说明**: 注册停止后的函数 + +**参数**: +> - func: + +**返回**: LIFESPAN_FUNC: - LIFESPAN_FUNC:
-源代码 + 源代码 ```python def on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: @@ -227,20 +184,20 @@ def on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: ```
-###   ***def*** `on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` +### *method* `on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` - 注册重启时的函数 -Args: - func: +**说明**: 注册重启时的函数 -Returns: +**参数**: +> - func: + +**返回**: LIFESPAN_FUNC: - LIFESPAN_FUNC:
-源代码 + 源代码 ```python def on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: @@ -256,20 +213,20 @@ def on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: ```
-###   ***def*** `on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` +### *method* `on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` - 注册重启后的函数 -Args: - func: +**说明**: 注册重启后的函数 -Returns: +**参数**: +> - func: + +**返回**: LIFESPAN_FUNC: - LIFESPAN_FUNC:
-源代码 + 源代码 ```python def on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: @@ -285,20 +242,18 @@ def on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: ```
-###   ***def*** `on_after_nonebot_init(self, func: Any) -> None` - - 注册 NoneBot 初始化后的函数 - -Args: - - func: +### *method* `on_after_nonebot_init(self, func)` -Returns: +**说明**: 注册 NoneBot 初始化后的函数 + +**参数**: +> - func: +
-源代码 + 源代码 ```python def on_after_nonebot_init(self, func): @@ -315,14 +270,15 @@ def on_after_nonebot_init(self, func): ```
-###   ***def*** `before_start(self) -> None` +### *method* `before_start(self) -> None` - 启动前 -Returns: + +**说明**: 启动前 +
-源代码 + 源代码 ```python def before_start(self) -> None: @@ -335,14 +291,15 @@ def before_start(self) -> None: ```
-###   ***def*** `after_start(self) -> None` +### *method* `after_start(self) -> None` - 启动后 -Returns: + +**说明**: 启动后 +
-源代码 + 源代码 ```python def after_start(self) -> None: @@ -355,14 +312,15 @@ def after_start(self) -> None: ```
-###   ***def*** `before_process_shutdown(self) -> None` +### *method* `before_process_shutdown(self) -> None` - 停止前 -Returns: + +**说明**: 停止前 +
-源代码 + 源代码 ```python def before_process_shutdown(self) -> None: @@ -375,14 +333,15 @@ def before_process_shutdown(self) -> None: ```
-###   ***def*** `after_shutdown(self) -> None` +### *method* `after_shutdown(self) -> None` - 停止后 -Returns: + +**说明**: 停止后 +
-源代码 + 源代码 ```python def after_shutdown(self) -> None: @@ -395,14 +354,15 @@ def after_shutdown(self) -> None: ```
-###   ***def*** `before_process_restart(self) -> None` +### *method* `before_process_restart(self) -> None` - 重启前 -Returns: + +**说明**: 重启前 +
-源代码 + 源代码 ```python def before_process_restart(self) -> None: @@ -415,14 +375,15 @@ def before_process_restart(self) -> None: ```
-###   ***def*** `after_restart(self) -> None` +### *method* `after_restart(self) -> None` - 重启后 -Returns: + +**说明**: 重启后 +
-源代码 + 源代码 ```python def after_restart(self) -> None: @@ -436,15 +397,27 @@ def after_restart(self) -> None: ```
-### ***var*** `tasks = []` +### ***var*** `SYNC_LIFESPAN_FUNC = Callable[[], Any]` +- **类型**: `TypeAlias` +### ***var*** `ASYNC_LIFESPAN_FUNC = Callable[[], Awaitable[Any]]` -### ***var*** `loop = asyncio.get_event_loop()` +- **类型**: `TypeAlias` +### ***var*** `LIFESPAN_FUNC = SYNC_LIFESPAN_FUNC | ASYNC_LIFESPAN_FUNC` +- **类型**: `TypeAlias` -### ***var*** `loop = asyncio.new_event_loop()` +### ***var*** `SYNC_PROCESS_LIFESPAN_FUNC = Callable[[str], Any]` +- **类型**: `TypeAlias` +### ***var*** `ASYNC_PROCESS_LIFESPAN_FUNC = Callable[[str], Awaitable[Any]]` + +- **类型**: `TypeAlias` + +### ***var*** `PROCESS_LIFESPAN_FUNC = SYNC_PROCESS_LIFESPAN_FUNC | ASYNC_PROCESS_LIFESPAN_FUNC` + +- **类型**: `TypeAlias` diff --git a/docs/dev/api/comm/README.md b/docs/dev/api/comm/README.md index 09bccc34..2c1adbbf 100644 --- a/docs/dev/api/comm/README.md +++ b/docs/dev/api/comm/README.md @@ -1,7 +1,3 @@ --- title: liteyuki.comm -index: true -icon: laptop-code -category: API --- - diff --git a/docs/dev/api/comm/channel.md b/docs/dev/api/comm/channel.md index 8e5e170f..8617f964 100644 --- a/docs/dev/api/comm/channel.md +++ b/docs/dev/api/comm/channel.md @@ -1,22 +1,19 @@ --- title: liteyuki.comm.channel -order: 1 -icon: laptop-code -category: API --- +### *func* `set_channel()` -### ***def*** `set_channel(name: str, channel: Channel) -> None` -设置通道实例 -Args: +**说明**: 设置通道实例 - name: 通道名称 +**参数**: +> - name: 通道名称 +> - channel: 通道实例 - channel: 通道实例
-源代码 + 源代码 ```python def set_channel(name: str, channel: Channel): @@ -35,16 +32,18 @@ def set_channel(name: str, channel: Channel): ```
-### ***def*** `set_channels(channels: dict[str, Channel]) -> None` +### *func* `set_channels()` -设置通道实例 -Args: - channels: 通道名称 +**说明**: 设置通道实例 + +**参数**: +> - channels: 通道名称 +
-源代码 + 源代码 ```python def set_channels(channels: dict[str, Channel]): @@ -58,18 +57,18 @@ def set_channels(channels: dict[str, Channel]): ```
-### ***def*** `get_channel(name: str) -> Channel` +### *func* `get_channel() -> Channel` -获取通道实例 -Args: - name: 通道名称 +**说明**: 获取通道实例 + +**参数**: +> - name: 通道名称 -Returns:
-源代码 + 源代码 ```python def get_channel(name: str) -> Channel: @@ -88,14 +87,15 @@ def get_channel(name: str) -> Channel: ```
-### ***def*** `get_channels() -> dict[str, Channel]` +### *func* `get_channels() -> dict[str, Channel]` -获取通道实例 -Returns: + +**说明**: 获取通道实例 +
-源代码 + 源代码 ```python def get_channels() -> dict[str, Channel]: @@ -112,12 +112,12 @@ def get_channels() -> dict[str, Channel]: ```
-### ***def*** `on_set_channel(data: tuple[str, dict[str, Any]]) -> None` - +### `@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'set_channel')` +### *func* `on_set_channel()`
-源代码 + 源代码 ```python @channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'set_channel') @@ -127,12 +127,12 @@ def on_set_channel(data: tuple[str, dict[str, Any]]): ```
-### ***def*** `on_get_channel(data: tuple[str, dict[str, Any]]) -> None` - +### `@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channel')` +### *func* `on_get_channel()`
-源代码 + 源代码 ```python @channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channel') @@ -142,12 +142,12 @@ def on_get_channel(data: tuple[str, dict[str, Any]]): ```
-### ***def*** `on_get_channels(data: tuple[str, dict[str, Any]]) -> None` - +### `@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channels')` +### *func* `on_get_channels()`
-源代码 + 源代码 ```python @channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channels') @@ -157,81 +157,23 @@ def on_get_channels(data: tuple[str, dict[str, Any]]): ```
-### ***def*** `decorator(func: Callable[[T], Any]) -> Callable[[T], Any]` +### **class** `Channel(Generic[T])` +### *method* `__init__(self, _id: str = '', type_check: Optional[bool] = None)` +**说明**: 初始化通道 + +**参数**: +> - _id: 通道ID +> - type_check: 是否开启类型检查, 若为空,则传入泛型默认开启,否则默认关闭 + +
-源代码 + 源代码 ```python -def decorator(func: Callable[[T], Any]) -> Callable[[T], Any]: - global _func_id - - async def wrapper(data: T) -> Any: - if filter_func is not None: - if is_coroutine_callable(filter_func): - if not await filter_func(data): - return - elif not filter_func(data): - return - if is_coroutine_callable(func): - return await func(data) - else: - return func(data) - _callback_funcs[_func_id] = wrapper - if IS_MAIN_PROCESS: - self._on_main_receive_funcs.append(_func_id) - else: - self._on_sub_receive_funcs.append(_func_id) - _func_id += 1 - return func -``` -
- -### ***async def*** `wrapper(data: T) -> Any` - - - -
-源代码 - -```python -async def wrapper(data: T) -> Any: - if filter_func is not None: - if is_coroutine_callable(filter_func): - if not await filter_func(data): - return - elif not filter_func(data): - return - if is_coroutine_callable(func): - return await func(data) - else: - return func(data) -``` -
- -### ***class*** `Channel(Generic[T])` - -通道类,可以在进程间和进程内通信,双向但同时只能有一个发送者和一个接收者 - -有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器 - -###   ***def*** `__init__(self, _id: str, type_check: Optional[bool]) -> None` - - 初始化通道 - -Args: - - _id: 通道ID - - type_check: 是否开启类型检查, 若为空,则传入泛型默认开启,否则默认关闭 - -
-源代码 - -```python -def __init__(self, _id: str, type_check: Optional[bool]=None): +def __init__(self, _id: str='', type_check: Optional[bool]=None): """ 初始化通道 Args: @@ -254,16 +196,90 @@ def __init__(self, _id: str, type_check: Optional[bool]=None): ```
-###   ***def*** `send(self, data: T) -> None` +### *method* `_get_generic_type(self) -> Optional[type]` - 发送数据 -Args: - data: 数据 +**说明**: 获取通道传递泛型类型 + + +**返回**: Optional[type]: 泛型类型 +
-源代码 + 源代码 + +```python +def _get_generic_type(self) -> Optional[type]: + """ + 获取通道传递泛型类型 + + Returns: + Optional[type]: 泛型类型 + """ + if hasattr(self, '__orig_class__'): + return get_args(self.__orig_class__)[0] + return None +``` +
+ +### *method* `_validate_structure(self, data: Any, structure: type) -> bool` + + + +**说明**: 验证数据结构 + +**参数**: +> - data: 数据 +> - structure: 结构 + +**返回**: bool: 是否通过验证 + + +
+ 源代码 + +```python +def _validate_structure(self, data: Any, structure: type) -> bool: + """ + 验证数据结构 + Args: + data: 数据 + structure: 结构 + + Returns: + bool: 是否通过验证 + """ + if isinstance(structure, type): + return isinstance(data, structure) + elif isinstance(structure, tuple): + if not isinstance(data, tuple) or len(data) != len(structure): + return False + return all((self._validate_structure(d, s) for d, s in zip(data, structure))) + elif isinstance(structure, list): + if not isinstance(data, list): + return False + return all((self._validate_structure(d, structure[0]) for d in data)) + elif isinstance(structure, dict): + if not isinstance(data, dict): + return False + return all((k in data and self._validate_structure(data[k], structure[k]) for k in structure)) + return False +``` +
+ +### *method* `send(self, data: T)` + + + +**说明**: 发送数据 + +**参数**: +> - data: 数据 + + +
+ 源代码 ```python def send(self, data: T): @@ -282,14 +298,15 @@ def send(self, data: T): ```
-###   ***def*** `receive(self) -> T` +### *method* `receive(self) -> T` - 接收数据 -Args: + +**说明**: 接收数据 +
-源代码 + 源代码 ```python def receive(self) -> T: @@ -305,12 +322,15 @@ def receive(self) -> T: ```
-###   ***def*** `close(self) -> None` +### *method* `close(self)` + + + +**说明**: 关闭通道 - 关闭通道
-源代码 + 源代码 ```python def close(self): @@ -323,20 +343,20 @@ def close(self): ```
-###   ***def*** `on_receive(self, filter_func: Optional[FILTER_FUNC]) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]` +### *method* `on_receive(self, filter_func: Optional[FILTER_FUNC] = None) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]` - 接收数据并执行函数 -Args: - filter_func: 过滤函数,为None则不过滤 +**说明**: 接收数据并执行函数 -Returns: +**参数**: +> - filter_func: 过滤函数,为None则不过滤 + +**返回**: 装饰器,装饰一个函数在接收到数据后执行 - 装饰器,装饰一个函数在接收到数据后执行
-源代码 + 源代码 ```python def on_receive(self, filter_func: Optional[FILTER_FUNC]=None) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]: @@ -377,51 +397,159 @@ def on_receive(self, filter_func: Optional[FILTER_FUNC]=None) -> Callable[[Calla ```
-### ***var*** `T = TypeVar('T')` +### *method* `_run_on_main_receive_funcs(self, data: Any)` -### ***var*** `channel_deliver_active_channel = Channel(_id='channel_deliver_active_channel')` +**说明**: 运行接收函数 + +**参数**: +> - data: 数据 + + +
+ 源代码 + +```python +def _run_on_main_receive_funcs(self, data: Any): + """ + 运行接收函数 + Args: + data: 数据 + """ + for func_id in self._on_main_receive_funcs: + func = _callback_funcs[func_id] + run_coroutine(func(data)) +``` +
+ +### *method* `_run_on_sub_receive_funcs(self, data: Any)` -### ***var*** `channel_deliver_passive_channel = Channel(_id='channel_deliver_passive_channel')` +**说明**: 运行接收函数 + +**参数**: +> - data: 数据 + + +
+ 源代码 + +```python +def _run_on_sub_receive_funcs(self, data: Any): + """ + 运行接收函数 + Args: + data: 数据 + """ + for func_id in self._on_sub_receive_funcs: + func = _callback_funcs[func_id] + run_coroutine(func(data)) +``` +
+ +### *method* `_start_main_receive_loop(self)` -### ***var*** `recv_chan = data[1]['recv_chan']` +**说明**: 开始接收数据 + + +
+ 源代码 + +```python +def _start_main_receive_loop(self): + """ + 开始接收数据 + """ + self.is_main_receive_loop_running = True + while not self._closed: + data = self.conn_recv.recv() + self._run_on_main_receive_funcs(data) +``` +
+ +### *method* `_start_sub_receive_loop(self)` -### ***var*** `recv_chan = Channel[Channel[Any]]('recv_chan')` +**说明**: 开始接收数据 +
+ 源代码 -### ***var*** `recv_chan = Channel[dict[str, Channel[Any]]]('recv_chan')` +```python +def _start_sub_receive_loop(self): + """ + 开始接收数据 + """ + self.is_sub_receive_loop_running = True + while not self._closed: + data = self.conn_recv.recv() + self._run_on_sub_receive_funcs(data) +``` +
+### ***var*** `SYNC_ON_RECEIVE_FUNC = Callable[[T], Any]` +- **类型**: `TypeAlias` -### ***var*** `type_check = self._get_generic_type() is not None` +### ***var*** `ASYNC_ON_RECEIVE_FUNC = Callable[[T], Coroutine[Any, Any, Any]]` +- **类型**: `TypeAlias` +### ***var*** `ON_RECEIVE_FUNC = SYNC_ON_RECEIVE_FUNC | ASYNC_ON_RECEIVE_FUNC` -### ***var*** `data = self.conn_recv.recv()` +- **类型**: `TypeAlias` +### ***var*** `SYNC_FILTER_FUNC = Callable[[T], bool]` +- **类型**: `TypeAlias` -### ***var*** `func = _callback_funcs[func_id]` +### ***var*** `ASYNC_FILTER_FUNC = Callable[[T], Coroutine[Any, Any, bool]]` +- **类型**: `TypeAlias` +### ***var*** `FILTER_FUNC = SYNC_FILTER_FUNC | ASYNC_FILTER_FUNC` -### ***var*** `func = _callback_funcs[func_id]` +- **类型**: `TypeAlias` +### ***var*** `_func_id = 0` +- **类型**: `int` -### ***var*** `data = self.conn_recv.recv()` +### ***var*** `_channel = {}` +- **类型**: `dict[str, 'Channel']` +### ***var*** `_callback_funcs = {}` -### ***var*** `data = self.conn_recv.recv()` +- **类型**: `dict[int, ON_RECEIVE_FUNC]` +### ***var*** `active_channel = None` +- **类型**: `Optional['Channel']` + +- **说明**: 子进程可用的主动和被动通道 + +### ***var*** `passive_channel = None` + +- **类型**: `Optional['Channel']` + +### ***var*** `publish_channel = Channel(_id='publish_channel')` + +- **类型**: `Channel[tuple[str, dict[str, Any]]]` + +### ***var*** `channel_deliver_active_channel = NO_DEFAULT` + +- **类型**: `Channel[Channel[Any]]` + +- **说明**: 通道传递通道,主进程创建单例,子进程初始化时实例化 + +### ***var*** `channel_deliver_passive_channel = NO_DEFAULT` + +- **类型**: `Channel[tuple[str, dict[str, Any]]]` diff --git a/docs/dev/api/comm/event.md b/docs/dev/api/comm/event.md index a2a15f55..dd4c729d 100644 --- a/docs/dev/api/comm/event.md +++ b/docs/dev/api/comm/event.md @@ -1,20 +1,12 @@ --- title: liteyuki.comm.event -order: 1 -icon: laptop-code -category: API --- +### **class** `Event` +### *method* `__init__(self, name: str, data: dict[str, Any])` -### ***class*** `Event` - -事件类 - -###   ***def*** `__init__(self, name: str, data: dict[str, Any]) -> None` - - 
-源代码 + 源代码 ```python def __init__(self, name: str, data: dict[str, Any]): diff --git a/docs/dev/api/comm/storage.md b/docs/dev/api/comm/storage.md index cd19dbb7..4abe59de 100644 --- a/docs/dev/api/comm/storage.md +++ b/docs/dev/api/comm/storage.md @@ -1,46 +1,12 @@ --- title: liteyuki.comm.storage -order: 1 -icon: laptop-code -category: API --- - -### ***def*** `run_subscriber_receive_funcs(channel_: str, data: Any) -> None` - -运行订阅者接收函数 - -Args: - - channel_: 频道 - - data: 数据 - -
-源代码 - -```python -@staticmethod -def run_subscriber_receive_funcs(channel_: str, data: Any): - """ - 运行订阅者接收函数 - Args: - channel_: 频道 - data: 数据 - """ - if IS_MAIN_PROCESS: - if channel_ in _on_main_subscriber_receive_funcs and _on_main_subscriber_receive_funcs[channel_]: - run_coroutine(*[func(data) for func in _on_main_subscriber_receive_funcs[channel_]]) - elif channel_ in _on_sub_subscriber_receive_funcs and _on_sub_subscriber_receive_funcs[channel_]: - run_coroutine(*[func(data) for func in _on_sub_subscriber_receive_funcs[channel_]]) -``` -
- -### ***def*** `on_get(data: tuple[str, dict[str, Any]]) -> None` - +### `@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get')` +### *func* `on_get()`
-源代码 + 源代码 ```python @shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get') @@ -52,12 +18,12 @@ def on_get(data: tuple[str, dict[str, Any]]): ```
-### ***def*** `on_set(data: tuple[str, dict[str, Any]]) -> None` - +### `@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'set')` +### *func* `on_set()`
-源代码 + 源代码 ```python @shared_memory.passive_chan.on_receive(lambda d: d[0] == 'set') @@ -68,12 +34,12 @@ def on_set(data: tuple[str, dict[str, Any]]): ```
-### ***def*** `on_delete(data: tuple[str, dict[str, Any]]) -> None` - +### `@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'delete')` +### *func* `on_delete()`
-源代码 + 源代码 ```python @shared_memory.passive_chan.on_receive(lambda d: d[0] == 'delete') @@ -83,12 +49,12 @@ def on_delete(data: tuple[str, dict[str, Any]]): ```
-### ***def*** `on_get_all(data: tuple[str, dict[str, Any]]) -> None` - +### `@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get_all')` +### *func* `on_get_all()`
-源代码 + 源代码 ```python @shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get_all') @@ -98,12 +64,12 @@ def on_get_all(data: tuple[str, dict[str, Any]]): ```
-### ***def*** `on_publish(data: tuple[str, Any]) -> None` - +### `@channel.publish_channel.on_receive()` +### *func* `on_publish()`
-源代码 + 源代码 ```python @channel.publish_channel.on_receive() @@ -113,59 +79,12 @@ def on_publish(data: tuple[str, Any]): ```
-### ***def*** `decorator(func: ON_RECEIVE_FUNC) -> ON_RECEIVE_FUNC` - +### **class** `Subscriber` +### *method* `__init__(self)`
-源代码 - -```python -def decorator(func: ON_RECEIVE_FUNC) -> ON_RECEIVE_FUNC: - - async def wrapper(data: Any): - if is_coroutine_callable(func): - await func(data) - else: - func(data) - if IS_MAIN_PROCESS: - if channel_ not in _on_main_subscriber_receive_funcs: - _on_main_subscriber_receive_funcs[channel_] = [] - _on_main_subscriber_receive_funcs[channel_].append(wrapper) - else: - if channel_ not in _on_sub_subscriber_receive_funcs: - _on_sub_subscriber_receive_funcs[channel_] = [] - _on_sub_subscriber_receive_funcs[channel_].append(wrapper) - return wrapper -``` -
- -### ***async def*** `wrapper(data: Any) -> None` - - - -
-源代码 - -```python -async def wrapper(data: Any): - if is_coroutine_callable(func): - await func(data) - else: - func(data) -``` -
- -### ***class*** `Subscriber` - - - -###   ***def*** `__init__(self) -> None` - -  - -
-源代码 + 源代码 ```python def __init__(self): @@ -173,12 +92,11 @@ def __init__(self): ```
-###   ***def*** `receive(self) -> Any` +### *method* `receive(self) -> Any` - 
-源代码 + 源代码 ```python def receive(self) -> Any: @@ -186,12 +104,11 @@ def receive(self) -> Any: ```
-###   ***def*** `unsubscribe(self) -> None` +### *method* `unsubscribe(self) -> None` - 
-源代码 + 源代码 ```python def unsubscribe(self) -> None: @@ -199,16 +116,12 @@ def unsubscribe(self) -> None: ```
-### ***class*** `KeyValueStore` +### **class** `KeyValueStore` +### *method* `__init__(self)` - -###   ***def*** `__init__(self) -> None` - -  -
-源代码 + 源代码 ```python def __init__(self): @@ -221,18 +134,19 @@ def __init__(self): ```
-###   ***def*** `set(self, key: str, value: Any) -> None` +### *method* `set(self, key: str, value: Any) -> None` - 设置键值对 -Args: - key: 键 +**说明**: 设置键值对 + +**参数**: +> - key: 键 +> - value: 值 - value: 值
-源代码 + 源代码 ```python def set(self, key: str, value: Any) -> None: @@ -252,24 +166,21 @@ def set(self, key: str, value: Any) -> None: ```
-###   ***def*** `get(self, key: str, default: Optional[Any]) -> Optional[Any]` - - 获取键值对 - -Args: - - key: 键 - - default: 默认值 +### *method* `get(self, key: str, default: Optional[Any] = None) -> Optional[Any]` -Returns: +**说明**: 获取键值对 + +**参数**: +> - key: 键 +> - default: 默认值 + +**返回**: Any: 值 - Any: 值
-源代码 + 源代码 ```python def get(self, key: str, default: Optional[Any]=None) -> Optional[Any]: @@ -293,22 +204,19 @@ def get(self, key: str, default: Optional[Any]=None) -> Optional[Any]: ```
-###   ***def*** `delete(self, key: str, ignore_key_error: bool) -> None` - - 删除键值对 - -Args: - - key: 键 - - ignore_key_error: 是否忽略键不存在的错误 +### *method* `delete(self, key: str, ignore_key_error: bool = True) -> None` -Returns: +**说明**: 删除键值对 + +**参数**: +> - key: 键 +> - ignore_key_error: 是否忽略键不存在的错误 +
-源代码 + 源代码 ```python def delete(self, key: str, ignore_key_error: bool=True) -> None: @@ -335,16 +243,17 @@ def delete(self, key: str, ignore_key_error: bool=True) -> None: ```
-###   ***def*** `get_all(self) -> dict[str, Any]` +### *method* `get_all(self) -> dict[str, Any]` - 获取所有键值对 -Returns: - dict[str, Any]: 键值对 +**说明**: 获取所有键值对 + +**返回**: dict[str, Any]: 键值对 +
-源代码 + 源代码 ```python def get_all(self) -> dict[str, Any]: @@ -362,22 +271,19 @@ def get_all(self) -> dict[str, Any]: ```
-###   ***def*** `publish(self, channel_: str, data: Any) -> None` - - 发布消息 - -Args: - - channel_: 频道 - - data: 数据 +### *method* `publish(self, channel_: str, data: Any) -> None` -Returns: +**说明**: 发布消息 + +**参数**: +> - channel_: 频道 +> - data: 数据 +
-源代码 + 源代码 ```python def publish(self, channel_: str, data: Any) -> None: @@ -393,22 +299,20 @@ def publish(self, channel_: str, data: Any) -> None: ```
-###   ***def*** `on_subscriber_receive(self, channel_: str) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]` - - 订阅者接收消息时的回调 - -Args: - - channel_: 频道 +### *method* `on_subscriber_receive(self, channel_: str) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]` -Returns: +**说明**: 订阅者接收消息时的回调 + +**参数**: +> - channel_: 频道 + +**返回**: 装饰器 - 装饰器
-源代码 + 源代码 ```python def on_subscriber_receive(self, channel_: str) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]: @@ -447,19 +351,20 @@ def on_subscriber_receive(self, channel_: str) -> Callable[[ON_RECEIVE_FUNC], ON ```
-###   ***@staticmethod*** -###   ***def*** `run_subscriber_receive_funcs(channel_: str, data: Any) -> None` +### `@staticmethod` +### *method* `run_subscriber_receive_funcs(channel_: str, data: Any)` - 运行订阅者接收函数 -Args: - channel_: 频道 +**说明**: 运行订阅者接收函数 + +**参数**: +> - channel_: 频道 +> - data: 数据 - data: 数据
-源代码 + 源代码 ```python @staticmethod @@ -472,23 +377,48 @@ def run_subscriber_receive_funcs(channel_: str, data: Any): """ if IS_MAIN_PROCESS: if channel_ in _on_main_subscriber_receive_funcs and _on_main_subscriber_receive_funcs[channel_]: - run_coroutine(*[func(data) for func in _on_main_subscriber_receive_funcs[channel_]]) + run_coroutine_in_thread(*[func(data) for func in _on_main_subscriber_receive_funcs[channel_]]) elif channel_ in _on_sub_subscriber_receive_funcs and _on_sub_subscriber_receive_funcs[channel_]: - run_coroutine(*[func(data) for func in _on_sub_subscriber_receive_funcs[channel_]]) + run_coroutine_in_thread(*[func(data) for func in _on_sub_subscriber_receive_funcs[channel_]]) ```
-### ***class*** `GlobalKeyValueStore` +### *method* `_start_receive_loop(self)` -###   ***@classmethod*** -###   ***def*** `get_instance(cls: Any) -> None` +**说明**: 启动发布订阅接收器循环,在主进程中运行,若有子进程订阅则推送给子进程 - 
-源代码 + 源代码 + +```python +def _start_receive_loop(self): + """ + 启动发布订阅接收器循环,在主进程中运行,若有子进程订阅则推送给子进程 + """ + if IS_MAIN_PROCESS: + while True: + data = self.active_chan.receive() + if data[0] == 'publish': + self.run_subscriber_receive_funcs(data[1]['channel'], data[1]['data']) + self.publish_channel.send(data) + else: + while True: + data = self.publish_channel.receive() + if data[0] == 'publish': + self.run_subscriber_receive_funcs(data[1]['channel'], data[1]['data']) +``` +
+ +### **class** `GlobalKeyValueStore` +### `@classmethod` +### *method* `get_instance(cls)` + + +
+ 源代码 ```python @classmethod @@ -501,63 +431,19 @@ def get_instance(cls): ```
-###   ***attr*** `_instance: None` +### ***var*** `_on_main_subscriber_receive_funcs = {}` -###   ***attr*** `_lock: threading.Lock()` +- **类型**: `dict[str, list[ASYNC_ON_RECEIVE_FUNC]]` -### ***var*** `key = data[1]['key']` +- **说明**: 主进程订阅者接收函数 +### ***var*** `_on_sub_subscriber_receive_funcs = {}` +- **类型**: `dict[str, list[ASYNC_ON_RECEIVE_FUNC]]` -### ***var*** `default = data[1]['default']` - - - -### ***var*** `recv_chan = data[1]['recv_chan']` - - - -### ***var*** `key = data[1]['key']` - - - -### ***var*** `value = data[1]['value']` - - - -### ***var*** `key = data[1]['key']` - - - -### ***var*** `recv_chan = data[1]['recv_chan']` - - - -### ***var*** `lock = _get_lock(key)` - - - -### ***var*** `lock = _get_lock(key)` - - - -### ***var*** `recv_chan = Channel[Optional[Any]]('recv_chan')` - - - -### ***var*** `lock = _get_lock(key)` - - - -### ***var*** `recv_chan = Channel[dict[str, Any]]('recv_chan')` - - - -### ***var*** `data = self.active_chan.receive()` - - - -### ***var*** `data = self.publish_channel.receive()` +- **说明**: 子进程订阅者接收函数 +### ***var*** `shared_memory = GlobalKeyValueStore.get_instance()` +- **类型**: `KeyValueStore` diff --git a/docs/dev/api/config.md b/docs/dev/api/config.md index 24afd6c9..e5c5a1c9 100644 --- a/docs/dev/api/config.md +++ b/docs/dev/api/config.md @@ -1,30 +1,22 @@ --- title: liteyuki.config -order: 1 -icon: laptop-code -category: API --- - -### ***def*** `flat_config(config: dict[str, Any]) -> dict[str, Any]` - -扁平化配置文件 +### *func* `flat_config() -> dict[str, Any]` +**说明**: 扁平化配置文件 + {a:{b:{c:1}}} -> {"a.b.c": 1} -Args: +**参数**: +> - config: 配置项目 - config: 配置项目 +**返回**: 扁平化后的配置文件,但也包含原有的键值对 - -Returns: - - 扁平化后的配置文件,但也包含原有的键值对 -
-源代码 + 源代码 ```python def flat_config(config: dict[str, Any]) -> dict[str, Any]: @@ -47,69 +39,80 @@ def flat_config(config: dict[str, Any]) -> dict[str, Any]: ```
-### ***def*** `load_from_yaml(file: str) -> dict[str, Any]` +### *func* `load_from_yaml() -> dict[str, Any]` + + + +**说明**: Load config from yaml file -Load config from yaml file
-源代码 + 源代码 ```python -def load_from_yaml(file: str) -> dict[str, Any]: +def load_from_yaml(file_: str) -> dict[str, Any]: """ Load config from yaml file """ - logger.debug(f'Loading YAML config from {file}') - config = yaml.safe_load(open(file, 'r', encoding='utf-8')) + logger.debug(f'Loading YAML config from {file_}') + config = yaml.safe_load(open(file_, 'r', encoding='utf-8')) return flat_config(config if config is not None else {}) ```
-### ***def*** `load_from_json(file: str) -> dict[str, Any]` +### *func* `load_from_json() -> dict[str, Any]` + + + +**说明**: Load config from json file -Load config from json file
-源代码 + 源代码 ```python -def load_from_json(file: str) -> dict[str, Any]: +def load_from_json(file_: str) -> dict[str, Any]: """ Load config from json file """ - logger.debug(f'Loading JSON config from {file}') - config = json.load(open(file, 'r', encoding='utf-8')) + logger.debug(f'Loading JSON config from {file_}') + config = json.load(open(file_, 'r', encoding='utf-8')) return flat_config(config if config is not None else {}) ```
-### ***def*** `load_from_toml(file: str) -> dict[str, Any]` +### *func* `load_from_toml() -> dict[str, Any]` + + + +**说明**: Load config from toml file -Load config from toml file
-源代码 + 源代码 ```python -def load_from_toml(file: str) -> dict[str, Any]: +def load_from_toml(file_: str) -> dict[str, Any]: """ Load config from toml file """ - logger.debug(f'Loading TOML config from {file}') - config = toml.load(open(file, 'r', encoding='utf-8')) + logger.debug(f'Loading TOML config from {file_}') + config = toml.load(open(file_, 'r', encoding='utf-8')) return flat_config(config if config is not None else {}) ```
-### ***def*** `load_from_files() -> dict[str, Any]` +### *func* `load_from_files(*, no_warning: bool = False) -> dict[str, Any]` -从指定文件加载配置项,会自动识别文件格式 + +**说明**: 从指定文件加载配置项,会自动识别文件格式 默认执行扁平化选项 +
-源代码 + 源代码 ```python def load_from_files(*files: str, no_warning: bool=False) -> dict[str, Any]: @@ -134,16 +137,17 @@ def load_from_files(*files: str, no_warning: bool=False) -> dict[str, Any]: ```
-### ***def*** `load_configs_from_dirs() -> dict[str, Any]` +### *func* `load_configs_from_dirs(*, no_waring: bool = False) -> dict[str, Any]` -从目录下加载配置文件,不递归 + +**说明**: 从目录下加载配置文件,不递归 按照读取文件的优先级反向覆盖 - 默认执行扁平化选项 +
-源代码 + 源代码 ```python def load_configs_from_dirs(*directories: str, no_waring: bool=False) -> dict[str, Any]: @@ -165,16 +169,17 @@ def load_configs_from_dirs(*directories: str, no_waring: bool=False) -> dict[str ```
-### ***def*** `load_config_in_default(no_waring: bool) -> dict[str, Any]` +### *func* `load_config_in_default(no_waring: bool = False) -> dict[str, Any]` -从一个标准的轻雪项目加载配置文件 + +**说明**: 从一个标准的轻雪项目加载配置文件 项目目录下的config.*和config目录下的所有配置文件 - 项目目录下的配置文件优先 +
-源代码 + 源代码 ```python def load_config_in_default(no_waring: bool=False) -> dict[str, Any]: @@ -189,43 +194,3 @@ def load_config_in_default(no_waring: bool=False) -> dict[str, Any]: ```
-### ***class*** `SatoriNodeConfig(BaseModel)` - - - -### ***class*** `SatoriConfig(BaseModel)` - - - -### ***class*** `BasicConfig(BaseModel)` - - - -### ***var*** `new_config = copy.deepcopy(config)` - - - -### ***var*** `config = yaml.safe_load(open(file, 'r', encoding='utf-8'))` - - - -### ***var*** `config = json.load(open(file, 'r', encoding='utf-8'))` - - - -### ***var*** `config = toml.load(open(file, 'r', encoding='utf-8'))` - - - -### ***var*** `config = {}` - - - -### ***var*** `config = {}` - - - -### ***var*** `config = load_configs_from_dirs('config', no_waring=no_waring)` - - - diff --git a/docs/dev/api/core/README.md b/docs/dev/api/core/README.md index 26c3500b..50a83056 100644 --- a/docs/dev/api/core/README.md +++ b/docs/dev/api/core/README.md @@ -1,7 +1,3 @@ --- title: liteyuki.core -index: true -icon: laptop-code -category: API --- - diff --git a/docs/dev/api/core/manager.md b/docs/dev/api/core/manager.md index 52397375..ad552cb9 100644 --- a/docs/dev/api/core/manager.md +++ b/docs/dev/api/core/manager.md @@ -1,20 +1,12 @@ --- title: liteyuki.core.manager -order: 1 -icon: laptop-code -category: API --- +### **class** `ChannelDeliver` +### *method* `__init__(self, active: Channel[Any], passive: Channel[Any], channel_deliver_active: Channel[Channel[Any]], channel_deliver_passive: Channel[tuple[str, dict]], publish: Channel[tuple[str, Any]])` -### ***class*** `ChannelDeliver` - - - -###   ***def*** `__init__(self, active: Channel[Any], passive: Channel[Any], channel_deliver_active: Channel[Channel[Any]], channel_deliver_passive: Channel[tuple[str, dict]], publish: Channel[tuple[str, Any]]) -> None` - - 
-源代码 + 源代码 ```python def __init__(self, active: Channel[Any], passive: Channel[Any], channel_deliver_active: Channel[Channel[Any]], channel_deliver_passive: Channel[tuple[str, dict]], publish: Channel[tuple[str, Any]]): @@ -26,16 +18,12 @@ def __init__(self, active: Channel[Any], passive: Channel[Any], channel_deliver_ ```
-### ***class*** `ProcessManager` +### **class** `ProcessManager` +### *method* `__init__(self, lifespan: Lifespan)` -进程管理器 - -###   ***def*** `__init__(self, lifespan: 'Lifespan') -> None` - - 
-源代码 + 源代码 ```python def __init__(self, lifespan: 'Lifespan'): @@ -45,18 +33,18 @@ def __init__(self, lifespan: 'Lifespan'): ```
-###   ***def*** `start(self, name: str) -> None` +### *method* `start(self, name: str)` - 开启后自动监控进程,并添加到进程字典中 -Args: - name: +**说明**: 开启后自动监控进程,并添加到进程字典中 + +**参数**: +> - name: -Returns:
-源代码 + 源代码 ```python def start(self, name: str): @@ -95,12 +83,15 @@ def start(self, name: str): ```
-###   ***def*** `start_all(self) -> None` +### *method* `start_all(self)` + + + +**说明**: 启动所有进程 - 启动所有进程
-源代码 + 源代码 ```python def start_all(self): @@ -112,22 +103,21 @@ def start_all(self): ```
-###   ***def*** `add_target(self, name: str, target: TARGET_FUNC, args: tuple, kwargs: Any) -> None` +### *method* `add_target(self, name: str, target: TARGET_FUNC, args: tuple = (), kwargs = None)` - 添加进程 -Args: - name: 进程名,用于获取和唯一标识 +**说明**: 添加进程 - target: 进程函数 +**参数**: +> - name: 进程名,用于获取和唯一标识 +> - target: 进程函数 +> - args: 进程函数参数 +> - kwargs: 进程函数关键字参数,通常会默认传入chan_active和chan_passive - args: 进程函数参数 - - kwargs: 进程函数关键字参数,通常会默认传入chan_active和chan_passive
-源代码 + 源代码 ```python def add_target(self, name: str, target: TARGET_FUNC, args: tuple=(), kwargs=None): @@ -149,12 +139,11 @@ def add_target(self, name: str, target: TARGET_FUNC, args: tuple=(), kwargs=None ```
-###   ***def*** `join_all(self) -> None` +### *method* `join_all(self)` - 
-源代码 + 源代码 ```python def join_all(self): @@ -163,20 +152,18 @@ def join_all(self): ```
-###   ***def*** `terminate(self, name: str) -> None` - - 终止进程并从进程字典中删除 - -Args: - - name: +### *method* `terminate(self, name: str)` -Returns: +**说明**: 终止进程并从进程字典中删除 + +**参数**: +> - name: +
-源代码 + 源代码 ```python def terminate(self, name: str): @@ -200,12 +187,11 @@ def terminate(self, name: str): ```
-###   ***def*** `terminate_all(self) -> None` +### *method* `terminate_all(self)` - 
-源代码 + 源代码 ```python def terminate_all(self): @@ -214,20 +200,18 @@ def terminate_all(self): ```
-###   ***def*** `is_process_alive(self, name: str) -> bool` - - 检查进程是否存活 - -Args: - - name: +### *method* `is_process_alive(self, name: str) -> bool` -Returns: +**说明**: 检查进程是否存活 + +**参数**: +> - name: +
-源代码 + 源代码 ```python def is_process_alive(self, name: str) -> bool: @@ -245,31 +229,7 @@ def is_process_alive(self, name: str) -> bool: ```
-### ***var*** `TIMEOUT = 10` - - - -### ***var*** `chan_active = get_channel(f'{name}-active')` - - - -### ***var*** `channel_deliver = ChannelDeliver(active=chan_active, passive=chan_passive, channel_deliver_active=channel_deliver_active_channel, channel_deliver_passive=channel_deliver_passive_channel, publish=publish_channel)` - - - -### ***var*** `process = self.processes[name]` - - - -### ***var*** `process = Process(target=self.targets[name][0], args=self.targets[name][1], kwargs=self.targets[name][2], daemon=True)` - - - -### ***var*** `data = chan_active.receive()` - - - -### ***var*** `kwargs = {}` - +### ***var*** `TARGET_FUNC = Callable[..., Any]` +- **类型**: `TypeAlias` diff --git a/docs/dev/api/dev/README.md b/docs/dev/api/dev/README.md index 6d883442..45d41428 100644 --- a/docs/dev/api/dev/README.md +++ b/docs/dev/api/dev/README.md @@ -1,7 +1,3 @@ --- title: liteyuki.dev -index: true -icon: laptop-code -category: API --- - diff --git a/docs/dev/api/dev/observer.md b/docs/dev/api/dev/observer.md index 2005bbf2..689ae2b0 100644 --- a/docs/dev/api/dev/observer.md +++ b/docs/dev/api/dev/observer.md @@ -1,16 +1,15 @@ --- title: liteyuki.dev.observer -order: 1 -icon: laptop-code -category: API --- +### *func* `debounce()` -### ***def*** `debounce(wait: Any) -> None` -防抖函数 + +**说明**: 防抖函数 +
-源代码 + 源代码 ```python def debounce(wait): @@ -32,24 +31,22 @@ def debounce(wait): ```
-### ***def*** `on_file_system_event(directories: tuple[str], recursive: bool, event_filter: FILTER_FUNC) -> Callable[[CALLBACK_FUNC], CALLBACK_FUNC]` +### *func* `on_file_system_event(directories: tuple[str] = True, recursive: bool = None) -> Callable[[CALLBACK_FUNC], CALLBACK_FUNC]` -注册文件系统变化监听器 -Args: - directories: 监听目录们 +**说明**: 注册文件系统变化监听器 - recursive: 是否递归监听子目录 +**参数**: +> - directories: 监听目录们 +> - recursive: 是否递归监听子目录 +> - event_filter: 事件过滤器, 返回True则执行回调函数 - event_filter: 事件过滤器, 返回True则执行回调函数 +**返回**: 装饰器,装饰一个函数在接收到数据后执行 -Returns: - - 装饰器,装饰一个函数在接收到数据后执行
-源代码 + 源代码 ```python def on_file_system_event(directories: tuple[str], recursive: bool=True, event_filter: FILTER_FUNC=None) -> Callable[[CALLBACK_FUNC], CALLBACK_FUNC]: @@ -78,91 +75,13 @@ def on_file_system_event(directories: tuple[str], recursive: bool=True, event_fi ```
-### ***def*** `decorator(func: Any) -> None` - +### **class** `CodeModifiedHandler(FileSystemEventHandler)` +### `@debounce(1)` +### *method* `on_modified(self, event)`
-源代码 - -```python -def decorator(func): - - def wrapper(*args, **kwargs): - nonlocal last_call_time - current_time = time.time() - if current_time - last_call_time > wait: - last_call_time = current_time - return func(*args, **kwargs) - last_call_time = None - return wrapper -``` -
- -### ***def*** `decorator(func: CALLBACK_FUNC) -> CALLBACK_FUNC` - - - -
-源代码 - -```python -def decorator(func: CALLBACK_FUNC) -> CALLBACK_FUNC: - - def wrapper(event: FileSystemEvent): - if event_filter is not None and (not event_filter(event)): - return - func(event) - code_modified_handler = CodeModifiedHandler() - code_modified_handler.on_modified = wrapper - for directory in directories: - observer.schedule(code_modified_handler, directory, recursive=recursive) - return func -``` -
- -### ***def*** `wrapper() -> None` - - - -
-源代码 - -```python -def wrapper(*args, **kwargs): - nonlocal last_call_time - current_time = time.time() - if current_time - last_call_time > wait: - last_call_time = current_time - return func(*args, **kwargs) -``` -
- -### ***def*** `wrapper(event: FileSystemEvent) -> None` - - - -
-源代码 - -```python -def wrapper(event: FileSystemEvent): - if event_filter is not None and (not event_filter(event)): - return - func(event) -``` -
- -### ***class*** `CodeModifiedHandler(FileSystemEventHandler)` - -Handler for code file changes - -###   ***def*** `on_modified(self, event: Any) -> None` - -  - -
-源代码 + 源代码 ```python @debounce(1) @@ -171,12 +90,11 @@ def on_modified(self, event): ```
-###   ***def*** `on_created(self, event: Any) -> None` +### *method* `on_created(self, event)` - 
-源代码 + 源代码 ```python def on_created(self, event): @@ -184,12 +102,11 @@ def on_created(self, event): ```
-###   ***def*** `on_deleted(self, event: Any) -> None` +### *method* `on_deleted(self, event)` - 
-源代码 + 源代码 ```python def on_deleted(self, event): @@ -197,12 +114,11 @@ def on_deleted(self, event): ```
-###   ***def*** `on_moved(self, event: Any) -> None` +### *method* `on_moved(self, event)` - 
-源代码 + 源代码 ```python def on_moved(self, event): @@ -210,12 +126,11 @@ def on_moved(self, event): ```
-###   ***def*** `on_any_event(self, event: Any) -> None` +### *method* `on_any_event(self, event)` - 
-源代码 + 源代码 ```python def on_any_event(self, event): @@ -223,27 +138,15 @@ def on_any_event(self, event): ```
-### ***var*** `liteyuki_bot = get_bot()` +### ***var*** `CALLBACK_FUNC = Callable[[FileSystemEvent], None]` +- **类型**: `TypeAlias` +- **说明**: 位置1为FileSystemEvent -### ***var*** `observer = Observer()` - - - -### ***var*** `last_call_time = None` - - - -### ***var*** `code_modified_handler = CodeModifiedHandler()` - - - -### ***var*** `current_time = time.time()` - - - -### ***var*** `last_call_time = current_time` +### ***var*** `FILTER_FUNC = Callable[[FileSystemEvent], bool]` +- **类型**: `TypeAlias` +- **说明**: 位置1为FileSystemEvent diff --git a/docs/dev/api/dev/plugin.md b/docs/dev/api/dev/plugin.md index caafa5ea..c0462584 100644 --- a/docs/dev/api/dev/plugin.md +++ b/docs/dev/api/dev/plugin.md @@ -1,20 +1,18 @@ --- title: liteyuki.dev.plugin -order: 1 -icon: laptop-code -category: API --- +### *func* `run_plugins()` -### ***def*** `run_plugins() -> None` -运行插件,无需手动初始化bot -Args: +**说明**: 运行插件,无需手动初始化bot + +**参数**: +> - module_path: 插件路径,参考`liteyuki.load_plugin`的函数签名 - module_path: 插件路径,参考`liteyuki.load_plugin`的函数签名
-源代码 + 源代码 ```python def run_plugins(*module_path: str | Path): @@ -32,15 +30,3 @@ def run_plugins(*module_path: str | Path): ```
-### ***var*** `cfg = load_config_in_default()` - - - -### ***var*** `plugins = cfg.get('liteyuki.plugins', [])` - - - -### ***var*** `bot = LiteyukiBot(**cfg)` - - - diff --git a/docs/dev/api/exception.md b/docs/dev/api/exception.md index 469c00d6..4134eec4 100644 --- a/docs/dev/api/exception.md +++ b/docs/dev/api/exception.md @@ -1,11 +1,4 @@ --- title: liteyuki.exception -order: 1 -icon: laptop-code -category: API --- - -### ***class*** `LiteyukiException(BaseException)` - -Liteyuki的异常基类。 - +### **class** `LiteyukiException(BaseException)` diff --git a/docs/dev/api/log.md b/docs/dev/api/log.md index af382275..32f26597 100644 --- a/docs/dev/api/log.md +++ b/docs/dev/api/log.md @@ -1,16 +1,11 @@ --- title: liteyuki.log -order: 1 -icon: laptop-code -category: API --- - -### ***def*** `get_format(level: str) -> str` - +### *func* `get_format() -> str`
-源代码 + 源代码 ```python def get_format(level: str) -> str: @@ -21,14 +16,15 @@ def get_format(level: str) -> str: ```
-### ***def*** `init_log(config: dict) -> None` +### *func* `init_log()` -在语言加载完成后执行 -Returns: + +**说明**: 在语言加载完成后执行 +
-源代码 + 源代码 ```python def init_log(config: dict): @@ -48,11 +44,11 @@ def init_log(config: dict): ```
-### ***var*** `logger = loguru.logger` +### ***var*** `debug_format = '{time:YYYY-MM-DD HH:mm:ss} [{level.icon}] <{name}.{module}.{function}:{line}> {message}'` +- **类型**: `str` +### ***var*** `default_format = '{time:MM-DD HH:mm:ss} [{level.icon}] <{name}> {message}'` -### ***var*** `show_icon = config.get('log_icon', True)` - - +- **类型**: `str` diff --git a/docs/dev/api/message/README.md b/docs/dev/api/message/README.md index 0d664851..57e6955a 100644 --- a/docs/dev/api/message/README.md +++ b/docs/dev/api/message/README.md @@ -1,7 +1,3 @@ --- title: liteyuki.message -index: true -icon: laptop-code -category: API --- - diff --git a/docs/dev/api/message/event.md b/docs/dev/api/message/event.md index 740ff8bd..f1dbb7fa 100644 --- a/docs/dev/api/message/event.md +++ b/docs/dev/api/message/event.md @@ -1,47 +1,19 @@ --- title: liteyuki.message.event -order: 1 -icon: laptop-code -category: API --- - -### ***class*** `MessageEvent` +### **class** `MessageEvent` +### *method* `__init__(self, bot_id: str, message: list[dict[str, Any]] | str, message_type: str, raw_message: str, session_id: str, user_id: str, session_type: str, receive_channel: str, data: Optional[dict[str, Any]] = None)` -###   ***def*** `__init__(self, bot_id: str, message: list[dict[str, Any]] | str, message_type: str, raw_message: str, session_id: str, session_type: str, receive_channel: str, data: Optional[dict[str, Any]]) -> None` +**说明**: 轻雪抽象消息事件 - 轻雪抽象消息事件 - -Args: - - - - bot_id: 机器人ID - - message: 消息,消息段数组[{type: str, data: dict[str, Any]}] - - raw_message: 原始消息(通常为纯文本的格式) - - message_type: 消息类型(private, group, other) - - - - session_id: 会话ID(私聊通常为用户ID,群聊通常为群ID) - - session_type: 会话类型(private, group) - - receive_channel: 接收频道(用于回复消息) - - - - data: 附加数据
-源代码 + 源代码 ```python -def __init__(self, bot_id: str, message: list[dict[str, Any]] | str, message_type: str, raw_message: str, session_id: str, session_type: str, receive_channel: str, data: Optional[dict[str, Any]]=None): +def __init__(self, bot_id: str, message: list[dict[str, Any]] | str, message_type: str, raw_message: str, session_id: str, user_id: str, session_type: str, receive_channel: str, data: Optional[dict[str, Any]]=None): """ 轻雪抽象消息事件 Args: @@ -66,22 +38,23 @@ def __init__(self, bot_id: str, message: list[dict[str, Any]] | str, message_typ self.raw_message = raw_message self.session_id = session_id self.session_type = session_type + self.user_id = user_id self.receive_channel = receive_channel ```
-###   ***def*** `reply(self, message: str | dict[str, Any]) -> None` +### *method* `reply(self, message: str | dict[str, Any])` - 回复消息 -Args: - message: +**说明**: 回复消息 + +**参数**: +> - message: -Returns:
-源代码 + 源代码 ```python def reply(self, message: str | dict[str, Any]): @@ -96,11 +69,3 @@ def reply(self, message: str | dict[str, Any]): ```
-### ***var*** `reply_event = MessageEvent(message_type=self.session_type, message=message, raw_message='', data={'message': message}, bot_id=self.bot_id, session_id=self.session_id, session_type=self.session_type, receive_channel='_')` - - - -### ***var*** `data = {}` - - - diff --git a/docs/dev/api/message/matcher.md b/docs/dev/api/message/matcher.md index 4a6341b6..9e480098 100644 --- a/docs/dev/api/message/matcher.md +++ b/docs/dev/api/message/matcher.md @@ -1,28 +1,21 @@ --- title: liteyuki.message.matcher -order: 1 -icon: laptop-code -category: API --- - -### ***class*** `Matcher` +### **class** `Matcher` +### *method* `__init__(self, rule: Rule, priority: int, block: bool)` -###   ***def*** `__init__(self, rule: Rule, priority: int, block: bool) -> None` +**说明**: 匹配器 - 匹配器 +**参数**: +> - rule: 规则 +> - priority: 优先级 >= 0 +> - block: 是否阻断后续优先级更低的匹配器 -Args: - - rule: 规则 - - priority: 优先级 >= 0 - - block: 是否阻断后续优先级更低的匹配器
-源代码 + 源代码 ```python def __init__(self, rule: Rule, priority: int, block: bool): @@ -40,32 +33,65 @@ def __init__(self, rule: Rule, priority: int, block: bool): ```
-###   ***def*** `handle(self, handler: EventHandler) -> EventHandler` +### *method* `handle(self) -> Callable[[EventHandler], EventHandler]` - 添加处理函数,装饰器 -Args: - handler: +**说明**: 添加处理函数,装饰器 -Returns: +**返回**: 装饰器 handler - EventHandler
-源代码 + 源代码 ```python -def handle(self, handler: EventHandler) -> EventHandler: +def handle(self) -> Callable[[EventHandler], EventHandler]: """ 添加处理函数,装饰器 - Args: - handler: Returns: - EventHandler + 装饰器 handler """ - self.handlers.append(handler) - return handler + + def decorator(handler: EventHandler) -> EventHandler: + self.handlers.append(handler) + return handler + return decorator ```
+### *async method* `run(self, event: MessageEvent) -> None` + + + +**说明**: 运行处理函数 + +**参数**: +> - event: + + +
+ 源代码 + +```python +async def run(self, event: MessageEvent) -> None: + """ + 运行处理函数 + Args: + event: + Returns: + """ + if not await self.rule(event): + return + for handler in self.handlers: + try: + await handler(event) + except Exception: + traceback.print_exc() +``` +
+ +### ***var*** `EventHandler = Callable[[MessageEvent], Coroutine[None, None, Any]]` + +- **类型**: `TypeAlias` + diff --git a/docs/dev/api/message/on.md b/docs/dev/api/message/on.md index 4f3c7230..0b793857 100644 --- a/docs/dev/api/message/on.md +++ b/docs/dev/api/message/on.md @@ -1,19 +1,14 @@ --- title: liteyuki.message.on -order: 1 -icon: laptop-code -category: API --- - -### ***def*** `on_message(rule: Rule, priority: int, block: bool) -> Matcher` - +### *func* `on_message(rule: Rule = empty_rule, priority: int = 0, block: bool = False) -> Matcher`
-源代码 + 源代码 ```python -def on_message(rule: Rule=Rule(), priority: int=0, block: bool=True) -> Matcher: +def on_message(rule: Rule=empty_rule, priority: int=0, block: bool=False) -> Matcher: matcher = Matcher(rule, priority, block) for i, m in enumerate(_matcher_list): if m.priority < matcher.priority: @@ -25,15 +20,27 @@ def on_message(rule: Rule=Rule(), priority: int=0, block: bool=True) -> Matcher: ```
-### ***var*** `current_priority = -1` +### *func* `on_keywords(keywords: list[str] = empty_rule, rule = 0, priority: int = False) -> Matcher` +
+ 源代码 -### ***var*** `matcher = Matcher(rule, priority, block)` +```python +def on_keywords(keywords: list[str], rule=empty_rule, priority: int=0, block: bool=False) -> Matcher: + @Rule + async def on_keywords_rule(event: MessageEvent): + return any((keyword in event.raw_message for keyword in keywords)) + return on_message(on_keywords_rule & rule, priority, block) +``` +
+### ***var*** `_matcher_list = []` -### ***var*** `current_priority = matcher.priority` +- **类型**: `list[Matcher]` +### ***var*** `_queue = Queue()` +- **类型**: `Queue` diff --git a/docs/dev/api/message/rule.md b/docs/dev/api/message/rule.md index a135f740..7d58cdfa 100644 --- a/docs/dev/api/message/rule.md +++ b/docs/dev/api/message/rule.md @@ -1,24 +1,98 @@ --- title: liteyuki.message.rule -order: 1 -icon: laptop-code -category: API --- +### `@Rule` +### *async func* `empty_rule() -> bool` -### ***class*** `Rule` - - - -###   ***def*** `__init__(self, handler: Optional[RuleHandler]) -> None` - - 
-源代码 + 源代码 ```python -def __init__(self, handler: Optional[RuleHandler]=None): +@Rule +async def empty_rule(event: MessageEvent) -> bool: + return True +``` +
+ +### `@Rule` +### *async func* `is_su_rule() -> bool` + + +
+ 源代码 + +```python +@Rule +async def is_su_rule(event: MessageEvent) -> bool: + return str(event.user_id) in _superusers +``` +
+ +### **class** `Rule` +### *method* `__init__(self, handler: RuleHandlerFunc)` + + +
+ 源代码 + +```python +def __init__(self, handler: RuleHandlerFunc): self.handler = handler ```
+### *method* `__or__(self, other: Rule) -> Rule` + + +
+ 源代码 + +```python +def __or__(self, other: 'Rule') -> 'Rule': + + async def combined_handler(event: MessageEvent) -> bool: + return await self.handler(event) or await other.handler(event) + return Rule(combined_handler) +``` +
+ +### *method* `__and__(self, other: Rule) -> Rule` + + +
+ 源代码 + +```python +def __and__(self, other: 'Rule') -> 'Rule': + + async def combined_handler(event: MessageEvent) -> bool: + return await self.handler(event) and await other.handler(event) + return Rule(combined_handler) +``` +
+ +### *async method* `__call__(self, event: MessageEvent) -> bool` + + +
+ 源代码 + +```python +async def __call__(self, event: MessageEvent) -> bool: + if self.handler is None: + return True + return await self.handler(event) +``` +
+ +### ***var*** `_superusers = get_config('liteyuki.superusers', [])` + +- **类型**: `list[str]` + +### ***var*** `RuleHandlerFunc = Callable[[MessageEvent], Coroutine[None, None, bool]]` + +- **类型**: `TypeAlias` + +- **说明**: 规则函数签名 + diff --git a/docs/dev/api/message/session.md b/docs/dev/api/message/session.md index f8fcad6f..28c3e5c9 100644 --- a/docs/dev/api/message/session.md +++ b/docs/dev/api/message/session.md @@ -1,7 +1,3 @@ --- title: liteyuki.message.session -order: 1 -icon: laptop-code -category: API --- - diff --git a/docs/dev/api/mkdoc.md b/docs/dev/api/mkdoc.md index 802aff3c..5ca668a6 100644 --- a/docs/dev/api/mkdoc.md +++ b/docs/dev/api/mkdoc.md @@ -1,22 +1,19 @@ --- title: liteyuki.mkdoc -order: 1 -icon: laptop-code -category: API --- +### *func* `get_relative_path() -> str` -### ***def*** `get_relative_path(base_path: str, target_path: str) -> str` -获取相对路径 -Args: +**说明**: 获取相对路径 - base_path: 基础路径 +**参数**: +> - base_path: 基础路径 +> - target_path: 目标路径 - target_path: 目标路径
-源代码 + 源代码 ```python def get_relative_path(base_path: str, target_path: str) -> str: @@ -30,16 +27,18 @@ def get_relative_path(base_path: str, target_path: str) -> str: ```
-### ***def*** `write_to_files(file_data: dict[str, str]) -> None` +### *func* `write_to_files()` -输出文件 -Args: - file_data: 文件数据 相对路径 +**说明**: 输出文件 + +**参数**: +> - file_data: 文件数据 相对路径 +
-源代码 + 源代码 ```python def write_to_files(file_data: dict[str, str]): @@ -56,12 +55,11 @@ def write_to_files(file_data: dict[str, str]): ```
-### ***def*** `get_file_list(module_folder: str) -> None` - +### *func* `get_file_list()`
-源代码 + 源代码 ```python def get_file_list(module_folder: str): @@ -74,22 +72,21 @@ def get_file_list(module_folder: str): ```
-### ***def*** `get_module_info_normal(file_path: str, ignore_private: bool) -> ModuleInfo` +### *func* `get_module_info_normal(file_path: str = True) -> ModuleInfo` -获取函数和类 -Args: - file_path: Python 文件路径 +**说明**: 获取函数和类 - ignore_private: 忽略私有函数和类 +**参数**: +> - file_path: Python 文件路径 +> - ignore_private: 忽略私有函数和类 -Returns: +**返回**: 模块信息 - 模块信息
-源代码 + 源代码 ```python def get_module_info_normal(file_path: str, ignore_private: bool=True) -> ModuleInfo: @@ -149,33 +146,33 @@ def get_module_info_normal(file_path: str, ignore_private: bool=True) -> ModuleI ```
-### ***def*** `generate_markdown(module_info: ModuleInfo, front_matter: Any) -> str` +### *func* `generate_markdown(module_info: ModuleInfo = None, front_matter = 'zh-CN') -> str` -生成模块的Markdown + +**说明**: 生成模块的Markdown 你可在此自定义生成的Markdown格式 -Args: +**参数**: +> - module_info: 模块信息 +> - front_matter: 自定义选项title, index, icon, category +> - lang: 语言 - module_info: 模块信息 +**返回**: Markdown 字符串 - front_matter: 自定义选项title, index, icon, category - -Returns: - - Markdown 字符串
-源代码 + 源代码 ```python -def generate_markdown(module_info: ModuleInfo, front_matter=None) -> str: +def generate_markdown(module_info: ModuleInfo, front_matter=None, lang: str='zh-CN') -> str: """ 生成模块的Markdown 你可在此自定义生成的Markdown格式 Args: module_info: 模块信息 front_matter: 自定义选项title, index, icon, category + lang: 语言 Returns: Markdown 字符串 """ @@ -205,7 +202,11 @@ def generate_markdown(module_info: ModuleInfo, front_matter=None) -> str: content += f"###   ***{('async ' if method.is_async else '')}def*** `{method.name}({', '.join(args_with_type)}) -> {method.return_type}`\n\n" method.docstring = method.docstring.replace('\n', '\n\n') content += f' {method.docstring}\n\n' - content += f'
\n源代码\n\n```python\n{method.source_code}\n```\n
\n\n' + if lang == 'zh-CN': + TEXT_SOURCE_CODE = '源代码' + else: + TEXT_SOURCE_CODE = 'Source Code' + content += f'
\n{TEXT_SOURCE_CODE}\n\n```python\n{method.source_code}\n```\n
\n\n' for attr in cls.attributes: content += f'###   ***attr*** `{attr.name}: {attr.type}`\n\n' for attr in module_info.attributes: @@ -219,25 +220,25 @@ def generate_markdown(module_info: ModuleInfo, front_matter=None) -> str: ```
-### ***def*** `generate_docs(module_folder: str, output_dir: str, with_top: bool, ignored_paths: Any) -> None` +### *func* `generate_docs(module_folder: str = False, output_dir: str = 'zh-CN', with_top: bool = None)` -生成文档 -Args: - module_folder: 模块文件夹 +**说明**: 生成文档 - output_dir: 输出文件夹 +**参数**: +> - module_folder: 模块文件夹 +> - output_dir: 输出文件夹 +> - with_top: 是否包含顶层文件夹 False时例如docs/api/module_a, docs/api/module_b, True时例如docs/api/module/module_a.md, docs/api/module/module_b.md +> - ignored_paths: 忽略的路径 +> - lang: 语言 - with_top: 是否包含顶层文件夹 False时例如docs/api/module_a, docs/api/module_b, True时例如docs/api/module/module_a.md, docs/api/module/module_b.md - - ignored_paths: 忽略的路径
-源代码 + 源代码 ```python -def generate_docs(module_folder: str, output_dir: str, with_top: bool=False, ignored_paths=None): +def generate_docs(module_folder: str, output_dir: str, with_top: bool=False, lang: str='zh-CN', ignored_paths=None): """ 生成文档 Args: @@ -245,6 +246,7 @@ def generate_docs(module_folder: str, output_dir: str, with_top: bool=False, ign output_dir: 输出文件夹 with_top: 是否包含顶层文件夹 False时例如docs/api/module_a, docs/api/module_b, True时例如docs/api/module/module_a.md, docs/api/module/module_b.md ignored_paths: 忽略的路径 + lang: 语言 """ if ignored_paths is None: ignored_paths = [] @@ -273,201 +275,8 @@ def generate_docs(module_folder: str, output_dir: str, with_top: bool=False, ign ```
-### ***class*** `DefType(Enum)` - - - -###   ***attr*** `FUNCTION: 'function'` - -###   ***attr*** `METHOD: 'method'` - -###   ***attr*** `STATIC_METHOD: 'staticmethod'` - -###   ***attr*** `CLASS_METHOD: 'classmethod'` - -###   ***attr*** `PROPERTY: 'property'` - -### ***class*** `FunctionInfo(BaseModel)` - - - -### ***class*** `AttributeInfo(BaseModel)` - - - -### ***class*** `ClassInfo(BaseModel)` - - - -### ***class*** `ModuleInfo(BaseModel)` - - - -### ***var*** `NO_TYPE_ANY = 'Any'` - - - -### ***var*** `NO_TYPE_HINT = 'NoTypeHint'` - - - -### ***var*** `FUNCTION = 'function'` - - - -### ***var*** `METHOD = 'method'` - - - -### ***var*** `STATIC_METHOD = 'staticmethod'` - - - -### ***var*** `CLASS_METHOD = 'classmethod'` - - - -### ***var*** `PROPERTY = 'property'` - - - -### ***var*** `file_list = []` - - - -### ***var*** `dot_sep_module_path = file_path.replace(os.sep, '.').replace('.py', '').replace('.pyi', '')` - - - -### ***var*** `module_docstring = ast.get_docstring(tree)` - - - -### ***var*** `module_info = ModuleInfo(module_path=dot_sep_module_path, functions=[], classes=[], attributes=[], docstring=module_docstring if module_docstring else '')` - - - -### ***var*** `content = ''` - - - -### ***var*** `front_matter = '---\n' + '\n'.join([f'{k}: {v}' for k, v in front_matter.items()]) + '\n---\n\n'` - - - -### ***var*** `file_list = get_file_list(module_folder)` - - - -### ***var*** `replace_data = {'__init__': 'README', '.py': '.md'}` - - - -### ***var*** `file_content = file.read()` - - - -### ***var*** `tree = ast.parse(file_content)` - - - -### ***var*** `args_with_type = [f'{arg[0]}: {arg[1]}' if arg[1] else arg[0] for arg in func.args]` - - - -### ***var*** `ignored_paths = []` - - - -### ***var*** `no_module_name_pyfile_path = get_relative_path(module_folder, pyfile_path)` - - - -### ***var*** `rel_md_path = pyfile_path if with_top else no_module_name_pyfile_path` - - - -### ***var*** `abs_md_path = os.path.join(output_dir, rel_md_path)` - - - -### ***var*** `module_info = get_module_info_normal(pyfile_path)` - - - -### ***var*** `md_content = generate_markdown(module_info, front_matter)` - - - -### ***var*** `inherit = f"({', '.join(cls.inherit)})" if cls.inherit else ''` - - - -### ***var*** `rel_md_path = rel_md_path.replace(rk, rv)` - - - -### ***var*** `front_matter = {'title': module_info.module_path.replace('.__init__', '').replace('_', '\\n'), 'index': 'true', 'icon': 'laptop-code', 'category': 'API'}` - - - -### ***var*** `front_matter = {'title': module_info.module_path.replace('_', '\\n'), 'order': '1', 'icon': 'laptop-code', 'category': 'API'}` - - - -### ***var*** `function_docstring = ast.get_docstring(node)` - - - -### ***var*** `func_info = FunctionInfo(name=node.name, args=[(arg.arg, ast.unparse(arg.annotation) if arg.annotation else NO_TYPE_ANY) for arg in node.args.args], return_type=ast.unparse(node.returns) if node.returns else 'None', docstring=function_docstring if function_docstring else '', type=DefType.FUNCTION, is_async=isinstance(node, ast.AsyncFunctionDef), source_code=ast.unparse(node))` - - - -### ***var*** `class_docstring = ast.get_docstring(node)` - - - -### ***var*** `class_info = ClassInfo(name=node.name, docstring=class_docstring if class_docstring else '', methods=[], attributes=[], inherit=[ast.unparse(base) for base in node.bases])` - - - -### ***var*** `args_with_type = [f'{arg[0]}: {arg[1]}' if arg[1] else arg[0] for arg in method.args]` - - - -### ***var*** `args_with_type = [f'{arg[0]}: {arg[1]}' if arg[1] and arg[0] != 'self' else arg[0] for arg in method.args]` - - - -### ***var*** `first_arg = node.args.args[0]` - - - -### ***var*** `method_docstring = ast.get_docstring(class_node)` - - - -### ***var*** `def_type = DefType.METHOD` - - - -### ***var*** `def_type = DefType.STATIC_METHOD` - - - -### ***var*** `attr_type = NO_TYPE_HINT` - - - -### ***var*** `def_type = DefType.CLASS_METHOD` - - - -### ***var*** `attr_type = ast.unparse(node.value.annotation)` - - - -### ***var*** `def_type = DefType.PROPERTY` - - - +### **class** `DefType(Enum)` +### **class** `FunctionInfo(BaseModel)` +### **class** `AttributeInfo(BaseModel)` +### **class** `ClassInfo(BaseModel)` +### **class** `ModuleInfo(BaseModel)` diff --git a/docs/dev/api/plugin/README.md b/docs/dev/api/plugin/README.md index 6c67f485..05c3a526 100644 --- a/docs/dev/api/plugin/README.md +++ b/docs/dev/api/plugin/README.md @@ -1,20 +1,17 @@ --- title: liteyuki.plugin -index: true -icon: laptop-code -category: API --- +### *func* `get_loaded_plugins() -> dict[str, Plugin]` -### ***def*** `get_loaded_plugins() -> dict[str, Plugin]` -获取已加载的插件 -Returns: +**说明**: 获取已加载的插件 + +**返回**: dict[str, Plugin]: 插件字典 - dict[str, Plugin]: 插件字典
-源代码 + 源代码 ```python def get_loaded_plugins() -> dict[str, Plugin]: diff --git a/docs/dev/api/plugin/load.md b/docs/dev/api/plugin/load.md index b999f5e1..becac96b 100644 --- a/docs/dev/api/plugin/load.md +++ b/docs/dev/api/plugin/load.md @@ -1,24 +1,20 @@ --- title: liteyuki.plugin.load -order: 1 -icon: laptop-code -category: API --- - -### ***def*** `load_plugin(module_path: str | Path) -> Optional[Plugin]` - -加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。 +### *func* `load_plugin() -> Optional[Plugin]` -参数: +**说明**: 加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。 - module_path: 插件名称 `path.to.your.plugin` - 或插件路径 `pathlib.Path(path/to/your/plugin)` +**参数**: +> - module_path: 插件名称 `path.to.your.plugin` +> - 或插件路径 `pathlib.Path(path/to/your/plugin)`: +
-源代码 + 源代码 ```python def load_plugin(module_path: str | Path) -> Optional[Plugin]: @@ -31,11 +27,21 @@ def load_plugin(module_path: str | Path) -> Optional[Plugin]: module_path = path_to_module_name(Path(module_path)) if isinstance(module_path, Path) else module_path try: module = import_module(module_path) - _plugins[module.__name__] = Plugin(name=module.__name__, module=module, module_name=module_path, metadata=module.__dict__.get('__plugin_metadata__', None)) - display_name = module.__name__.split('.')[-1] - if module.__dict__.get('__plugin_meta__'): + _plugins[module.__name__] = Plugin(name=module.__name__, module=module, module_name=module_path) + if module.__dict__.get('__plugin_metadata__', None): + metadata: 'PluginMetadata' = module.__dict__['__plugin_metadata__'] + display_name = module.__name__.split('.')[-1] + elif module.__dict__.get('__liteyuki_plugin_meta__', None): + metadata: 'PluginMetadata' = module.__dict__['__liteyuki_plugin_meta__'] + display_name = format_display_name(f"{metadata.name}({module.__name__.split('.')[-1]})", metadata.type) + elif module.__dict__.get('__plugin_meta__', None): metadata: 'PluginMetadata' = module.__dict__['__plugin_meta__'] display_name = format_display_name(f"{metadata.name}({module.__name__.split('.')[-1]})", metadata.type) + else: + logger.opt(colors=True).warning(f'The metadata of Liteyuki plugin "{module.__name__}" is not specified, use empty.') + metadata = PluginMetadata(name=module.__name__) + display_name = module.__name__.split('.')[-1] + _plugins[module.__name__].metadata = metadata logger.opt(colors=True).success(f'Succeeded to load liteyuki plugin "{display_name}"') return _plugins[module.__name__] except Exception as e: @@ -45,20 +51,20 @@ def load_plugin(module_path: str | Path) -> Optional[Plugin]: ```
-### ***def*** `load_plugins() -> set[Plugin]` - -导入文件夹下多个插件 +### *func* `load_plugins(*, ignore_warning: bool = True) -> set[Plugin]` -参数: +**说明**: 导入文件夹下多个插件 - plugin_dir: 文件夹路径 - ignore_warning: 是否忽略警告,通常是目录不存在或目录为空 +**参数**: +> - plugin_dir: 文件夹路径 +> - ignore_warning: 是否忽略警告,通常是目录不存在或目录为空 +
-源代码 + 源代码 ```python def load_plugins(*plugin_dir: str, ignore_warning: bool=True) -> set[Plugin]: @@ -97,24 +103,21 @@ def load_plugins(*plugin_dir: str, ignore_warning: bool=True) -> set[Plugin]: ```
-### ***def*** `format_display_name(display_name: str, plugin_type: PluginType) -> str` - -设置插件名称颜色,根据不同类型插件设置颜色 - -Args: - - display_name: 插件名称 - - plugin_type: 插件类型 +### *func* `format_display_name() -> str` -Returns: +**说明**: 设置插件名称颜色,根据不同类型插件设置颜色 + +**参数**: +> - display_name: 插件名称 +> - plugin_type: 插件类型 + +**返回**: str: 设置后的插件名称 name - str: 设置后的插件名称 name
-源代码 + 源代码 ```python def format_display_name(display_name: str, plugin_type: PluginType) -> str: @@ -141,59 +144,19 @@ def format_display_name(display_name: str, plugin_type: PluginType) -> str: ```
-### ***var*** `module_path = path_to_module_name(Path(module_path)) if isinstance(module_path, Path) else module_path` +### ***var*** `_plugins = {}` +- **类型**: `dict[str, Plugin]` +### ***var*** `metadata = module.__dict__['__plugin_metadata__']` -### ***var*** `plugins = set()` +- **类型**: `'PluginMetadata'` +### ***var*** `metadata = module.__dict__['__liteyuki_plugin_meta__']` +- **类型**: `'PluginMetadata'` -### ***var*** `color = 'y'` - - - -### ***var*** `module = import_module(module_path)` - - - -### ***var*** `display_name = module.__name__.split('.')[-1]` - - - -### ***var*** `display_name = format_display_name(f"{metadata.name}({module.__name__.split('.')[-1]})", metadata.type)` - - - -### ***var*** `path = Path(os.path.join(dir_path, f))` - - - -### ***var*** `module_name = None` - - - -### ***var*** `color = 'm'` - - - -### ***var*** `color = 'g'` - - - -### ***var*** `color = 'e'` - - - -### ***var*** `color = 'c'` - - - -### ***var*** `module_name = f'{path_to_module_name(Path(dir_path))}.{f[:-3]}'` - - - -### ***var*** `module_name = path_to_module_name(path)` - +### ***var*** `metadata = module.__dict__['__plugin_meta__']` +- **类型**: `'PluginMetadata'` diff --git a/docs/dev/api/plugin/manager.md b/docs/dev/api/plugin/manager.md index 7d4d951c..ce7983da 100644 --- a/docs/dev/api/plugin/manager.md +++ b/docs/dev/api/plugin/manager.md @@ -1,7 +1,3 @@ --- title: liteyuki.plugin.manager -order: 1 -icon: laptop-code -category: API --- - diff --git a/docs/dev/api/plugin/model.md b/docs/dev/api/plugin/model.md index 6f986049..12319b96 100644 --- a/docs/dev/api/plugin/model.md +++ b/docs/dev/api/plugin/model.md @@ -1,89 +1,18 @@ --- title: liteyuki.plugin.model -order: 1 -icon: laptop-code -category: API --- - -### ***class*** `PluginType(Enum)` - -插件类型枚举值 - -###   ***attr*** `APPLICATION: 'application'` - -###   ***attr*** `SERVICE: 'service'` - -###   ***attr*** `MODULE: 'module'` - -###   ***attr*** `UNCLASSIFIED: 'unclassified'` - -###   ***attr*** `TEST: 'test'` - -### ***class*** `PluginMetadata(BaseModel)` - -轻雪插件元数据,由插件编写者提供,name为必填项 - -Attributes: - ----------- +### **class** `PluginType(Enum)` +### **class** `PluginMetadata(BaseModel)` +### **class** `Plugin(BaseModel)` +### *method* `__hash__(self)` +
+ 源代码 -name: str - - 插件名称 - -description: str - - 插件描述 - -usage: str - - 插件使用方法 - -type: str - - 插件类型 - -author: str - - 插件作者 - -homepage: str - - 插件主页 - -extra: dict[str, Any] - - 额外信息 - -### ***class*** `Plugin(BaseModel)` - -存储插件信息 - -###   ***attr*** `model_config: {'arbitrary_types_allowed': True}` - -### ***var*** `APPLICATION = 'application'` - - - -### ***var*** `SERVICE = 'service'` - - - -### ***var*** `MODULE = 'module'` - - - -### ***var*** `UNCLASSIFIED = 'unclassified'` - - - -### ***var*** `TEST = 'test'` - - - -### ***var*** `model_config = {'arbitrary_types_allowed': True}` - - +```python +def __hash__(self): + return hash(self.module_name) +``` +
diff --git a/docs/dev/api/plugins/liteecho.md b/docs/dev/api/plugins/liteecho.md new file mode 100644 index 00000000..9d1faf3f --- /dev/null +++ b/docs/dev/api/plugins/liteecho.md @@ -0,0 +1,17 @@ +--- +title: liteyuki.plugins.liteecho +--- +### `@on_startswith(['liteecho'], rule=is_su_rule).handle()` +### *async func* `liteecho()` + + +
+ 源代码 + +```python +@on_startswith(['liteecho'], rule=is_su_rule).handle() +async def liteecho(event: MessageEvent): + event.reply(event.raw_message.strip()[8:].strip()) +``` +
+ diff --git a/docs/dev/api/plugins/plugin_loader/README.md b/docs/dev/api/plugins/plugin_loader/README.md new file mode 100644 index 00000000..be3b2ceb --- /dev/null +++ b/docs/dev/api/plugins/plugin_loader/README.md @@ -0,0 +1,25 @@ +--- +title: liteyuki.plugins.plugin_loader +--- +### *func* `default_plugins_loader()` + + + +**说明**: 默认插件加载器,应在初始化时调用 + + +
+ 源代码 + +```python +def default_plugins_loader(): + """ + 默认插件加载器,应在初始化时调用 + """ + for plugin in get_config('liteyuki.plugins', []): + load_plugin(plugin) + for plugin_dir in get_config('liteyuki.plugin_dirs', ['src/liteyuki_plugins']): + load_plugins(plugin_dir) +``` +
+ diff --git a/docs/dev/api/utils.md b/docs/dev/api/utils.md index 84532f46..cf3cb746 100644 --- a/docs/dev/api/utils.md +++ b/docs/dev/api/utils.md @@ -1,24 +1,20 @@ --- title: liteyuki.utils -order: 1 -icon: laptop-code -category: API --- +### *func* `is_coroutine_callable() -> bool` -### ***def*** `is_coroutine_callable(call: Callable[..., Any]) -> bool` -判断是否为协程可调用对象 -Args: +**说明**: 判断是否为协程可调用对象 - call: 可调用对象 +**参数**: +> - call: 可调用对象 -Returns: +**返回**: bool: 是否为协程可调用对象 - bool: 是否为协程可调用对象
-源代码 + 源代码 ```python def is_coroutine_callable(call: Callable[..., Any]) -> bool: @@ -38,20 +34,18 @@ def is_coroutine_callable(call: Callable[..., Any]) -> bool: ```
-### ***def*** `run_coroutine() -> None` - -运行协程 - -Args: - - coro: +### *func* `run_coroutine()` -Returns: +**说明**: 运行协程 + +**参数**: +> - coro: +
-源代码 + 源代码 ```python def run_coroutine(*coro: Coroutine): @@ -81,20 +75,47 @@ def run_coroutine(*coro: Coroutine): ```
-### ***def*** `path_to_module_name(path: Path) -> str` +### *func* `run_coroutine_in_thread()` -转换路径为模块名 -Args: - path: 路径a/b/c/d -> a.b.c.d +**说明**: 在新线程中运行协程 -Returns: +**参数**: +> - coro: - str: 模块名
-源代码 + 源代码 + +```python +def run_coroutine_in_thread(*coro: Coroutine): + """ + 在新线程中运行协程 + Args: + coro: + + Returns: + + """ + threading.Thread(target=run_coroutine, args=coro, daemon=True).start() +``` +
+ +### *func* `path_to_module_name() -> str` + + + +**说明**: 转换路径为模块名 + +**参数**: +> - path: 路径a/b/c/d -> a.b.c.d + +**返回**: str: 模块名 + + +
+ 源代码 ```python def path_to_module_name(path: Path) -> str: @@ -113,20 +134,20 @@ def path_to_module_name(path: Path) -> str: ```
-### ***def*** `async_wrapper(func: Callable[..., Any]) -> Callable[..., Coroutine]` +### *func* `async_wrapper() -> Callable[..., Coroutine]` -异步包装器 -Args: - func: Sync Callable +**说明**: 异步包装器 -Returns: +**参数**: +> - func: Sync Callable + +**返回**: Coroutine: Asynchronous Callable - Coroutine: Asynchronous Callable
-源代码 + 源代码 ```python def async_wrapper(func: Callable[..., Any]) -> Callable[..., Coroutine]: @@ -145,36 +166,3 @@ def async_wrapper(func: Callable[..., Any]) -> Callable[..., Coroutine]: ```
-### ***async def*** `wrapper() -> None` - - - -
-源代码 - -```python -async def wrapper(*args, **kwargs): - return func(*args, **kwargs) -``` -
- -### ***var*** `IS_MAIN_PROCESS = multiprocessing.current_process().name == 'MainProcess'` - - - -### ***var*** `func_ = getattr(call, '__call__', None)` - - - -### ***var*** `rel_path = path.resolve().relative_to(Path.cwd().resolve())` - - - -### ***var*** `loop = asyncio.get_event_loop()` - - - -### ***var*** `loop = asyncio.new_event_loop()` - - - diff --git a/docs/en/dev/api/README.md b/docs/en/dev/api/README.md index 181ec73d..5e57461c 100644 --- a/docs/en/dev/api/README.md +++ b/docs/en/dev/api/README.md @@ -1,7 +1,3 @@ --- title: liteyuki -index: true -icon: laptop-code -category: API --- - diff --git a/docs/en/dev/api/bot/README.md b/docs/en/dev/api/bot/README.md index 9f019444..94dd4656 100644 --- a/docs/en/dev/api/bot/README.md +++ b/docs/en/dev/api/bot/README.md @@ -1,22 +1,18 @@ --- title: liteyuki.bot -index: true -icon: laptop-code -category: API --- - -### ***def*** `get_bot() -> LiteyukiBot` - -获取轻雪实例 +### *func* `get_bot() -> LiteyukiBot` -Returns: +**Description**: 获取轻雪实例 + + +**Return**: LiteyukiBot: 当前的轻雪实例 - LiteyukiBot: 当前的轻雪实例
-源代码 + Source code ```python def get_bot() -> LiteyukiBot: @@ -35,24 +31,21 @@ def get_bot() -> LiteyukiBot: ```
-### ***def*** `get_config(key: str, default: Any) -> Any` - -获取配置 - -Args: - - key: 配置键 - - default: 默认值 +### *func* `get_config(key: str = None) -> Any` -Returns: +**Description**: 获取配置 + +**Arguments**: +> - key: 配置键 +> - default: 默认值 + +**Return**: Any: 配置值 - Any: 配置值
-源代码 + Source code ```python def get_config(key: str, default: Any=None) -> Any: @@ -69,26 +62,22 @@ def get_config(key: str, default: Any=None) -> Any: ```
-### ***def*** `get_config_with_compat(key: str, compat_keys: tuple[str], default: Any) -> Any` - -获取配置,兼容旧版本 - -Args: - - key: 配置键 - - compat_keys: 兼容键 - - default: 默认值 +### *func* `get_config_with_compat(key: str = None) -> Any` -Returns: +**Description**: 获取配置,兼容旧版本 + +**Arguments**: +> - key: 配置键 +> - compat_keys: 兼容键 +> - default: 默认值 + +**Return**: Any: 配置值 - Any: 配置值
-源代码 + Source code ```python def get_config_with_compat(key: str, compat_keys: tuple[str], default: Any=None) -> Any: @@ -112,12 +101,11 @@ def get_config_with_compat(key: str, compat_keys: tuple[str], default: Any=None) ```
-### ***def*** `print_logo() -> None` - +### *func* `print_logo()`
-源代码 + Source code ```python def print_logo(): @@ -125,22 +113,20 @@ def print_logo(): ```
-### ***class*** `LiteyukiBot` +### **class** `LiteyukiBot` +### *method* `__init__(self) -> None` -###   ***def*** `__init__(self) -> None` +**Description**: 初始化轻雪实例 - 初始化轻雪实例 +**Arguments**: +> - *args: +> - **kwargs: 配置 -Args: - - *args: - - **kwargs: 配置
-源代码 + Source code ```python def __init__(self, *args, **kwargs) -> None: @@ -177,12 +163,15 @@ def __init__(self, *args, **kwargs) -> None: ```
-###   ***def*** `run(self) -> None` +### *method* `run(self)` + + + +**Description**: 启动逻辑 - 启动逻辑
-源代码 + Source code ```python def run(self): @@ -196,14 +185,15 @@ def run(self): ```
-###   ***def*** `keep_alive(self) -> None` +### *method* `keep_alive(self)` - 保持轻雪运行 -Returns: + +**Description**: 保持轻雪运行 +
-源代码 + Source code ```python def keep_alive(self): @@ -221,14 +211,46 @@ def keep_alive(self): ```
-###   ***def*** `restart(self, delay: int) -> None` +### *method* `_handle_exit(self, signum, frame)` - 重启轻雪本体 -Returns: + +**Description**: 信号处理 + +**Arguments**: +> - signum: +> - frame: +
-源代码 + Source code + +```python +def _handle_exit(self, signum, frame): + """ + 信号处理 + Args: + signum: + frame: + + Returns: + + """ + logger.info('Received signal, stopping all processes.') + self.stop() + sys.exit(0) +``` +
+ +### *method* `restart(self, delay: int = 0)` + + + +**Description**: 重启轻雪本体 + + +
+ Source code ```python def restart(self, delay: int=0): @@ -257,18 +279,18 @@ def restart(self, delay: int=0): ```
-###   ***def*** `restart_process(self, name: Optional[str]) -> None` +### *method* `restart_process(self, name: Optional[str] = None)` - 停止轻雪 -Args: - name: 进程名称, 默认为None, 所有进程 +**Description**: 停止轻雪 + +**Arguments**: +> - name: 进程名称, 默认为None, 所有进程 -Returns:
-源代码 + Source code ```python def restart_process(self, name: Optional[str]=None): @@ -290,14 +312,15 @@ def restart_process(self, name: Optional[str]=None): ```
-###   ***def*** `init(self) -> None` +### *method* `init(self)` - 初始化轻雪, 自动调用 -Returns: + +**Description**: 初始化轻雪, 自动调用 +
-源代码 + Source code ```python def init(self, *args, **kwargs): @@ -310,12 +333,11 @@ def init(self, *args, **kwargs): ```
-###   ***def*** `init_logger(self) -> None` +### *method* `init_logger(self)` - 
-源代码 + Source code ```python def init_logger(self): @@ -323,14 +345,15 @@ def init_logger(self): ```
-###   ***def*** `stop(self) -> None` +### *method* `stop(self)` - 停止轻雪 -Returns: + +**Description**: 停止轻雪 +
-源代码 + Source code ```python def stop(self): @@ -344,20 +367,18 @@ def stop(self): ```
-###   ***def*** `on_before_start(self, func: LIFESPAN_FUNC) -> None` - - 注册启动前的函数 - -Args: - - func: +### *method* `on_before_start(self, func: LIFESPAN_FUNC)` -Returns: +**Description**: 注册启动前的函数 + +**Arguments**: +> - func: +
-源代码 + Source code ```python def on_before_start(self, func: LIFESPAN_FUNC): @@ -373,20 +394,18 @@ def on_before_start(self, func: LIFESPAN_FUNC): ```
-###   ***def*** `on_after_start(self, func: LIFESPAN_FUNC) -> None` - - 注册启动后的函数 - -Args: - - func: +### *method* `on_after_start(self, func: LIFESPAN_FUNC)` -Returns: +**Description**: 注册启动后的函数 + +**Arguments**: +> - func: +
-源代码 + Source code ```python def on_after_start(self, func: LIFESPAN_FUNC): @@ -402,20 +421,18 @@ def on_after_start(self, func: LIFESPAN_FUNC): ```
-###   ***def*** `on_after_shutdown(self, func: LIFESPAN_FUNC) -> None` - - 注册停止后的函数:未实现 - -Args: - - func: +### *method* `on_after_shutdown(self, func: LIFESPAN_FUNC)` -Returns: +**Description**: 注册停止后的函数:未实现 + +**Arguments**: +> - func: +
-源代码 + Source code ```python def on_after_shutdown(self, func: LIFESPAN_FUNC): @@ -431,20 +448,18 @@ def on_after_shutdown(self, func: LIFESPAN_FUNC): ```
-###   ***def*** `on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> None` - - 注册进程停止前的函数,为子进程停止时调用 - -Args: - - func: +### *method* `on_before_process_shutdown(self, func: LIFESPAN_FUNC)` -Returns: +**Description**: 注册进程停止前的函数,为子进程停止时调用 + +**Arguments**: +> - func: +
-源代码 + Source code ```python def on_before_process_shutdown(self, func: LIFESPAN_FUNC): @@ -460,20 +475,18 @@ def on_before_process_shutdown(self, func: LIFESPAN_FUNC): ```
-###   ***def*** `on_before_process_restart(self, func: LIFESPAN_FUNC) -> None` - - 注册进程重启前的函数,为子进程重启时调用 - -Args: - - func: +### *method* `on_before_process_restart(self, func: LIFESPAN_FUNC)` -Returns: +**Description**: 注册进程重启前的函数,为子进程重启时调用 + +**Arguments**: +> - func: +
-源代码 + Source code ```python def on_before_process_restart(self, func: LIFESPAN_FUNC): @@ -489,20 +502,18 @@ def on_before_process_restart(self, func: LIFESPAN_FUNC): ```
-###   ***def*** `on_after_restart(self, func: LIFESPAN_FUNC) -> None` - - 注册重启后的函数:未实现 - -Args: - - func: +### *method* `on_after_restart(self, func: LIFESPAN_FUNC)` -Returns: +**Description**: 注册重启后的函数:未实现 + +**Arguments**: +> - func: +
-源代码 + Source code ```python def on_after_restart(self, func: LIFESPAN_FUNC): @@ -518,20 +529,18 @@ def on_after_restart(self, func: LIFESPAN_FUNC): ```
-###   ***def*** `on_after_nonebot_init(self, func: LIFESPAN_FUNC) -> None` - - 注册nonebot初始化后的函数 - -Args: - - func: +### *method* `on_after_nonebot_init(self, func: LIFESPAN_FUNC)` -Returns: +**Description**: 注册nonebot初始化后的函数 + +**Arguments**: +> - func: +
-源代码 + Source code ```python def on_after_nonebot_init(self, func: LIFESPAN_FUNC): @@ -547,35 +556,7 @@ def on_after_nonebot_init(self, func: LIFESPAN_FUNC): ```
-### ***var*** `executable = sys.executable` - - - -### ***var*** `args = sys.argv` - - - -### ***var*** `chan_active = get_channel(f'{name}-active')` - - - -### ***var*** `cmd = 'start'` - - - -### ***var*** `chan_active = get_channel(f'{process_name}-active')` - - - -### ***var*** `cmd = 'nohup'` - - - -### ***var*** `cmd = 'open'` - - - -### ***var*** `cmd = 'nohup'` - +### ***var*** `_BOT_INSTANCE = NO_DEFAULT` +- **Type**: `LiteyukiBot` diff --git a/docs/en/dev/api/bot/lifespan.md b/docs/en/dev/api/bot/lifespan.md index 6466bd52..1580b253 100644 --- a/docs/en/dev/api/bot/lifespan.md +++ b/docs/en/dev/api/bot/lifespan.md @@ -1,57 +1,16 @@ --- title: liteyuki.bot.lifespan -order: 1 -icon: laptop-code -category: API --- +### **class** `Lifespan` +### *method* `__init__(self) -> None` -### ***def*** `run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC]) -> None` -运行函数 -Args: +**Description**: 轻雪生命周期管理,启动、停止、重启 - funcs: - -Returns:
-源代码 - -```python -@staticmethod -def run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC], *args, **kwargs) -> None: - """ - 运行函数 - Args: - funcs: - Returns: - """ - try: - loop = asyncio.get_event_loop() - except RuntimeError: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - tasks = [] - for func in funcs: - if is_coroutine_callable(func): - tasks.append(func(*args, **kwargs)) - else: - tasks.append(async_wrapper(func)(*args, **kwargs)) - loop.run_until_complete(asyncio.gather(*tasks)) -``` -
- -### ***class*** `Lifespan` - - - -###   ***def*** `__init__(self) -> None` - - 轻雪生命周期管理,启动、停止、重启 - -
-源代码 + Source code ```python def __init__(self) -> None: @@ -69,19 +28,19 @@ def __init__(self) -> None: ```
-###   ***@staticmethod*** -###   ***def*** `run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC]) -> None` +### `@staticmethod` +### *method* `run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC]) -> None` - 运行函数 -Args: - funcs: +**Description**: 运行函数 + +**Arguments**: +> - funcs: -Returns:
-源代码 + Source code ```python @staticmethod @@ -107,20 +66,20 @@ def run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC], *args, **kwarg ```
-###   ***def*** `on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` +### *method* `on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` - 注册启动时的函数 -Args: - func: +**Description**: 注册启动时的函数 -Returns: +**Arguments**: +> - func: + +**Return**: LIFESPAN_FUNC: - LIFESPAN_FUNC:
-源代码 + Source code ```python def on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: @@ -136,20 +95,20 @@ def on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: ```
-###   ***def*** `on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` +### *method* `on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` - 注册启动时的函数 -Args: - func: +**Description**: 注册启动时的函数 -Returns: +**Arguments**: +> - func: + +**Return**: LIFESPAN_FUNC: - LIFESPAN_FUNC:
-源代码 + Source code ```python def on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: @@ -165,20 +124,20 @@ def on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: ```
-###   ***def*** `on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` +### *method* `on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` - 注册停止前的函数 -Args: - func: +**Description**: 注册停止前的函数 -Returns: +**Arguments**: +> - func: + +**Return**: LIFESPAN_FUNC: - LIFESPAN_FUNC:
-源代码 + Source code ```python def on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: @@ -194,22 +153,20 @@ def on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: ```
-###   ***def*** `on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` - - 注册停止后的函数 - -Args: - - func: +### *method* `on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` -Returns: +**Description**: 注册停止后的函数 + +**Arguments**: +> - func: + +**Return**: LIFESPAN_FUNC: - LIFESPAN_FUNC:
-源代码 + Source code ```python def on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: @@ -227,20 +184,20 @@ def on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: ```
-###   ***def*** `on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` +### *method* `on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` - 注册重启时的函数 -Args: - func: +**Description**: 注册重启时的函数 -Returns: +**Arguments**: +> - func: + +**Return**: LIFESPAN_FUNC: - LIFESPAN_FUNC:
-源代码 + Source code ```python def on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: @@ -256,20 +213,20 @@ def on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: ```
-###   ***def*** `on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` +### *method* `on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC` - 注册重启后的函数 -Args: - func: +**Description**: 注册重启后的函数 -Returns: +**Arguments**: +> - func: + +**Return**: LIFESPAN_FUNC: - LIFESPAN_FUNC:
-源代码 + Source code ```python def on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: @@ -285,20 +242,18 @@ def on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC: ```
-###   ***def*** `on_after_nonebot_init(self, func: Any) -> None` - - 注册 NoneBot 初始化后的函数 - -Args: - - func: +### *method* `on_after_nonebot_init(self, func)` -Returns: +**Description**: 注册 NoneBot 初始化后的函数 + +**Arguments**: +> - func: +
-源代码 + Source code ```python def on_after_nonebot_init(self, func): @@ -315,14 +270,15 @@ def on_after_nonebot_init(self, func): ```
-###   ***def*** `before_start(self) -> None` +### *method* `before_start(self) -> None` - 启动前 -Returns: + +**Description**: 启动前 +
-源代码 + Source code ```python def before_start(self) -> None: @@ -335,14 +291,15 @@ def before_start(self) -> None: ```
-###   ***def*** `after_start(self) -> None` +### *method* `after_start(self) -> None` - 启动后 -Returns: + +**Description**: 启动后 +
-源代码 + Source code ```python def after_start(self) -> None: @@ -355,14 +312,15 @@ def after_start(self) -> None: ```
-###   ***def*** `before_process_shutdown(self) -> None` +### *method* `before_process_shutdown(self) -> None` - 停止前 -Returns: + +**Description**: 停止前 +
-源代码 + Source code ```python def before_process_shutdown(self) -> None: @@ -375,14 +333,15 @@ def before_process_shutdown(self) -> None: ```
-###   ***def*** `after_shutdown(self) -> None` +### *method* `after_shutdown(self) -> None` - 停止后 -Returns: + +**Description**: 停止后 +
-源代码 + Source code ```python def after_shutdown(self) -> None: @@ -395,14 +354,15 @@ def after_shutdown(self) -> None: ```
-###   ***def*** `before_process_restart(self) -> None` +### *method* `before_process_restart(self) -> None` - 重启前 -Returns: + +**Description**: 重启前 +
-源代码 + Source code ```python def before_process_restart(self) -> None: @@ -415,14 +375,15 @@ def before_process_restart(self) -> None: ```
-###   ***def*** `after_restart(self) -> None` +### *method* `after_restart(self) -> None` - 重启后 -Returns: + +**Description**: 重启后 +
-源代码 + Source code ```python def after_restart(self) -> None: @@ -436,15 +397,27 @@ def after_restart(self) -> None: ```
-### ***var*** `tasks = []` +### ***var*** `SYNC_LIFESPAN_FUNC = Callable[[], Any]` +- **Type**: `TypeAlias` +### ***var*** `ASYNC_LIFESPAN_FUNC = Callable[[], Awaitable[Any]]` -### ***var*** `loop = asyncio.get_event_loop()` +- **Type**: `TypeAlias` +### ***var*** `LIFESPAN_FUNC = SYNC_LIFESPAN_FUNC | ASYNC_LIFESPAN_FUNC` +- **Type**: `TypeAlias` -### ***var*** `loop = asyncio.new_event_loop()` +### ***var*** `SYNC_PROCESS_LIFESPAN_FUNC = Callable[[str], Any]` +- **Type**: `TypeAlias` +### ***var*** `ASYNC_PROCESS_LIFESPAN_FUNC = Callable[[str], Awaitable[Any]]` + +- **Type**: `TypeAlias` + +### ***var*** `PROCESS_LIFESPAN_FUNC = SYNC_PROCESS_LIFESPAN_FUNC | ASYNC_PROCESS_LIFESPAN_FUNC` + +- **Type**: `TypeAlias` diff --git a/docs/en/dev/api/comm/README.md b/docs/en/dev/api/comm/README.md index 09bccc34..2c1adbbf 100644 --- a/docs/en/dev/api/comm/README.md +++ b/docs/en/dev/api/comm/README.md @@ -1,7 +1,3 @@ --- title: liteyuki.comm -index: true -icon: laptop-code -category: API --- - diff --git a/docs/en/dev/api/comm/channel.md b/docs/en/dev/api/comm/channel.md index 8e5e170f..df7aee9a 100644 --- a/docs/en/dev/api/comm/channel.md +++ b/docs/en/dev/api/comm/channel.md @@ -1,22 +1,19 @@ --- title: liteyuki.comm.channel -order: 1 -icon: laptop-code -category: API --- +### *func* `set_channel()` -### ***def*** `set_channel(name: str, channel: Channel) -> None` -设置通道实例 -Args: +**Description**: 设置通道实例 - name: 通道名称 +**Arguments**: +> - name: 通道名称 +> - channel: 通道实例 - channel: 通道实例
-源代码 + Source code ```python def set_channel(name: str, channel: Channel): @@ -35,16 +32,18 @@ def set_channel(name: str, channel: Channel): ```
-### ***def*** `set_channels(channels: dict[str, Channel]) -> None` +### *func* `set_channels()` -设置通道实例 -Args: - channels: 通道名称 +**Description**: 设置通道实例 + +**Arguments**: +> - channels: 通道名称 +
-源代码 + Source code ```python def set_channels(channels: dict[str, Channel]): @@ -58,18 +57,18 @@ def set_channels(channels: dict[str, Channel]): ```
-### ***def*** `get_channel(name: str) -> Channel` +### *func* `get_channel() -> Channel` -获取通道实例 -Args: - name: 通道名称 +**Description**: 获取通道实例 + +**Arguments**: +> - name: 通道名称 -Returns:
-源代码 + Source code ```python def get_channel(name: str) -> Channel: @@ -88,14 +87,15 @@ def get_channel(name: str) -> Channel: ```
-### ***def*** `get_channels() -> dict[str, Channel]` +### *func* `get_channels() -> dict[str, Channel]` -获取通道实例 -Returns: + +**Description**: 获取通道实例 +
-源代码 + Source code ```python def get_channels() -> dict[str, Channel]: @@ -112,12 +112,12 @@ def get_channels() -> dict[str, Channel]: ```
-### ***def*** `on_set_channel(data: tuple[str, dict[str, Any]]) -> None` - +### `@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'set_channel')` +### *func* `on_set_channel()`
-源代码 + Source code ```python @channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'set_channel') @@ -127,12 +127,12 @@ def on_set_channel(data: tuple[str, dict[str, Any]]): ```
-### ***def*** `on_get_channel(data: tuple[str, dict[str, Any]]) -> None` - +### `@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channel')` +### *func* `on_get_channel()`
-源代码 + Source code ```python @channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channel') @@ -142,12 +142,12 @@ def on_get_channel(data: tuple[str, dict[str, Any]]): ```
-### ***def*** `on_get_channels(data: tuple[str, dict[str, Any]]) -> None` - +### `@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channels')` +### *func* `on_get_channels()`
-源代码 + Source code ```python @channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channels') @@ -157,81 +157,23 @@ def on_get_channels(data: tuple[str, dict[str, Any]]): ```
-### ***def*** `decorator(func: Callable[[T], Any]) -> Callable[[T], Any]` +### **class** `Channel(Generic[T])` +### *method* `__init__(self, _id: str = '', type_check: Optional[bool] = None)` +**Description**: 初始化通道 + +**Arguments**: +> - _id: 通道ID +> - type_check: 是否开启类型检查, 若为空,则传入泛型默认开启,否则默认关闭 + +
-源代码 + Source code ```python -def decorator(func: Callable[[T], Any]) -> Callable[[T], Any]: - global _func_id - - async def wrapper(data: T) -> Any: - if filter_func is not None: - if is_coroutine_callable(filter_func): - if not await filter_func(data): - return - elif not filter_func(data): - return - if is_coroutine_callable(func): - return await func(data) - else: - return func(data) - _callback_funcs[_func_id] = wrapper - if IS_MAIN_PROCESS: - self._on_main_receive_funcs.append(_func_id) - else: - self._on_sub_receive_funcs.append(_func_id) - _func_id += 1 - return func -``` -
- -### ***async def*** `wrapper(data: T) -> Any` - - - -
-源代码 - -```python -async def wrapper(data: T) -> Any: - if filter_func is not None: - if is_coroutine_callable(filter_func): - if not await filter_func(data): - return - elif not filter_func(data): - return - if is_coroutine_callable(func): - return await func(data) - else: - return func(data) -``` -
- -### ***class*** `Channel(Generic[T])` - -通道类,可以在进程间和进程内通信,双向但同时只能有一个发送者和一个接收者 - -有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器 - -###   ***def*** `__init__(self, _id: str, type_check: Optional[bool]) -> None` - - 初始化通道 - -Args: - - _id: 通道ID - - type_check: 是否开启类型检查, 若为空,则传入泛型默认开启,否则默认关闭 - -
-源代码 - -```python -def __init__(self, _id: str, type_check: Optional[bool]=None): +def __init__(self, _id: str='', type_check: Optional[bool]=None): """ 初始化通道 Args: @@ -254,16 +196,90 @@ def __init__(self, _id: str, type_check: Optional[bool]=None): ```
-###   ***def*** `send(self, data: T) -> None` +### *method* `_get_generic_type(self) -> Optional[type]` - 发送数据 -Args: - data: 数据 +**Description**: 获取通道传递泛型类型 + + +**Return**: Optional[type]: 泛型类型 +
-源代码 + Source code + +```python +def _get_generic_type(self) -> Optional[type]: + """ + 获取通道传递泛型类型 + + Returns: + Optional[type]: 泛型类型 + """ + if hasattr(self, '__orig_class__'): + return get_args(self.__orig_class__)[0] + return None +``` +
+ +### *method* `_validate_structure(self, data: Any, structure: type) -> bool` + + + +**Description**: 验证数据结构 + +**Arguments**: +> - data: 数据 +> - structure: 结构 + +**Return**: bool: 是否通过验证 + + +
+ Source code + +```python +def _validate_structure(self, data: Any, structure: type) -> bool: + """ + 验证数据结构 + Args: + data: 数据 + structure: 结构 + + Returns: + bool: 是否通过验证 + """ + if isinstance(structure, type): + return isinstance(data, structure) + elif isinstance(structure, tuple): + if not isinstance(data, tuple) or len(data) != len(structure): + return False + return all((self._validate_structure(d, s) for d, s in zip(data, structure))) + elif isinstance(structure, list): + if not isinstance(data, list): + return False + return all((self._validate_structure(d, structure[0]) for d in data)) + elif isinstance(structure, dict): + if not isinstance(data, dict): + return False + return all((k in data and self._validate_structure(data[k], structure[k]) for k in structure)) + return False +``` +
+ +### *method* `send(self, data: T)` + + + +**Description**: 发送数据 + +**Arguments**: +> - data: 数据 + + +
+ Source code ```python def send(self, data: T): @@ -282,14 +298,15 @@ def send(self, data: T): ```
-###   ***def*** `receive(self) -> T` +### *method* `receive(self) -> T` - 接收数据 -Args: + +**Description**: 接收数据 +
-源代码 + Source code ```python def receive(self) -> T: @@ -305,12 +322,15 @@ def receive(self) -> T: ```
-###   ***def*** `close(self) -> None` +### *method* `close(self)` + + + +**Description**: 关闭通道 - 关闭通道
-源代码 + Source code ```python def close(self): @@ -323,20 +343,20 @@ def close(self): ```
-###   ***def*** `on_receive(self, filter_func: Optional[FILTER_FUNC]) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]` +### *method* `on_receive(self, filter_func: Optional[FILTER_FUNC] = None) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]` - 接收数据并执行函数 -Args: - filter_func: 过滤函数,为None则不过滤 +**Description**: 接收数据并执行函数 -Returns: +**Arguments**: +> - filter_func: 过滤函数,为None则不过滤 + +**Return**: 装饰器,装饰一个函数在接收到数据后执行 - 装饰器,装饰一个函数在接收到数据后执行
-源代码 + Source code ```python def on_receive(self, filter_func: Optional[FILTER_FUNC]=None) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]: @@ -377,51 +397,159 @@ def on_receive(self, filter_func: Optional[FILTER_FUNC]=None) -> Callable[[Calla ```
-### ***var*** `T = TypeVar('T')` +### *method* `_run_on_main_receive_funcs(self, data: Any)` -### ***var*** `channel_deliver_active_channel = Channel(_id='channel_deliver_active_channel')` +**Description**: 运行接收函数 + +**Arguments**: +> - data: 数据 + + +
+ Source code + +```python +def _run_on_main_receive_funcs(self, data: Any): + """ + 运行接收函数 + Args: + data: 数据 + """ + for func_id in self._on_main_receive_funcs: + func = _callback_funcs[func_id] + run_coroutine(func(data)) +``` +
+ +### *method* `_run_on_sub_receive_funcs(self, data: Any)` -### ***var*** `channel_deliver_passive_channel = Channel(_id='channel_deliver_passive_channel')` +**Description**: 运行接收函数 + +**Arguments**: +> - data: 数据 + + +
+ Source code + +```python +def _run_on_sub_receive_funcs(self, data: Any): + """ + 运行接收函数 + Args: + data: 数据 + """ + for func_id in self._on_sub_receive_funcs: + func = _callback_funcs[func_id] + run_coroutine(func(data)) +``` +
+ +### *method* `_start_main_receive_loop(self)` -### ***var*** `recv_chan = data[1]['recv_chan']` +**Description**: 开始接收数据 + + +
+ Source code + +```python +def _start_main_receive_loop(self): + """ + 开始接收数据 + """ + self.is_main_receive_loop_running = True + while not self._closed: + data = self.conn_recv.recv() + self._run_on_main_receive_funcs(data) +``` +
+ +### *method* `_start_sub_receive_loop(self)` -### ***var*** `recv_chan = Channel[Channel[Any]]('recv_chan')` +**Description**: 开始接收数据 +
+ Source code -### ***var*** `recv_chan = Channel[dict[str, Channel[Any]]]('recv_chan')` +```python +def _start_sub_receive_loop(self): + """ + 开始接收数据 + """ + self.is_sub_receive_loop_running = True + while not self._closed: + data = self.conn_recv.recv() + self._run_on_sub_receive_funcs(data) +``` +
+### ***var*** `SYNC_ON_RECEIVE_FUNC = Callable[[T], Any]` +- **Type**: `TypeAlias` -### ***var*** `type_check = self._get_generic_type() is not None` +### ***var*** `ASYNC_ON_RECEIVE_FUNC = Callable[[T], Coroutine[Any, Any, Any]]` +- **Type**: `TypeAlias` +### ***var*** `ON_RECEIVE_FUNC = SYNC_ON_RECEIVE_FUNC | ASYNC_ON_RECEIVE_FUNC` -### ***var*** `data = self.conn_recv.recv()` +- **Type**: `TypeAlias` +### ***var*** `SYNC_FILTER_FUNC = Callable[[T], bool]` +- **Type**: `TypeAlias` -### ***var*** `func = _callback_funcs[func_id]` +### ***var*** `ASYNC_FILTER_FUNC = Callable[[T], Coroutine[Any, Any, bool]]` +- **Type**: `TypeAlias` +### ***var*** `FILTER_FUNC = SYNC_FILTER_FUNC | ASYNC_FILTER_FUNC` -### ***var*** `func = _callback_funcs[func_id]` +- **Type**: `TypeAlias` +### ***var*** `_func_id = 0` +- **Type**: `int` -### ***var*** `data = self.conn_recv.recv()` +### ***var*** `_channel = {}` +- **Type**: `dict[str, 'Channel']` +### ***var*** `_callback_funcs = {}` -### ***var*** `data = self.conn_recv.recv()` +- **Type**: `dict[int, ON_RECEIVE_FUNC]` +### ***var*** `active_channel = None` +- **Type**: `Optional['Channel']` + +- **Description**: 子进程可用的主动和被动通道 + +### ***var*** `passive_channel = None` + +- **Type**: `Optional['Channel']` + +### ***var*** `publish_channel = Channel(_id='publish_channel')` + +- **Type**: `Channel[tuple[str, dict[str, Any]]]` + +### ***var*** `channel_deliver_active_channel = NO_DEFAULT` + +- **Type**: `Channel[Channel[Any]]` + +- **Description**: 通道传递通道,主进程创建单例,子进程初始化时实例化 + +### ***var*** `channel_deliver_passive_channel = NO_DEFAULT` + +- **Type**: `Channel[tuple[str, dict[str, Any]]]` diff --git a/docs/en/dev/api/comm/event.md b/docs/en/dev/api/comm/event.md index a2a15f55..b89b17b5 100644 --- a/docs/en/dev/api/comm/event.md +++ b/docs/en/dev/api/comm/event.md @@ -1,20 +1,12 @@ --- title: liteyuki.comm.event -order: 1 -icon: laptop-code -category: API --- +### **class** `Event` +### *method* `__init__(self, name: str, data: dict[str, Any])` -### ***class*** `Event` - -事件类 - -###   ***def*** `__init__(self, name: str, data: dict[str, Any]) -> None` - - 
-源代码 + Source code ```python def __init__(self, name: str, data: dict[str, Any]): diff --git a/docs/en/dev/api/comm/storage.md b/docs/en/dev/api/comm/storage.md index cd19dbb7..c738ef30 100644 --- a/docs/en/dev/api/comm/storage.md +++ b/docs/en/dev/api/comm/storage.md @@ -1,46 +1,12 @@ --- title: liteyuki.comm.storage -order: 1 -icon: laptop-code -category: API --- - -### ***def*** `run_subscriber_receive_funcs(channel_: str, data: Any) -> None` - -运行订阅者接收函数 - -Args: - - channel_: 频道 - - data: 数据 - -
-源代码 - -```python -@staticmethod -def run_subscriber_receive_funcs(channel_: str, data: Any): - """ - 运行订阅者接收函数 - Args: - channel_: 频道 - data: 数据 - """ - if IS_MAIN_PROCESS: - if channel_ in _on_main_subscriber_receive_funcs and _on_main_subscriber_receive_funcs[channel_]: - run_coroutine(*[func(data) for func in _on_main_subscriber_receive_funcs[channel_]]) - elif channel_ in _on_sub_subscriber_receive_funcs and _on_sub_subscriber_receive_funcs[channel_]: - run_coroutine(*[func(data) for func in _on_sub_subscriber_receive_funcs[channel_]]) -``` -
- -### ***def*** `on_get(data: tuple[str, dict[str, Any]]) -> None` - +### `@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get')` +### *func* `on_get()`
-源代码 + Source code ```python @shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get') @@ -52,12 +18,12 @@ def on_get(data: tuple[str, dict[str, Any]]): ```
-### ***def*** `on_set(data: tuple[str, dict[str, Any]]) -> None` - +### `@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'set')` +### *func* `on_set()`
-源代码 + Source code ```python @shared_memory.passive_chan.on_receive(lambda d: d[0] == 'set') @@ -68,12 +34,12 @@ def on_set(data: tuple[str, dict[str, Any]]): ```
-### ***def*** `on_delete(data: tuple[str, dict[str, Any]]) -> None` - +### `@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'delete')` +### *func* `on_delete()`
-源代码 + Source code ```python @shared_memory.passive_chan.on_receive(lambda d: d[0] == 'delete') @@ -83,12 +49,12 @@ def on_delete(data: tuple[str, dict[str, Any]]): ```
-### ***def*** `on_get_all(data: tuple[str, dict[str, Any]]) -> None` - +### `@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get_all')` +### *func* `on_get_all()`
-源代码 + Source code ```python @shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get_all') @@ -98,12 +64,12 @@ def on_get_all(data: tuple[str, dict[str, Any]]): ```
-### ***def*** `on_publish(data: tuple[str, Any]) -> None` - +### `@channel.publish_channel.on_receive()` +### *func* `on_publish()`
-源代码 + Source code ```python @channel.publish_channel.on_receive() @@ -113,59 +79,12 @@ def on_publish(data: tuple[str, Any]): ```
-### ***def*** `decorator(func: ON_RECEIVE_FUNC) -> ON_RECEIVE_FUNC` - +### **class** `Subscriber` +### *method* `__init__(self)`
-源代码 - -```python -def decorator(func: ON_RECEIVE_FUNC) -> ON_RECEIVE_FUNC: - - async def wrapper(data: Any): - if is_coroutine_callable(func): - await func(data) - else: - func(data) - if IS_MAIN_PROCESS: - if channel_ not in _on_main_subscriber_receive_funcs: - _on_main_subscriber_receive_funcs[channel_] = [] - _on_main_subscriber_receive_funcs[channel_].append(wrapper) - else: - if channel_ not in _on_sub_subscriber_receive_funcs: - _on_sub_subscriber_receive_funcs[channel_] = [] - _on_sub_subscriber_receive_funcs[channel_].append(wrapper) - return wrapper -``` -
- -### ***async def*** `wrapper(data: Any) -> None` - - - -
-源代码 - -```python -async def wrapper(data: Any): - if is_coroutine_callable(func): - await func(data) - else: - func(data) -``` -
- -### ***class*** `Subscriber` - - - -###   ***def*** `__init__(self) -> None` - -  - -
-源代码 + Source code ```python def __init__(self): @@ -173,12 +92,11 @@ def __init__(self): ```
-###   ***def*** `receive(self) -> Any` +### *method* `receive(self) -> Any` - 
-源代码 + Source code ```python def receive(self) -> Any: @@ -186,12 +104,11 @@ def receive(self) -> Any: ```
-###   ***def*** `unsubscribe(self) -> None` +### *method* `unsubscribe(self) -> None` - 
-源代码 + Source code ```python def unsubscribe(self) -> None: @@ -199,16 +116,12 @@ def unsubscribe(self) -> None: ```
-### ***class*** `KeyValueStore` +### **class** `KeyValueStore` +### *method* `__init__(self)` - -###   ***def*** `__init__(self) -> None` - -  -
-源代码 + Source code ```python def __init__(self): @@ -221,18 +134,19 @@ def __init__(self): ```
-###   ***def*** `set(self, key: str, value: Any) -> None` +### *method* `set(self, key: str, value: Any) -> None` - 设置键值对 -Args: - key: 键 +**Description**: 设置键值对 + +**Arguments**: +> - key: 键 +> - value: 值 - value: 值
-源代码 + Source code ```python def set(self, key: str, value: Any) -> None: @@ -252,24 +166,21 @@ def set(self, key: str, value: Any) -> None: ```
-###   ***def*** `get(self, key: str, default: Optional[Any]) -> Optional[Any]` - - 获取键值对 - -Args: - - key: 键 - - default: 默认值 +### *method* `get(self, key: str, default: Optional[Any] = None) -> Optional[Any]` -Returns: +**Description**: 获取键值对 + +**Arguments**: +> - key: 键 +> - default: 默认值 + +**Return**: Any: 值 - Any: 值
-源代码 + Source code ```python def get(self, key: str, default: Optional[Any]=None) -> Optional[Any]: @@ -293,22 +204,19 @@ def get(self, key: str, default: Optional[Any]=None) -> Optional[Any]: ```
-###   ***def*** `delete(self, key: str, ignore_key_error: bool) -> None` - - 删除键值对 - -Args: - - key: 键 - - ignore_key_error: 是否忽略键不存在的错误 +### *method* `delete(self, key: str, ignore_key_error: bool = True) -> None` -Returns: +**Description**: 删除键值对 + +**Arguments**: +> - key: 键 +> - ignore_key_error: 是否忽略键不存在的错误 +
-源代码 + Source code ```python def delete(self, key: str, ignore_key_error: bool=True) -> None: @@ -335,16 +243,17 @@ def delete(self, key: str, ignore_key_error: bool=True) -> None: ```
-###   ***def*** `get_all(self) -> dict[str, Any]` +### *method* `get_all(self) -> dict[str, Any]` - 获取所有键值对 -Returns: - dict[str, Any]: 键值对 +**Description**: 获取所有键值对 + +**Return**: dict[str, Any]: 键值对 +
-源代码 + Source code ```python def get_all(self) -> dict[str, Any]: @@ -362,22 +271,19 @@ def get_all(self) -> dict[str, Any]: ```
-###   ***def*** `publish(self, channel_: str, data: Any) -> None` - - 发布消息 - -Args: - - channel_: 频道 - - data: 数据 +### *method* `publish(self, channel_: str, data: Any) -> None` -Returns: +**Description**: 发布消息 + +**Arguments**: +> - channel_: 频道 +> - data: 数据 +
-源代码 + Source code ```python def publish(self, channel_: str, data: Any) -> None: @@ -393,22 +299,20 @@ def publish(self, channel_: str, data: Any) -> None: ```
-###   ***def*** `on_subscriber_receive(self, channel_: str) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]` - - 订阅者接收消息时的回调 - -Args: - - channel_: 频道 +### *method* `on_subscriber_receive(self, channel_: str) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]` -Returns: +**Description**: 订阅者接收消息时的回调 + +**Arguments**: +> - channel_: 频道 + +**Return**: 装饰器 - 装饰器
-源代码 + Source code ```python def on_subscriber_receive(self, channel_: str) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]: @@ -447,19 +351,20 @@ def on_subscriber_receive(self, channel_: str) -> Callable[[ON_RECEIVE_FUNC], ON ```
-###   ***@staticmethod*** -###   ***def*** `run_subscriber_receive_funcs(channel_: str, data: Any) -> None` +### `@staticmethod` +### *method* `run_subscriber_receive_funcs(channel_: str, data: Any)` - 运行订阅者接收函数 -Args: - channel_: 频道 +**Description**: 运行订阅者接收函数 + +**Arguments**: +> - channel_: 频道 +> - data: 数据 - data: 数据
-源代码 + Source code ```python @staticmethod @@ -472,23 +377,48 @@ def run_subscriber_receive_funcs(channel_: str, data: Any): """ if IS_MAIN_PROCESS: if channel_ in _on_main_subscriber_receive_funcs and _on_main_subscriber_receive_funcs[channel_]: - run_coroutine(*[func(data) for func in _on_main_subscriber_receive_funcs[channel_]]) + run_coroutine_in_thread(*[func(data) for func in _on_main_subscriber_receive_funcs[channel_]]) elif channel_ in _on_sub_subscriber_receive_funcs and _on_sub_subscriber_receive_funcs[channel_]: - run_coroutine(*[func(data) for func in _on_sub_subscriber_receive_funcs[channel_]]) + run_coroutine_in_thread(*[func(data) for func in _on_sub_subscriber_receive_funcs[channel_]]) ```
-### ***class*** `GlobalKeyValueStore` +### *method* `_start_receive_loop(self)` -###   ***@classmethod*** -###   ***def*** `get_instance(cls: Any) -> None` +**Description**: 启动发布订阅接收器循环,在主进程中运行,若有子进程订阅则推送给子进程 - 
-源代码 + Source code + +```python +def _start_receive_loop(self): + """ + 启动发布订阅接收器循环,在主进程中运行,若有子进程订阅则推送给子进程 + """ + if IS_MAIN_PROCESS: + while True: + data = self.active_chan.receive() + if data[0] == 'publish': + self.run_subscriber_receive_funcs(data[1]['channel'], data[1]['data']) + self.publish_channel.send(data) + else: + while True: + data = self.publish_channel.receive() + if data[0] == 'publish': + self.run_subscriber_receive_funcs(data[1]['channel'], data[1]['data']) +``` +
+ +### **class** `GlobalKeyValueStore` +### `@classmethod` +### *method* `get_instance(cls)` + + +
+ Source code ```python @classmethod @@ -501,63 +431,19 @@ def get_instance(cls): ```
-###   ***attr*** `_instance: None` +### ***var*** `_on_main_subscriber_receive_funcs = {}` -###   ***attr*** `_lock: threading.Lock()` +- **Type**: `dict[str, list[ASYNC_ON_RECEIVE_FUNC]]` -### ***var*** `key = data[1]['key']` +- **Description**: 主进程订阅者接收函数 +### ***var*** `_on_sub_subscriber_receive_funcs = {}` +- **Type**: `dict[str, list[ASYNC_ON_RECEIVE_FUNC]]` -### ***var*** `default = data[1]['default']` - - - -### ***var*** `recv_chan = data[1]['recv_chan']` - - - -### ***var*** `key = data[1]['key']` - - - -### ***var*** `value = data[1]['value']` - - - -### ***var*** `key = data[1]['key']` - - - -### ***var*** `recv_chan = data[1]['recv_chan']` - - - -### ***var*** `lock = _get_lock(key)` - - - -### ***var*** `lock = _get_lock(key)` - - - -### ***var*** `recv_chan = Channel[Optional[Any]]('recv_chan')` - - - -### ***var*** `lock = _get_lock(key)` - - - -### ***var*** `recv_chan = Channel[dict[str, Any]]('recv_chan')` - - - -### ***var*** `data = self.active_chan.receive()` - - - -### ***var*** `data = self.publish_channel.receive()` +- **Description**: 子进程订阅者接收函数 +### ***var*** `shared_memory = GlobalKeyValueStore.get_instance()` +- **Type**: `KeyValueStore` diff --git a/docs/en/dev/api/config.md b/docs/en/dev/api/config.md index 24afd6c9..5c07b682 100644 --- a/docs/en/dev/api/config.md +++ b/docs/en/dev/api/config.md @@ -1,30 +1,22 @@ --- title: liteyuki.config -order: 1 -icon: laptop-code -category: API --- - -### ***def*** `flat_config(config: dict[str, Any]) -> dict[str, Any]` - -扁平化配置文件 +### *func* `flat_config() -> dict[str, Any]` +**Description**: 扁平化配置文件 + {a:{b:{c:1}}} -> {"a.b.c": 1} -Args: +**Arguments**: +> - config: 配置项目 - config: 配置项目 +**Return**: 扁平化后的配置文件,但也包含原有的键值对 - -Returns: - - 扁平化后的配置文件,但也包含原有的键值对 -
-源代码 + Source code ```python def flat_config(config: dict[str, Any]) -> dict[str, Any]: @@ -47,69 +39,80 @@ def flat_config(config: dict[str, Any]) -> dict[str, Any]: ```
-### ***def*** `load_from_yaml(file: str) -> dict[str, Any]` +### *func* `load_from_yaml() -> dict[str, Any]` + + + +**Description**: Load config from yaml file -Load config from yaml file
-源代码 + Source code ```python -def load_from_yaml(file: str) -> dict[str, Any]: +def load_from_yaml(file_: str) -> dict[str, Any]: """ Load config from yaml file """ - logger.debug(f'Loading YAML config from {file}') - config = yaml.safe_load(open(file, 'r', encoding='utf-8')) + logger.debug(f'Loading YAML config from {file_}') + config = yaml.safe_load(open(file_, 'r', encoding='utf-8')) return flat_config(config if config is not None else {}) ```
-### ***def*** `load_from_json(file: str) -> dict[str, Any]` +### *func* `load_from_json() -> dict[str, Any]` + + + +**Description**: Load config from json file -Load config from json file
-源代码 + Source code ```python -def load_from_json(file: str) -> dict[str, Any]: +def load_from_json(file_: str) -> dict[str, Any]: """ Load config from json file """ - logger.debug(f'Loading JSON config from {file}') - config = json.load(open(file, 'r', encoding='utf-8')) + logger.debug(f'Loading JSON config from {file_}') + config = json.load(open(file_, 'r', encoding='utf-8')) return flat_config(config if config is not None else {}) ```
-### ***def*** `load_from_toml(file: str) -> dict[str, Any]` +### *func* `load_from_toml() -> dict[str, Any]` + + + +**Description**: Load config from toml file -Load config from toml file
-源代码 + Source code ```python -def load_from_toml(file: str) -> dict[str, Any]: +def load_from_toml(file_: str) -> dict[str, Any]: """ Load config from toml file """ - logger.debug(f'Loading TOML config from {file}') - config = toml.load(open(file, 'r', encoding='utf-8')) + logger.debug(f'Loading TOML config from {file_}') + config = toml.load(open(file_, 'r', encoding='utf-8')) return flat_config(config if config is not None else {}) ```
-### ***def*** `load_from_files() -> dict[str, Any]` +### *func* `load_from_files(*, no_warning: bool = False) -> dict[str, Any]` -从指定文件加载配置项,会自动识别文件格式 + +**Description**: 从指定文件加载配置项,会自动识别文件格式 默认执行扁平化选项 +
-源代码 + Source code ```python def load_from_files(*files: str, no_warning: bool=False) -> dict[str, Any]: @@ -134,16 +137,17 @@ def load_from_files(*files: str, no_warning: bool=False) -> dict[str, Any]: ```
-### ***def*** `load_configs_from_dirs() -> dict[str, Any]` +### *func* `load_configs_from_dirs(*, no_waring: bool = False) -> dict[str, Any]` -从目录下加载配置文件,不递归 + +**Description**: 从目录下加载配置文件,不递归 按照读取文件的优先级反向覆盖 - 默认执行扁平化选项 +
-源代码 + Source code ```python def load_configs_from_dirs(*directories: str, no_waring: bool=False) -> dict[str, Any]: @@ -165,16 +169,17 @@ def load_configs_from_dirs(*directories: str, no_waring: bool=False) -> dict[str ```
-### ***def*** `load_config_in_default(no_waring: bool) -> dict[str, Any]` +### *func* `load_config_in_default(no_waring: bool = False) -> dict[str, Any]` -从一个标准的轻雪项目加载配置文件 + +**Description**: 从一个标准的轻雪项目加载配置文件 项目目录下的config.*和config目录下的所有配置文件 - 项目目录下的配置文件优先 +
-源代码 + Source code ```python def load_config_in_default(no_waring: bool=False) -> dict[str, Any]: @@ -189,43 +194,3 @@ def load_config_in_default(no_waring: bool=False) -> dict[str, Any]: ```
-### ***class*** `SatoriNodeConfig(BaseModel)` - - - -### ***class*** `SatoriConfig(BaseModel)` - - - -### ***class*** `BasicConfig(BaseModel)` - - - -### ***var*** `new_config = copy.deepcopy(config)` - - - -### ***var*** `config = yaml.safe_load(open(file, 'r', encoding='utf-8'))` - - - -### ***var*** `config = json.load(open(file, 'r', encoding='utf-8'))` - - - -### ***var*** `config = toml.load(open(file, 'r', encoding='utf-8'))` - - - -### ***var*** `config = {}` - - - -### ***var*** `config = {}` - - - -### ***var*** `config = load_configs_from_dirs('config', no_waring=no_waring)` - - - diff --git a/docs/en/dev/api/core/README.md b/docs/en/dev/api/core/README.md index 26c3500b..50a83056 100644 --- a/docs/en/dev/api/core/README.md +++ b/docs/en/dev/api/core/README.md @@ -1,7 +1,3 @@ --- title: liteyuki.core -index: true -icon: laptop-code -category: API --- - diff --git a/docs/en/dev/api/core/manager.md b/docs/en/dev/api/core/manager.md index 52397375..a7ec736d 100644 --- a/docs/en/dev/api/core/manager.md +++ b/docs/en/dev/api/core/manager.md @@ -1,20 +1,12 @@ --- title: liteyuki.core.manager -order: 1 -icon: laptop-code -category: API --- +### **class** `ChannelDeliver` +### *method* `__init__(self, active: Channel[Any], passive: Channel[Any], channel_deliver_active: Channel[Channel[Any]], channel_deliver_passive: Channel[tuple[str, dict]], publish: Channel[tuple[str, Any]])` -### ***class*** `ChannelDeliver` - - - -###   ***def*** `__init__(self, active: Channel[Any], passive: Channel[Any], channel_deliver_active: Channel[Channel[Any]], channel_deliver_passive: Channel[tuple[str, dict]], publish: Channel[tuple[str, Any]]) -> None` - - 
-源代码 + Source code ```python def __init__(self, active: Channel[Any], passive: Channel[Any], channel_deliver_active: Channel[Channel[Any]], channel_deliver_passive: Channel[tuple[str, dict]], publish: Channel[tuple[str, Any]]): @@ -26,16 +18,12 @@ def __init__(self, active: Channel[Any], passive: Channel[Any], channel_deliver_ ```
-### ***class*** `ProcessManager` +### **class** `ProcessManager` +### *method* `__init__(self, lifespan: Lifespan)` -进程管理器 - -###   ***def*** `__init__(self, lifespan: 'Lifespan') -> None` - - 
-源代码 + Source code ```python def __init__(self, lifespan: 'Lifespan'): @@ -45,18 +33,18 @@ def __init__(self, lifespan: 'Lifespan'): ```
-###   ***def*** `start(self, name: str) -> None` +### *method* `start(self, name: str)` - 开启后自动监控进程,并添加到进程字典中 -Args: - name: +**Description**: 开启后自动监控进程,并添加到进程字典中 + +**Arguments**: +> - name: -Returns:
-源代码 + Source code ```python def start(self, name: str): @@ -95,12 +83,15 @@ def start(self, name: str): ```
-###   ***def*** `start_all(self) -> None` +### *method* `start_all(self)` + + + +**Description**: 启动所有进程 - 启动所有进程
-源代码 + Source code ```python def start_all(self): @@ -112,22 +103,21 @@ def start_all(self): ```
-###   ***def*** `add_target(self, name: str, target: TARGET_FUNC, args: tuple, kwargs: Any) -> None` +### *method* `add_target(self, name: str, target: TARGET_FUNC, args: tuple = (), kwargs = None)` - 添加进程 -Args: - name: 进程名,用于获取和唯一标识 +**Description**: 添加进程 - target: 进程函数 +**Arguments**: +> - name: 进程名,用于获取和唯一标识 +> - target: 进程函数 +> - args: 进程函数参数 +> - kwargs: 进程函数关键字参数,通常会默认传入chan_active和chan_passive - args: 进程函数参数 - - kwargs: 进程函数关键字参数,通常会默认传入chan_active和chan_passive
-源代码 + Source code ```python def add_target(self, name: str, target: TARGET_FUNC, args: tuple=(), kwargs=None): @@ -149,12 +139,11 @@ def add_target(self, name: str, target: TARGET_FUNC, args: tuple=(), kwargs=None ```
-###   ***def*** `join_all(self) -> None` +### *method* `join_all(self)` - 
-源代码 + Source code ```python def join_all(self): @@ -163,20 +152,18 @@ def join_all(self): ```
-###   ***def*** `terminate(self, name: str) -> None` - - 终止进程并从进程字典中删除 - -Args: - - name: +### *method* `terminate(self, name: str)` -Returns: +**Description**: 终止进程并从进程字典中删除 + +**Arguments**: +> - name: +
-源代码 + Source code ```python def terminate(self, name: str): @@ -200,12 +187,11 @@ def terminate(self, name: str): ```
-###   ***def*** `terminate_all(self) -> None` +### *method* `terminate_all(self)` - 
-源代码 + Source code ```python def terminate_all(self): @@ -214,20 +200,18 @@ def terminate_all(self): ```
-###   ***def*** `is_process_alive(self, name: str) -> bool` - - 检查进程是否存活 - -Args: - - name: +### *method* `is_process_alive(self, name: str) -> bool` -Returns: +**Description**: 检查进程是否存活 + +**Arguments**: +> - name: +
-源代码 + Source code ```python def is_process_alive(self, name: str) -> bool: @@ -245,31 +229,7 @@ def is_process_alive(self, name: str) -> bool: ```
-### ***var*** `TIMEOUT = 10` - - - -### ***var*** `chan_active = get_channel(f'{name}-active')` - - - -### ***var*** `channel_deliver = ChannelDeliver(active=chan_active, passive=chan_passive, channel_deliver_active=channel_deliver_active_channel, channel_deliver_passive=channel_deliver_passive_channel, publish=publish_channel)` - - - -### ***var*** `process = self.processes[name]` - - - -### ***var*** `process = Process(target=self.targets[name][0], args=self.targets[name][1], kwargs=self.targets[name][2], daemon=True)` - - - -### ***var*** `data = chan_active.receive()` - - - -### ***var*** `kwargs = {}` - +### ***var*** `TARGET_FUNC = Callable[..., Any]` +- **Type**: `TypeAlias` diff --git a/docs/en/dev/api/dev/README.md b/docs/en/dev/api/dev/README.md index 6d883442..45d41428 100644 --- a/docs/en/dev/api/dev/README.md +++ b/docs/en/dev/api/dev/README.md @@ -1,7 +1,3 @@ --- title: liteyuki.dev -index: true -icon: laptop-code -category: API --- - diff --git a/docs/en/dev/api/dev/observer.md b/docs/en/dev/api/dev/observer.md index 2005bbf2..849e8087 100644 --- a/docs/en/dev/api/dev/observer.md +++ b/docs/en/dev/api/dev/observer.md @@ -1,16 +1,15 @@ --- title: liteyuki.dev.observer -order: 1 -icon: laptop-code -category: API --- +### *func* `debounce()` -### ***def*** `debounce(wait: Any) -> None` -防抖函数 + +**Description**: 防抖函数 +
-源代码 + Source code ```python def debounce(wait): @@ -32,24 +31,22 @@ def debounce(wait): ```
-### ***def*** `on_file_system_event(directories: tuple[str], recursive: bool, event_filter: FILTER_FUNC) -> Callable[[CALLBACK_FUNC], CALLBACK_FUNC]` +### *func* `on_file_system_event(directories: tuple[str] = True, recursive: bool = None) -> Callable[[CALLBACK_FUNC], CALLBACK_FUNC]` -注册文件系统变化监听器 -Args: - directories: 监听目录们 +**Description**: 注册文件系统变化监听器 - recursive: 是否递归监听子目录 +**Arguments**: +> - directories: 监听目录们 +> - recursive: 是否递归监听子目录 +> - event_filter: 事件过滤器, 返回True则执行回调函数 - event_filter: 事件过滤器, 返回True则执行回调函数 +**Return**: 装饰器,装饰一个函数在接收到数据后执行 -Returns: - - 装饰器,装饰一个函数在接收到数据后执行
-源代码 + Source code ```python def on_file_system_event(directories: tuple[str], recursive: bool=True, event_filter: FILTER_FUNC=None) -> Callable[[CALLBACK_FUNC], CALLBACK_FUNC]: @@ -78,91 +75,13 @@ def on_file_system_event(directories: tuple[str], recursive: bool=True, event_fi ```
-### ***def*** `decorator(func: Any) -> None` - +### **class** `CodeModifiedHandler(FileSystemEventHandler)` +### `@debounce(1)` +### *method* `on_modified(self, event)`
-源代码 - -```python -def decorator(func): - - def wrapper(*args, **kwargs): - nonlocal last_call_time - current_time = time.time() - if current_time - last_call_time > wait: - last_call_time = current_time - return func(*args, **kwargs) - last_call_time = None - return wrapper -``` -
- -### ***def*** `decorator(func: CALLBACK_FUNC) -> CALLBACK_FUNC` - - - -
-源代码 - -```python -def decorator(func: CALLBACK_FUNC) -> CALLBACK_FUNC: - - def wrapper(event: FileSystemEvent): - if event_filter is not None and (not event_filter(event)): - return - func(event) - code_modified_handler = CodeModifiedHandler() - code_modified_handler.on_modified = wrapper - for directory in directories: - observer.schedule(code_modified_handler, directory, recursive=recursive) - return func -``` -
- -### ***def*** `wrapper() -> None` - - - -
-源代码 - -```python -def wrapper(*args, **kwargs): - nonlocal last_call_time - current_time = time.time() - if current_time - last_call_time > wait: - last_call_time = current_time - return func(*args, **kwargs) -``` -
- -### ***def*** `wrapper(event: FileSystemEvent) -> None` - - - -
-源代码 - -```python -def wrapper(event: FileSystemEvent): - if event_filter is not None and (not event_filter(event)): - return - func(event) -``` -
- -### ***class*** `CodeModifiedHandler(FileSystemEventHandler)` - -Handler for code file changes - -###   ***def*** `on_modified(self, event: Any) -> None` - -  - -
-源代码 + Source code ```python @debounce(1) @@ -171,12 +90,11 @@ def on_modified(self, event): ```
-###   ***def*** `on_created(self, event: Any) -> None` +### *method* `on_created(self, event)` - 
-源代码 + Source code ```python def on_created(self, event): @@ -184,12 +102,11 @@ def on_created(self, event): ```
-###   ***def*** `on_deleted(self, event: Any) -> None` +### *method* `on_deleted(self, event)` - 
-源代码 + Source code ```python def on_deleted(self, event): @@ -197,12 +114,11 @@ def on_deleted(self, event): ```
-###   ***def*** `on_moved(self, event: Any) -> None` +### *method* `on_moved(self, event)` - 
-源代码 + Source code ```python def on_moved(self, event): @@ -210,12 +126,11 @@ def on_moved(self, event): ```
-###   ***def*** `on_any_event(self, event: Any) -> None` +### *method* `on_any_event(self, event)` - 
-源代码 + Source code ```python def on_any_event(self, event): @@ -223,27 +138,15 @@ def on_any_event(self, event): ```
-### ***var*** `liteyuki_bot = get_bot()` +### ***var*** `CALLBACK_FUNC = Callable[[FileSystemEvent], None]` +- **Type**: `TypeAlias` +- **Description**: 位置1为FileSystemEvent -### ***var*** `observer = Observer()` - - - -### ***var*** `last_call_time = None` - - - -### ***var*** `code_modified_handler = CodeModifiedHandler()` - - - -### ***var*** `current_time = time.time()` - - - -### ***var*** `last_call_time = current_time` +### ***var*** `FILTER_FUNC = Callable[[FileSystemEvent], bool]` +- **Type**: `TypeAlias` +- **Description**: 位置1为FileSystemEvent diff --git a/docs/en/dev/api/dev/plugin.md b/docs/en/dev/api/dev/plugin.md index caafa5ea..7f5b48f4 100644 --- a/docs/en/dev/api/dev/plugin.md +++ b/docs/en/dev/api/dev/plugin.md @@ -1,20 +1,18 @@ --- title: liteyuki.dev.plugin -order: 1 -icon: laptop-code -category: API --- +### *func* `run_plugins()` -### ***def*** `run_plugins() -> None` -运行插件,无需手动初始化bot -Args: +**Description**: 运行插件,无需手动初始化bot + +**Arguments**: +> - module_path: 插件路径,参考`liteyuki.load_plugin`的函数签名 - module_path: 插件路径,参考`liteyuki.load_plugin`的函数签名
-源代码 + Source code ```python def run_plugins(*module_path: str | Path): @@ -32,15 +30,3 @@ def run_plugins(*module_path: str | Path): ```
-### ***var*** `cfg = load_config_in_default()` - - - -### ***var*** `plugins = cfg.get('liteyuki.plugins', [])` - - - -### ***var*** `bot = LiteyukiBot(**cfg)` - - - diff --git a/docs/en/dev/api/exception.md b/docs/en/dev/api/exception.md index 469c00d6..4134eec4 100644 --- a/docs/en/dev/api/exception.md +++ b/docs/en/dev/api/exception.md @@ -1,11 +1,4 @@ --- title: liteyuki.exception -order: 1 -icon: laptop-code -category: API --- - -### ***class*** `LiteyukiException(BaseException)` - -Liteyuki的异常基类。 - +### **class** `LiteyukiException(BaseException)` diff --git a/docs/en/dev/api/log.md b/docs/en/dev/api/log.md index af382275..4b39a99d 100644 --- a/docs/en/dev/api/log.md +++ b/docs/en/dev/api/log.md @@ -1,16 +1,11 @@ --- title: liteyuki.log -order: 1 -icon: laptop-code -category: API --- - -### ***def*** `get_format(level: str) -> str` - +### *func* `get_format() -> str`
-源代码 + Source code ```python def get_format(level: str) -> str: @@ -21,14 +16,15 @@ def get_format(level: str) -> str: ```
-### ***def*** `init_log(config: dict) -> None` +### *func* `init_log()` -在语言加载完成后执行 -Returns: + +**Description**: 在语言加载完成后执行 +
-源代码 + Source code ```python def init_log(config: dict): @@ -48,11 +44,11 @@ def init_log(config: dict): ```
-### ***var*** `logger = loguru.logger` +### ***var*** `debug_format = '{time:YYYY-MM-DD HH:mm:ss} [{level.icon}] <{name}.{module}.{function}:{line}> {message}'` +- **Type**: `str` +### ***var*** `default_format = '{time:MM-DD HH:mm:ss} [{level.icon}] <{name}> {message}'` -### ***var*** `show_icon = config.get('log_icon', True)` - - +- **Type**: `str` diff --git a/docs/en/dev/api/message/README.md b/docs/en/dev/api/message/README.md index 0d664851..57e6955a 100644 --- a/docs/en/dev/api/message/README.md +++ b/docs/en/dev/api/message/README.md @@ -1,7 +1,3 @@ --- title: liteyuki.message -index: true -icon: laptop-code -category: API --- - diff --git a/docs/en/dev/api/message/event.md b/docs/en/dev/api/message/event.md index 740ff8bd..a96423bb 100644 --- a/docs/en/dev/api/message/event.md +++ b/docs/en/dev/api/message/event.md @@ -1,47 +1,19 @@ --- title: liteyuki.message.event -order: 1 -icon: laptop-code -category: API --- - -### ***class*** `MessageEvent` +### **class** `MessageEvent` +### *method* `__init__(self, bot_id: str, message: list[dict[str, Any]] | str, message_type: str, raw_message: str, session_id: str, user_id: str, session_type: str, receive_channel: str, data: Optional[dict[str, Any]] = None)` -###   ***def*** `__init__(self, bot_id: str, message: list[dict[str, Any]] | str, message_type: str, raw_message: str, session_id: str, session_type: str, receive_channel: str, data: Optional[dict[str, Any]]) -> None` +**Description**: 轻雪抽象消息事件 - 轻雪抽象消息事件 - -Args: - - - - bot_id: 机器人ID - - message: 消息,消息段数组[{type: str, data: dict[str, Any]}] - - raw_message: 原始消息(通常为纯文本的格式) - - message_type: 消息类型(private, group, other) - - - - session_id: 会话ID(私聊通常为用户ID,群聊通常为群ID) - - session_type: 会话类型(private, group) - - receive_channel: 接收频道(用于回复消息) - - - - data: 附加数据
-源代码 + Source code ```python -def __init__(self, bot_id: str, message: list[dict[str, Any]] | str, message_type: str, raw_message: str, session_id: str, session_type: str, receive_channel: str, data: Optional[dict[str, Any]]=None): +def __init__(self, bot_id: str, message: list[dict[str, Any]] | str, message_type: str, raw_message: str, session_id: str, user_id: str, session_type: str, receive_channel: str, data: Optional[dict[str, Any]]=None): """ 轻雪抽象消息事件 Args: @@ -66,22 +38,23 @@ def __init__(self, bot_id: str, message: list[dict[str, Any]] | str, message_typ self.raw_message = raw_message self.session_id = session_id self.session_type = session_type + self.user_id = user_id self.receive_channel = receive_channel ```
-###   ***def*** `reply(self, message: str | dict[str, Any]) -> None` +### *method* `reply(self, message: str | dict[str, Any])` - 回复消息 -Args: - message: +**Description**: 回复消息 + +**Arguments**: +> - message: -Returns:
-源代码 + Source code ```python def reply(self, message: str | dict[str, Any]): @@ -96,11 +69,3 @@ def reply(self, message: str | dict[str, Any]): ```
-### ***var*** `reply_event = MessageEvent(message_type=self.session_type, message=message, raw_message='', data={'message': message}, bot_id=self.bot_id, session_id=self.session_id, session_type=self.session_type, receive_channel='_')` - - - -### ***var*** `data = {}` - - - diff --git a/docs/en/dev/api/message/matcher.md b/docs/en/dev/api/message/matcher.md index 4a6341b6..44f99f0a 100644 --- a/docs/en/dev/api/message/matcher.md +++ b/docs/en/dev/api/message/matcher.md @@ -1,28 +1,21 @@ --- title: liteyuki.message.matcher -order: 1 -icon: laptop-code -category: API --- - -### ***class*** `Matcher` +### **class** `Matcher` +### *method* `__init__(self, rule: Rule, priority: int, block: bool)` -###   ***def*** `__init__(self, rule: Rule, priority: int, block: bool) -> None` +**Description**: 匹配器 - 匹配器 +**Arguments**: +> - rule: 规则 +> - priority: 优先级 >= 0 +> - block: 是否阻断后续优先级更低的匹配器 -Args: - - rule: 规则 - - priority: 优先级 >= 0 - - block: 是否阻断后续优先级更低的匹配器
-源代码 + Source code ```python def __init__(self, rule: Rule, priority: int, block: bool): @@ -40,32 +33,65 @@ def __init__(self, rule: Rule, priority: int, block: bool): ```
-###   ***def*** `handle(self, handler: EventHandler) -> EventHandler` +### *method* `handle(self) -> Callable[[EventHandler], EventHandler]` - 添加处理函数,装饰器 -Args: - handler: +**Description**: 添加处理函数,装饰器 -Returns: +**Return**: 装饰器 handler - EventHandler
-源代码 + Source code ```python -def handle(self, handler: EventHandler) -> EventHandler: +def handle(self) -> Callable[[EventHandler], EventHandler]: """ 添加处理函数,装饰器 - Args: - handler: Returns: - EventHandler + 装饰器 handler """ - self.handlers.append(handler) - return handler + + def decorator(handler: EventHandler) -> EventHandler: + self.handlers.append(handler) + return handler + return decorator ```
+### *async method* `run(self, event: MessageEvent) -> None` + + + +**Description**: 运行处理函数 + +**Arguments**: +> - event: + + +
+ Source code + +```python +async def run(self, event: MessageEvent) -> None: + """ + 运行处理函数 + Args: + event: + Returns: + """ + if not await self.rule(event): + return + for handler in self.handlers: + try: + await handler(event) + except Exception: + traceback.print_exc() +``` +
+ +### ***var*** `EventHandler = Callable[[MessageEvent], Coroutine[None, None, Any]]` + +- **Type**: `TypeAlias` + diff --git a/docs/en/dev/api/message/on.md b/docs/en/dev/api/message/on.md index 4f3c7230..1324ae50 100644 --- a/docs/en/dev/api/message/on.md +++ b/docs/en/dev/api/message/on.md @@ -1,19 +1,14 @@ --- title: liteyuki.message.on -order: 1 -icon: laptop-code -category: API --- - -### ***def*** `on_message(rule: Rule, priority: int, block: bool) -> Matcher` - +### *func* `on_message(rule: Rule = empty_rule, priority: int = 0, block: bool = False) -> Matcher`
-源代码 + Source code ```python -def on_message(rule: Rule=Rule(), priority: int=0, block: bool=True) -> Matcher: +def on_message(rule: Rule=empty_rule, priority: int=0, block: bool=False) -> Matcher: matcher = Matcher(rule, priority, block) for i, m in enumerate(_matcher_list): if m.priority < matcher.priority: @@ -25,15 +20,27 @@ def on_message(rule: Rule=Rule(), priority: int=0, block: bool=True) -> Matcher: ```
-### ***var*** `current_priority = -1` +### *func* `on_keywords(keywords: list[str] = empty_rule, rule = 0, priority: int = False) -> Matcher` +
+ Source code -### ***var*** `matcher = Matcher(rule, priority, block)` +```python +def on_keywords(keywords: list[str], rule=empty_rule, priority: int=0, block: bool=False) -> Matcher: + @Rule + async def on_keywords_rule(event: MessageEvent): + return any((keyword in event.raw_message for keyword in keywords)) + return on_message(on_keywords_rule & rule, priority, block) +``` +
+### ***var*** `_matcher_list = []` -### ***var*** `current_priority = matcher.priority` +- **Type**: `list[Matcher]` +### ***var*** `_queue = Queue()` +- **Type**: `Queue` diff --git a/docs/en/dev/api/message/rule.md b/docs/en/dev/api/message/rule.md index a135f740..729449bc 100644 --- a/docs/en/dev/api/message/rule.md +++ b/docs/en/dev/api/message/rule.md @@ -1,24 +1,98 @@ --- title: liteyuki.message.rule -order: 1 -icon: laptop-code -category: API --- +### `@Rule` +### *async func* `empty_rule() -> bool` -### ***class*** `Rule` - - - -###   ***def*** `__init__(self, handler: Optional[RuleHandler]) -> None` - - 
-源代码 + Source code ```python -def __init__(self, handler: Optional[RuleHandler]=None): +@Rule +async def empty_rule(event: MessageEvent) -> bool: + return True +``` +
+ +### `@Rule` +### *async func* `is_su_rule() -> bool` + + +
+ Source code + +```python +@Rule +async def is_su_rule(event: MessageEvent) -> bool: + return str(event.user_id) in _superusers +``` +
+ +### **class** `Rule` +### *method* `__init__(self, handler: RuleHandlerFunc)` + + +
+ Source code + +```python +def __init__(self, handler: RuleHandlerFunc): self.handler = handler ```
+### *method* `__or__(self, other: Rule) -> Rule` + + +
+ Source code + +```python +def __or__(self, other: 'Rule') -> 'Rule': + + async def combined_handler(event: MessageEvent) -> bool: + return await self.handler(event) or await other.handler(event) + return Rule(combined_handler) +``` +
+ +### *method* `__and__(self, other: Rule) -> Rule` + + +
+ Source code + +```python +def __and__(self, other: 'Rule') -> 'Rule': + + async def combined_handler(event: MessageEvent) -> bool: + return await self.handler(event) and await other.handler(event) + return Rule(combined_handler) +``` +
+ +### *async method* `__call__(self, event: MessageEvent) -> bool` + + +
+ Source code + +```python +async def __call__(self, event: MessageEvent) -> bool: + if self.handler is None: + return True + return await self.handler(event) +``` +
+ +### ***var*** `_superusers = get_config('liteyuki.superusers', [])` + +- **Type**: `list[str]` + +### ***var*** `RuleHandlerFunc = Callable[[MessageEvent], Coroutine[None, None, bool]]` + +- **Type**: `TypeAlias` + +- **Description**: 规则函数签名 + diff --git a/docs/en/dev/api/message/session.md b/docs/en/dev/api/message/session.md index f8fcad6f..28c3e5c9 100644 --- a/docs/en/dev/api/message/session.md +++ b/docs/en/dev/api/message/session.md @@ -1,7 +1,3 @@ --- title: liteyuki.message.session -order: 1 -icon: laptop-code -category: API --- - diff --git a/docs/en/dev/api/mkdoc.md b/docs/en/dev/api/mkdoc.md index 802aff3c..04056a88 100644 --- a/docs/en/dev/api/mkdoc.md +++ b/docs/en/dev/api/mkdoc.md @@ -1,22 +1,19 @@ --- title: liteyuki.mkdoc -order: 1 -icon: laptop-code -category: API --- +### *func* `get_relative_path() -> str` -### ***def*** `get_relative_path(base_path: str, target_path: str) -> str` -获取相对路径 -Args: +**Description**: 获取相对路径 - base_path: 基础路径 +**Arguments**: +> - base_path: 基础路径 +> - target_path: 目标路径 - target_path: 目标路径
-源代码 + Source code ```python def get_relative_path(base_path: str, target_path: str) -> str: @@ -30,16 +27,18 @@ def get_relative_path(base_path: str, target_path: str) -> str: ```
-### ***def*** `write_to_files(file_data: dict[str, str]) -> None` +### *func* `write_to_files()` -输出文件 -Args: - file_data: 文件数据 相对路径 +**Description**: 输出文件 + +**Arguments**: +> - file_data: 文件数据 相对路径 +
-源代码 + Source code ```python def write_to_files(file_data: dict[str, str]): @@ -56,12 +55,11 @@ def write_to_files(file_data: dict[str, str]): ```
-### ***def*** `get_file_list(module_folder: str) -> None` - +### *func* `get_file_list()`
-源代码 + Source code ```python def get_file_list(module_folder: str): @@ -74,22 +72,21 @@ def get_file_list(module_folder: str): ```
-### ***def*** `get_module_info_normal(file_path: str, ignore_private: bool) -> ModuleInfo` +### *func* `get_module_info_normal(file_path: str = True) -> ModuleInfo` -获取函数和类 -Args: - file_path: Python 文件路径 +**Description**: 获取函数和类 - ignore_private: 忽略私有函数和类 +**Arguments**: +> - file_path: Python 文件路径 +> - ignore_private: 忽略私有函数和类 -Returns: +**Return**: 模块信息 - 模块信息
-源代码 + Source code ```python def get_module_info_normal(file_path: str, ignore_private: bool=True) -> ModuleInfo: @@ -149,33 +146,33 @@ def get_module_info_normal(file_path: str, ignore_private: bool=True) -> ModuleI ```
-### ***def*** `generate_markdown(module_info: ModuleInfo, front_matter: Any) -> str` +### *func* `generate_markdown(module_info: ModuleInfo = None, front_matter = 'zh-CN') -> str` -生成模块的Markdown + +**Description**: 生成模块的Markdown 你可在此自定义生成的Markdown格式 -Args: +**Arguments**: +> - module_info: 模块信息 +> - front_matter: 自定义选项title, index, icon, category +> - lang: 语言 - module_info: 模块信息 +**Return**: Markdown 字符串 - front_matter: 自定义选项title, index, icon, category - -Returns: - - Markdown 字符串
-源代码 + Source code ```python -def generate_markdown(module_info: ModuleInfo, front_matter=None) -> str: +def generate_markdown(module_info: ModuleInfo, front_matter=None, lang: str='zh-CN') -> str: """ 生成模块的Markdown 你可在此自定义生成的Markdown格式 Args: module_info: 模块信息 front_matter: 自定义选项title, index, icon, category + lang: 语言 Returns: Markdown 字符串 """ @@ -205,7 +202,11 @@ def generate_markdown(module_info: ModuleInfo, front_matter=None) -> str: content += f"###   ***{('async ' if method.is_async else '')}def*** `{method.name}({', '.join(args_with_type)}) -> {method.return_type}`\n\n" method.docstring = method.docstring.replace('\n', '\n\n') content += f' {method.docstring}\n\n' - content += f'
\n源代码\n\n```python\n{method.source_code}\n```\n
\n\n' + if lang == 'zh-CN': + TEXT_SOURCE_CODE = '源代码' + else: + TEXT_SOURCE_CODE = 'Source Code' + content += f'
\n{TEXT_SOURCE_CODE}\n\n```python\n{method.source_code}\n```\n
\n\n' for attr in cls.attributes: content += f'###   ***attr*** `{attr.name}: {attr.type}`\n\n' for attr in module_info.attributes: @@ -219,25 +220,25 @@ def generate_markdown(module_info: ModuleInfo, front_matter=None) -> str: ```
-### ***def*** `generate_docs(module_folder: str, output_dir: str, with_top: bool, ignored_paths: Any) -> None` +### *func* `generate_docs(module_folder: str = False, output_dir: str = 'zh-CN', with_top: bool = None)` -生成文档 -Args: - module_folder: 模块文件夹 +**Description**: 生成文档 - output_dir: 输出文件夹 +**Arguments**: +> - module_folder: 模块文件夹 +> - output_dir: 输出文件夹 +> - with_top: 是否包含顶层文件夹 False时例如docs/api/module_a, docs/api/module_b, True时例如docs/api/module/module_a.md, docs/api/module/module_b.md +> - ignored_paths: 忽略的路径 +> - lang: 语言 - with_top: 是否包含顶层文件夹 False时例如docs/api/module_a, docs/api/module_b, True时例如docs/api/module/module_a.md, docs/api/module/module_b.md - - ignored_paths: 忽略的路径
-源代码 + Source code ```python -def generate_docs(module_folder: str, output_dir: str, with_top: bool=False, ignored_paths=None): +def generate_docs(module_folder: str, output_dir: str, with_top: bool=False, lang: str='zh-CN', ignored_paths=None): """ 生成文档 Args: @@ -245,6 +246,7 @@ def generate_docs(module_folder: str, output_dir: str, with_top: bool=False, ign output_dir: 输出文件夹 with_top: 是否包含顶层文件夹 False时例如docs/api/module_a, docs/api/module_b, True时例如docs/api/module/module_a.md, docs/api/module/module_b.md ignored_paths: 忽略的路径 + lang: 语言 """ if ignored_paths is None: ignored_paths = [] @@ -273,201 +275,8 @@ def generate_docs(module_folder: str, output_dir: str, with_top: bool=False, ign ```
-### ***class*** `DefType(Enum)` - - - -###   ***attr*** `FUNCTION: 'function'` - -###   ***attr*** `METHOD: 'method'` - -###   ***attr*** `STATIC_METHOD: 'staticmethod'` - -###   ***attr*** `CLASS_METHOD: 'classmethod'` - -###   ***attr*** `PROPERTY: 'property'` - -### ***class*** `FunctionInfo(BaseModel)` - - - -### ***class*** `AttributeInfo(BaseModel)` - - - -### ***class*** `ClassInfo(BaseModel)` - - - -### ***class*** `ModuleInfo(BaseModel)` - - - -### ***var*** `NO_TYPE_ANY = 'Any'` - - - -### ***var*** `NO_TYPE_HINT = 'NoTypeHint'` - - - -### ***var*** `FUNCTION = 'function'` - - - -### ***var*** `METHOD = 'method'` - - - -### ***var*** `STATIC_METHOD = 'staticmethod'` - - - -### ***var*** `CLASS_METHOD = 'classmethod'` - - - -### ***var*** `PROPERTY = 'property'` - - - -### ***var*** `file_list = []` - - - -### ***var*** `dot_sep_module_path = file_path.replace(os.sep, '.').replace('.py', '').replace('.pyi', '')` - - - -### ***var*** `module_docstring = ast.get_docstring(tree)` - - - -### ***var*** `module_info = ModuleInfo(module_path=dot_sep_module_path, functions=[], classes=[], attributes=[], docstring=module_docstring if module_docstring else '')` - - - -### ***var*** `content = ''` - - - -### ***var*** `front_matter = '---\n' + '\n'.join([f'{k}: {v}' for k, v in front_matter.items()]) + '\n---\n\n'` - - - -### ***var*** `file_list = get_file_list(module_folder)` - - - -### ***var*** `replace_data = {'__init__': 'README', '.py': '.md'}` - - - -### ***var*** `file_content = file.read()` - - - -### ***var*** `tree = ast.parse(file_content)` - - - -### ***var*** `args_with_type = [f'{arg[0]}: {arg[1]}' if arg[1] else arg[0] for arg in func.args]` - - - -### ***var*** `ignored_paths = []` - - - -### ***var*** `no_module_name_pyfile_path = get_relative_path(module_folder, pyfile_path)` - - - -### ***var*** `rel_md_path = pyfile_path if with_top else no_module_name_pyfile_path` - - - -### ***var*** `abs_md_path = os.path.join(output_dir, rel_md_path)` - - - -### ***var*** `module_info = get_module_info_normal(pyfile_path)` - - - -### ***var*** `md_content = generate_markdown(module_info, front_matter)` - - - -### ***var*** `inherit = f"({', '.join(cls.inherit)})" if cls.inherit else ''` - - - -### ***var*** `rel_md_path = rel_md_path.replace(rk, rv)` - - - -### ***var*** `front_matter = {'title': module_info.module_path.replace('.__init__', '').replace('_', '\\n'), 'index': 'true', 'icon': 'laptop-code', 'category': 'API'}` - - - -### ***var*** `front_matter = {'title': module_info.module_path.replace('_', '\\n'), 'order': '1', 'icon': 'laptop-code', 'category': 'API'}` - - - -### ***var*** `function_docstring = ast.get_docstring(node)` - - - -### ***var*** `func_info = FunctionInfo(name=node.name, args=[(arg.arg, ast.unparse(arg.annotation) if arg.annotation else NO_TYPE_ANY) for arg in node.args.args], return_type=ast.unparse(node.returns) if node.returns else 'None', docstring=function_docstring if function_docstring else '', type=DefType.FUNCTION, is_async=isinstance(node, ast.AsyncFunctionDef), source_code=ast.unparse(node))` - - - -### ***var*** `class_docstring = ast.get_docstring(node)` - - - -### ***var*** `class_info = ClassInfo(name=node.name, docstring=class_docstring if class_docstring else '', methods=[], attributes=[], inherit=[ast.unparse(base) for base in node.bases])` - - - -### ***var*** `args_with_type = [f'{arg[0]}: {arg[1]}' if arg[1] else arg[0] for arg in method.args]` - - - -### ***var*** `args_with_type = [f'{arg[0]}: {arg[1]}' if arg[1] and arg[0] != 'self' else arg[0] for arg in method.args]` - - - -### ***var*** `first_arg = node.args.args[0]` - - - -### ***var*** `method_docstring = ast.get_docstring(class_node)` - - - -### ***var*** `def_type = DefType.METHOD` - - - -### ***var*** `def_type = DefType.STATIC_METHOD` - - - -### ***var*** `attr_type = NO_TYPE_HINT` - - - -### ***var*** `def_type = DefType.CLASS_METHOD` - - - -### ***var*** `attr_type = ast.unparse(node.value.annotation)` - - - -### ***var*** `def_type = DefType.PROPERTY` - - - +### **class** `DefType(Enum)` +### **class** `FunctionInfo(BaseModel)` +### **class** `AttributeInfo(BaseModel)` +### **class** `ClassInfo(BaseModel)` +### **class** `ModuleInfo(BaseModel)` diff --git a/docs/en/dev/api/plugin/README.md b/docs/en/dev/api/plugin/README.md index 6c67f485..a4f34cfa 100644 --- a/docs/en/dev/api/plugin/README.md +++ b/docs/en/dev/api/plugin/README.md @@ -1,20 +1,17 @@ --- title: liteyuki.plugin -index: true -icon: laptop-code -category: API --- +### *func* `get_loaded_plugins() -> dict[str, Plugin]` -### ***def*** `get_loaded_plugins() -> dict[str, Plugin]` -获取已加载的插件 -Returns: +**Description**: 获取已加载的插件 + +**Return**: dict[str, Plugin]: 插件字典 - dict[str, Plugin]: 插件字典
-源代码 + Source code ```python def get_loaded_plugins() -> dict[str, Plugin]: diff --git a/docs/en/dev/api/plugin/load.md b/docs/en/dev/api/plugin/load.md index b999f5e1..50fb73f8 100644 --- a/docs/en/dev/api/plugin/load.md +++ b/docs/en/dev/api/plugin/load.md @@ -1,24 +1,20 @@ --- title: liteyuki.plugin.load -order: 1 -icon: laptop-code -category: API --- - -### ***def*** `load_plugin(module_path: str | Path) -> Optional[Plugin]` - -加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。 +### *func* `load_plugin() -> Optional[Plugin]` -参数: +**Description**: 加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。 - module_path: 插件名称 `path.to.your.plugin` - 或插件路径 `pathlib.Path(path/to/your/plugin)` +**Arguments**: +> - module_path: 插件名称 `path.to.your.plugin` +> - 或插件路径 `pathlib.Path(path/to/your/plugin)`: +
-源代码 + Source code ```python def load_plugin(module_path: str | Path) -> Optional[Plugin]: @@ -31,11 +27,21 @@ def load_plugin(module_path: str | Path) -> Optional[Plugin]: module_path = path_to_module_name(Path(module_path)) if isinstance(module_path, Path) else module_path try: module = import_module(module_path) - _plugins[module.__name__] = Plugin(name=module.__name__, module=module, module_name=module_path, metadata=module.__dict__.get('__plugin_metadata__', None)) - display_name = module.__name__.split('.')[-1] - if module.__dict__.get('__plugin_meta__'): + _plugins[module.__name__] = Plugin(name=module.__name__, module=module, module_name=module_path) + if module.__dict__.get('__plugin_metadata__', None): + metadata: 'PluginMetadata' = module.__dict__['__plugin_metadata__'] + display_name = module.__name__.split('.')[-1] + elif module.__dict__.get('__liteyuki_plugin_meta__', None): + metadata: 'PluginMetadata' = module.__dict__['__liteyuki_plugin_meta__'] + display_name = format_display_name(f"{metadata.name}({module.__name__.split('.')[-1]})", metadata.type) + elif module.__dict__.get('__plugin_meta__', None): metadata: 'PluginMetadata' = module.__dict__['__plugin_meta__'] display_name = format_display_name(f"{metadata.name}({module.__name__.split('.')[-1]})", metadata.type) + else: + logger.opt(colors=True).warning(f'The metadata of Liteyuki plugin "{module.__name__}" is not specified, use empty.') + metadata = PluginMetadata(name=module.__name__) + display_name = module.__name__.split('.')[-1] + _plugins[module.__name__].metadata = metadata logger.opt(colors=True).success(f'Succeeded to load liteyuki plugin "{display_name}"') return _plugins[module.__name__] except Exception as e: @@ -45,20 +51,20 @@ def load_plugin(module_path: str | Path) -> Optional[Plugin]: ```
-### ***def*** `load_plugins() -> set[Plugin]` - -导入文件夹下多个插件 +### *func* `load_plugins(*, ignore_warning: bool = True) -> set[Plugin]` -参数: +**Description**: 导入文件夹下多个插件 - plugin_dir: 文件夹路径 - ignore_warning: 是否忽略警告,通常是目录不存在或目录为空 +**Arguments**: +> - plugin_dir: 文件夹路径 +> - ignore_warning: 是否忽略警告,通常是目录不存在或目录为空 +
-源代码 + Source code ```python def load_plugins(*plugin_dir: str, ignore_warning: bool=True) -> set[Plugin]: @@ -97,24 +103,21 @@ def load_plugins(*plugin_dir: str, ignore_warning: bool=True) -> set[Plugin]: ```
-### ***def*** `format_display_name(display_name: str, plugin_type: PluginType) -> str` - -设置插件名称颜色,根据不同类型插件设置颜色 - -Args: - - display_name: 插件名称 - - plugin_type: 插件类型 +### *func* `format_display_name() -> str` -Returns: +**Description**: 设置插件名称颜色,根据不同类型插件设置颜色 + +**Arguments**: +> - display_name: 插件名称 +> - plugin_type: 插件类型 + +**Return**: str: 设置后的插件名称 name - str: 设置后的插件名称 name
-源代码 + Source code ```python def format_display_name(display_name: str, plugin_type: PluginType) -> str: @@ -141,59 +144,19 @@ def format_display_name(display_name: str, plugin_type: PluginType) -> str: ```
-### ***var*** `module_path = path_to_module_name(Path(module_path)) if isinstance(module_path, Path) else module_path` +### ***var*** `_plugins = {}` +- **Type**: `dict[str, Plugin]` +### ***var*** `metadata = module.__dict__['__plugin_metadata__']` -### ***var*** `plugins = set()` +- **Type**: `'PluginMetadata'` +### ***var*** `metadata = module.__dict__['__liteyuki_plugin_meta__']` +- **Type**: `'PluginMetadata'` -### ***var*** `color = 'y'` - - - -### ***var*** `module = import_module(module_path)` - - - -### ***var*** `display_name = module.__name__.split('.')[-1]` - - - -### ***var*** `display_name = format_display_name(f"{metadata.name}({module.__name__.split('.')[-1]})", metadata.type)` - - - -### ***var*** `path = Path(os.path.join(dir_path, f))` - - - -### ***var*** `module_name = None` - - - -### ***var*** `color = 'm'` - - - -### ***var*** `color = 'g'` - - - -### ***var*** `color = 'e'` - - - -### ***var*** `color = 'c'` - - - -### ***var*** `module_name = f'{path_to_module_name(Path(dir_path))}.{f[:-3]}'` - - - -### ***var*** `module_name = path_to_module_name(path)` - +### ***var*** `metadata = module.__dict__['__plugin_meta__']` +- **Type**: `'PluginMetadata'` diff --git a/docs/en/dev/api/plugin/manager.md b/docs/en/dev/api/plugin/manager.md index 7d4d951c..ce7983da 100644 --- a/docs/en/dev/api/plugin/manager.md +++ b/docs/en/dev/api/plugin/manager.md @@ -1,7 +1,3 @@ --- title: liteyuki.plugin.manager -order: 1 -icon: laptop-code -category: API --- - diff --git a/docs/en/dev/api/plugin/model.md b/docs/en/dev/api/plugin/model.md index 6f986049..81ad7fbd 100644 --- a/docs/en/dev/api/plugin/model.md +++ b/docs/en/dev/api/plugin/model.md @@ -1,89 +1,18 @@ --- title: liteyuki.plugin.model -order: 1 -icon: laptop-code -category: API --- - -### ***class*** `PluginType(Enum)` - -插件类型枚举值 - -###   ***attr*** `APPLICATION: 'application'` - -###   ***attr*** `SERVICE: 'service'` - -###   ***attr*** `MODULE: 'module'` - -###   ***attr*** `UNCLASSIFIED: 'unclassified'` - -###   ***attr*** `TEST: 'test'` - -### ***class*** `PluginMetadata(BaseModel)` - -轻雪插件元数据,由插件编写者提供,name为必填项 - -Attributes: - ----------- +### **class** `PluginType(Enum)` +### **class** `PluginMetadata(BaseModel)` +### **class** `Plugin(BaseModel)` +### *method* `__hash__(self)` +
+ Source code -name: str - - 插件名称 - -description: str - - 插件描述 - -usage: str - - 插件使用方法 - -type: str - - 插件类型 - -author: str - - 插件作者 - -homepage: str - - 插件主页 - -extra: dict[str, Any] - - 额外信息 - -### ***class*** `Plugin(BaseModel)` - -存储插件信息 - -###   ***attr*** `model_config: {'arbitrary_types_allowed': True}` - -### ***var*** `APPLICATION = 'application'` - - - -### ***var*** `SERVICE = 'service'` - - - -### ***var*** `MODULE = 'module'` - - - -### ***var*** `UNCLASSIFIED = 'unclassified'` - - - -### ***var*** `TEST = 'test'` - - - -### ***var*** `model_config = {'arbitrary_types_allowed': True}` - - +```python +def __hash__(self): + return hash(self.module_name) +``` +
diff --git a/docs/en/dev/api/plugins/liteecho.md b/docs/en/dev/api/plugins/liteecho.md new file mode 100644 index 00000000..e4eba49c --- /dev/null +++ b/docs/en/dev/api/plugins/liteecho.md @@ -0,0 +1,17 @@ +--- +title: liteyuki.plugins.liteecho +--- +### `@on_startswith(['liteecho'], rule=is_su_rule).handle()` +### *async func* `liteecho()` + + +
+ Source code + +```python +@on_startswith(['liteecho'], rule=is_su_rule).handle() +async def liteecho(event: MessageEvent): + event.reply(event.raw_message.strip()[8:].strip()) +``` +
+ diff --git a/docs/en/dev/api/plugins/plugin_loader/README.md b/docs/en/dev/api/plugins/plugin_loader/README.md new file mode 100644 index 00000000..76f8278f --- /dev/null +++ b/docs/en/dev/api/plugins/plugin_loader/README.md @@ -0,0 +1,25 @@ +--- +title: liteyuki.plugins.plugin_loader +--- +### *func* `default_plugins_loader()` + + + +**Description**: 默认插件加载器,应在初始化时调用 + + +
+ Source code + +```python +def default_plugins_loader(): + """ + 默认插件加载器,应在初始化时调用 + """ + for plugin in get_config('liteyuki.plugins', []): + load_plugin(plugin) + for plugin_dir in get_config('liteyuki.plugin_dirs', ['src/liteyuki_plugins']): + load_plugins(plugin_dir) +``` +
+ diff --git a/docs/en/dev/api/utils.md b/docs/en/dev/api/utils.md index 84532f46..ea4ea63b 100644 --- a/docs/en/dev/api/utils.md +++ b/docs/en/dev/api/utils.md @@ -1,24 +1,20 @@ --- title: liteyuki.utils -order: 1 -icon: laptop-code -category: API --- +### *func* `is_coroutine_callable() -> bool` -### ***def*** `is_coroutine_callable(call: Callable[..., Any]) -> bool` -判断是否为协程可调用对象 -Args: +**Description**: 判断是否为协程可调用对象 - call: 可调用对象 +**Arguments**: +> - call: 可调用对象 -Returns: +**Return**: bool: 是否为协程可调用对象 - bool: 是否为协程可调用对象
-源代码 + Source code ```python def is_coroutine_callable(call: Callable[..., Any]) -> bool: @@ -38,20 +34,18 @@ def is_coroutine_callable(call: Callable[..., Any]) -> bool: ```
-### ***def*** `run_coroutine() -> None` - -运行协程 - -Args: - - coro: +### *func* `run_coroutine()` -Returns: +**Description**: 运行协程 + +**Arguments**: +> - coro: +
-源代码 + Source code ```python def run_coroutine(*coro: Coroutine): @@ -81,20 +75,47 @@ def run_coroutine(*coro: Coroutine): ```
-### ***def*** `path_to_module_name(path: Path) -> str` +### *func* `run_coroutine_in_thread()` -转换路径为模块名 -Args: - path: 路径a/b/c/d -> a.b.c.d +**Description**: 在新线程中运行协程 -Returns: +**Arguments**: +> - coro: - str: 模块名
-源代码 + Source code + +```python +def run_coroutine_in_thread(*coro: Coroutine): + """ + 在新线程中运行协程 + Args: + coro: + + Returns: + + """ + threading.Thread(target=run_coroutine, args=coro, daemon=True).start() +``` +
+ +### *func* `path_to_module_name() -> str` + + + +**Description**: 转换路径为模块名 + +**Arguments**: +> - path: 路径a/b/c/d -> a.b.c.d + +**Return**: str: 模块名 + + +
+ Source code ```python def path_to_module_name(path: Path) -> str: @@ -113,20 +134,20 @@ def path_to_module_name(path: Path) -> str: ```
-### ***def*** `async_wrapper(func: Callable[..., Any]) -> Callable[..., Coroutine]` +### *func* `async_wrapper() -> Callable[..., Coroutine]` -异步包装器 -Args: - func: Sync Callable +**Description**: 异步包装器 -Returns: +**Arguments**: +> - func: Sync Callable + +**Return**: Coroutine: Asynchronous Callable - Coroutine: Asynchronous Callable
-源代码 + Source code ```python def async_wrapper(func: Callable[..., Any]) -> Callable[..., Coroutine]: @@ -145,36 +166,3 @@ def async_wrapper(func: Callable[..., Any]) -> Callable[..., Coroutine]: ```
-### ***async def*** `wrapper() -> None` - - - -
-源代码 - -```python -async def wrapper(*args, **kwargs): - return func(*args, **kwargs) -``` -
- -### ***var*** `IS_MAIN_PROCESS = multiprocessing.current_process().name == 'MainProcess'` - - - -### ***var*** `func_ = getattr(call, '__call__', None)` - - - -### ***var*** `rel_path = path.resolve().relative_to(Path.cwd().resolve())` - - - -### ***var*** `loop = asyncio.get_event_loop()` - - - -### ***var*** `loop = asyncio.new_event_loop()` - - - diff --git a/litedoc/__init__.py b/litedoc/__init__.py new file mode 100644 index 00000000..31a97612 --- /dev/null +++ b/litedoc/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午12:52 +@Author : snowykami +@Email : snowykami@outlook.com +@File : __init__.py.py +@Software: PyCharm +""" \ No newline at end of file diff --git a/litedoc/__main__.py b/litedoc/__main__.py new file mode 100644 index 00000000..db92b7b9 --- /dev/null +++ b/litedoc/__main__.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午4:08 +@Author : snowykami +@Email : snowykami@outlook.com +@File : __main__.py +@Software: PyCharm +""" +# command line tool +# args[0] path +# -o|--output output path +# -l|--lang zh-Hans en jp default zh-Hans + + +import argparse +import os +import sys + +from litedoc.output import generate_from_module + + +def main(): + parser = argparse.ArgumentParser(description="Generate documentation from Python modules.") + parser.add_argument("path", type=str, help="Path to the Python module or package.") + parser.add_argument("-o", "--output", default="doc-output", type=str, help="Output directory.") + parser.add_argument("-c", "--contain-top", action="store_true", help="Whether to contain top-level dir in output dir.") + parser.add_argument("-l", "--lang", default="zh_Hans", type=str, help="Languages of the document.") + parser.add_argument("-t", "--theme", default="vitepress", type=str, help="Theme of the document.") + parser.add_argument("-s", "--style", default="google", type=str, help="Style of the document.") + + args = parser.parse_args() + + if not os.path.exists(args.path): + print(f"Error: The path {args.path} does not exist.") + sys.exit(1) + + if not os.path.exists(args.output): + os.makedirs(args.output) + + lang = args.lang + + generate_from_module(args.path, args.output, with_top=args.contain_top, lang=lang, theme=args.theme, style=args.style) + + +if __name__ == '__main__': + main() diff --git a/litedoc/docstring/__init__.py b/litedoc/docstring/__init__.py new file mode 100644 index 00000000..323b116a --- /dev/null +++ b/litedoc/docstring/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午1:46 +@Author : snowykami +@Email : snowykami@outlook.com +@File : __init__.py.py +@Software: PyCharm +""" \ No newline at end of file diff --git a/litedoc/docstring/docstring.py b/litedoc/docstring/docstring.py new file mode 100644 index 00000000..4b3344cf --- /dev/null +++ b/litedoc/docstring/docstring.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午1:46 +@Author : snowykami +@Email : snowykami@outlook.com +@File : docstring.py +@Software: PyCharm +""" +from typing import Optional + +from pydantic import BaseModel, Field + +from litedoc.i18n import get_text + + +class Attr(BaseModel): + name: str + type: str = "" + desc: str = "" + + +class Args(BaseModel): + name: str + type: str = "" + desc: str = "" + + +class Return(BaseModel): + desc: str = "" + + +class Exception_(BaseModel): + name: str + desc: str = "" + + +class Raise(BaseModel): + exceptions: list[Exception_] = [] + + +class Example(BaseModel): + desc: str = "" + input: str = "" + output: str = "" + + +class Docstring(BaseModel): + desc: str = "" + args: list[Args] = [] + attrs: list[Attr] = [] + return_: Optional[Return] = None + raise_: list[Exception_] = [] + example: list[Example] = [] + + def add_desc(self, desc: str): + if self.desc == "": + self.desc = desc + else: + self.desc += "\n" + desc + + def add_arg(self, name: str, type_: str = "", desc: str = ""): + self.args.append(Args(name=name, type=type_, desc=desc)) + + def add_attrs(self, name: str, type_: str = "", desc: str = ""): + self.attrs.append(Attr(name=name, type=type_, desc=desc)) + + def add_return(self, desc: str = ""): + self.return_ = Return(desc=desc) + + def add_raise(self, name: str, desc: str = ""): + self.raise_.append(Exception_(name=name, desc=desc)) + + def add_example(self, desc: str = "", input_: str = "", output: str = ""): + self.example.append(Example(desc=desc, input=input_, output=output)) + + def reduction(self) -> str: + """ + 通过解析结果还原docstring + Args: + + Returns: + + """ + ret = "" + ret += self.desc + "\n" + if self.args: + ret += "Args:\n" + for arg in self.args: + ret += f" {arg.name}: {arg.type}\n {arg.desc}\n" + if self.attrs: + ret += "Attributes:\n" + for attr in self.attrs: + ret += f" {attr.name}: {attr.type}\n {attr.desc}\n" + if self.return_: + ret += "Returns:\n" + ret += f" {self.return_.desc}\n" + + if self.raise_: + ret += "Raises:\n" + for exception in self.raise_: + ret += f" {exception.name}\n {exception.desc}\n" + + if self.example: + ret += "Examples:\n" + for example in self.example: + ret += f" {example.desc}\n Input: {example.input}\n Output: {example.output}\n" + + return ret + + def markdown(self, lang: str, indent: int = 4, is_classmethod: bool = False) -> str: + """ + 生成markdown文档 + Args: + is_classmethod: + lang: + indent: + + Returns: + + """ + PREFIX = "" * indent + ret = "" + # ret += self.desc + "\n\n" + # print(self.reduction()) + # print(self.desc, self.return_) + # 单数属性 + if self.desc: + ret += PREFIX + f"\n**{get_text(lang, 'desc')}**: {self.desc}\n" + + # 复数属性 + if self.args: + ret += PREFIX + f"\n**{get_text(lang, 'docstring.args')}**:\n" + for arg in self.args: + ret += PREFIX + f"> - {arg.name}: {arg.type} {arg.desc}\n" + if self.attrs: + ret += PREFIX + f"\n**{get_text(lang, 'docstring.attrs')}**:\n" + for attr in self.attrs: + ret += PREFIX + f"> - {attr.name}: {attr.type} {attr.desc}\n" + + # 单数属性 + if self.return_ is not None: + ret += PREFIX + f"\n**{get_text(lang, 'docstring.return')}**: {self.return_.desc}\n" + # 复数属性 + if self.raise_: + ret += PREFIX + f"\n**{get_text(lang, 'docstring.raises')}**:\n" + for exception in self.raise_: + ret += PREFIX + f"> - {exception.name} {exception.desc}\n" + if self.example: + ret += PREFIX + f"\n**{get_text(lang, 'docstring.example')}**:\n" + for example in self.example: + ret += PREFIX + f" - {example.desc}\n> **{get_text(lang, 'docs.input')}**: {example.input}\n> **{get_text(lang, 'docs.output')}**: {example.output}\n" + return ret + + def __str__(self): + return self.desc diff --git a/litedoc/docstring/parser.py b/litedoc/docstring/parser.py new file mode 100644 index 00000000..24b7c20a --- /dev/null +++ b/litedoc/docstring/parser.py @@ -0,0 +1,192 @@ +""" +Google docstring parser for Python. +""" +from typing import Optional + +from litedoc.docstring.docstring import Docstring + + +class Parser: + ... + + +class GoogleDocstringParser(Parser): + _tokens = { + "Args" : "args", + "Arguments" : "args", + "参数" : "args", + + "Return" : "return", + "Returns" : "return", + "返回" : "return", + + "Attribute" : "attribute", + "Attributes" : "attribute", + "属性" : "attribute", + + "Raises" : "raises", + "Raise" : "raises", + "引发" : "raises", + + "Example" : "example", + "Examples" : "example", + "示例" : "example", + + "Yields" : "yields", + "Yield" : "yields", + "产出" : "yields", + + "Requires" : "requires", + "Require" : "requires", + "需要" : "requires", + + "FrontMatter": "front_matter", + "前言" : "front_matter", + } + + def __init__(self, docstring: str, indent: int = 4): + self.lines = docstring.splitlines() + self.indent = indent + self.lineno = 0 # Current line number + self.char = 0 # Current character position + + self.docstring = Docstring() + + def read_line(self, move: bool = True) -> str: + """ + 每次读取一行 + Args: + move: 是否移动指针 + Returns: + """ + if self.lineno >= len(self.lines): + return "" + line = self.lines[self.lineno] + if move: + self.lineno += 1 + return line + + def match_token(self) -> Optional[str]: + """ + 解析下一行的token + Returns: + + """ + for token in self._tokens: + line = self.read_line(move=False) + if line.strip().startswith(token): + self.lineno += 1 + return self._tokens[token] + return None + + def parse_args(self): + """ + 依次解析后面的参数行,直到缩进小于等于当前行的缩进 + """ + while line := self.match_next_line(): + if ":" in line: + name, desc = line.split(":", 1) + self.docstring.add_arg(name.strip(), desc.strip()) + else: + self.docstring.add_arg(line.strip()) + + def parse_return(self): + """ + 解析返回值行 + """ + if line := self.match_next_line(): + self.docstring.add_return(line.strip()) + + def parse_raises(self): + """ + 解析异常行 + """ + while line := self.match_next_line(): + if ":" in line: + name, desc = line.split(":", 1) + self.docstring.add_raise(name.strip(), desc.strip()) + else: + self.docstring.add_raise(line.strip()) + + def parse_example(self): + """ + 解析示例行 + """ + while line := self.match_next_line(): + if ":" in line: + name, desc = line.split(":", 1) + self.docstring.add_example(name.strip(), desc.strip()) + else: + self.docstring.add_example(line.strip()) + + def parse_attrs(self): + """ + 解析属性行 + """ + while line := self.match_next_line(): + if ":" in line: + name, desc = line.split(":", 1) + self.docstring.add_attrs(name.strip(), desc.strip()) + else: + self.docstring.add_attrs(line.strip()) + + def match_next_line(self) -> Optional[str]: + """ + 在一个子解析器中,解析下一行,直到缩进小于等于当前行的缩进 + Returns: + """ + line = self.read_line(move=False) + if line.startswith(" " * self.indent): + self.lineno += 1 + return line[self.indent:] + else: + return None + + def parse(self) -> Docstring: + """ + 逐行解析,直到遇到EOS + + 最开始未解析到的内容全部加入desc + + Returns: + + """ + add_desc = True + while self.lineno < len(self.lines): + token = self.match_token() + if token is None and add_desc: + self.docstring.add_desc(self.lines[self.lineno].strip()) + + if token is not None: + add_desc = False + + match token: + case "args": + self.parse_args() + case "return": + self.parse_return() + case "attribute": + self.parse_attrs() + case "raises": + self.parse_raises() + case "example": + self.parse_example() + case _: + self.lineno += 1 + + return self.docstring + + +class NumpyDocstringParser(Parser): + ... + + +class ReStructuredParser(Parser): + ... + + +def parse(docstring: str, parser: str = "google", indent: int = 4) -> Docstring: + if parser == "google": + return GoogleDocstringParser(docstring, indent).parse() + else: + raise ValueError(f"Unknown parser: {parser}") diff --git a/litedoc/i18n.py b/litedoc/i18n.py new file mode 100644 index 00000000..c45aea50 --- /dev/null +++ b/litedoc/i18n.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +""" +Internationalization module. +""" +from typing import Optional, TypeAlias + +NestedDict: TypeAlias = dict[str, 'str | NestedDict'] + +i18n_dict: dict[str, NestedDict] = { + "en" : { + "docstring": { + "args" : "Arguments", + "return" : "Return", + "attribute": "Attribute", + "raises" : "Raises", + "example" : "Examples", + "yields" : "Yields", + }, + "src": "Source code", + "desc": "Description", + "type": "Type", + }, + "zh-Hans": { + "docstring": { + "args" : "参数", + "return" : "返回", + "attribute": "属性", + "raises" : "引发", + "example" : "示例", + "yields" : "产出", + }, + "src": "源代码", + "desc": "说明", + "type": "类型", + }, + "zh-Hant": { + "docstring": { + "args" : "變數説明", + "return" : "返回", + "attribute": "屬性", + "raises" : "抛出", + "example" : "範例", + "yields" : "產出", + }, + "src": "源碼", + "desc": "説明", + "type": "類型", + }, + "ja" : { + "docstring": { + "args" : "引数", + "return" : "戻り値", + "attribute": "属性", + "raises" : "例外", + "example" : "例", + "yields" : "生成", + }, + "src": "ソースコード", + "desc": "説明", + "type": "タイプ", + }, +} + + +def flat_i18n_dict(data: dict[str, NestedDict]) -> dict[str, dict[str, str]]: + """ + Flatten i18n_dict. + Examples: + ```python + { + "en": { + "docs": { + "key1": "val1", + "key2": "val2", + } + } + } + ``` + + to + + ```python + { + "en": { + "docs.key1": "val1", + "docs.key2": "val2", + } + } + ``` + Returns: + """ + ret: dict[str, dict[str, str]] = {} + + def _flat(_lang_data: NestedDict) -> dict[str, str]: + res = {} + for k, v in _lang_data.items(): + if isinstance(v, dict): + for kk, vv in _flat(v).items(): + res[f"{k}.{kk}"] = vv + else: + res[k] = v + return res + + for lang, lang_data in data.items(): + ret[lang] = _flat(lang_data) + + return ret + + +i18n_flat_dict = flat_i18n_dict(i18n_dict) + + +def get_text(lang: str, key: str, default: Optional[str] = None, fallback: Optional[str] = "en") -> str: + """ + Get text from i18n_dict. + Args: + lang: language name + key: text key + default: default text, if None return fallback language or key + fallback: fallback language, priority is higher than default + Returns: + str: text + """ + if lang in i18n_flat_dict: + if key in i18n_flat_dict[lang]: + return i18n_flat_dict[lang][key] + + if fallback is not None: + return i18n_flat_dict.get(fallback, {}).get(key, default or key) + else: + return default or key diff --git a/litedoc/output.py b/litedoc/output.py new file mode 100644 index 00000000..24de0fe2 --- /dev/null +++ b/litedoc/output.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午3:59 +@Author : snowykami +@Email : snowykami@outlook.com +@File : output.py +@Software: PyCharm +""" +import os.path + +from litedoc.style.markdown import generate +from litedoc.syntax.astparser import AstParser + + +def write_to_file(content: str, output: str) -> None: + """ + Write content to file. + + Args: + content: str, content to write. + output: str, path to output file. + """ + if not os.path.exists(os.path.dirname(output)): + os.makedirs(os.path.dirname(output)) + + with open(output, "w", encoding="utf-8") as f: + f.write(content) + + +def get_file_list(module_folder: str): + file_list = [] + for root, dirs, files in os.walk(module_folder): + for file in files: + if file.endswith((".py", ".pyi")): + file_list.append(os.path.join(root, file)) + return file_list + + +def get_relative_path(base_path: str, target_path: str) -> str: + """ + 获取相对路径 + Args: + base_path: 基础路径 + target_path: 目标路径 + """ + return os.path.relpath(target_path, base_path) + + +def generate_from_module(module_folder: str, + output_dir: str, + with_top: bool = False, + lang: str = "zh-Hans", + ignored_paths=None, + theme: str = "vitepress", + style: str = "google" + ): + """ + 生成文档 + Args: + module_folder: 模块文件夹 + output_dir: 输出文件夹 + with_top: 是否包含顶层文件夹 False时例如docs/api/module_a, docs/api/module_b, True时例如docs/api/module/module_a.md, docs/api/module/module_b.md + ignored_paths: 忽略的路径 + lang: 语言 + theme: 主题 + style: 样式 + """ + if ignored_paths is None: + ignored_paths = [] + file_data: dict[str, str] = {} # 路径 -> 字串 + + file_list = get_file_list(module_folder) + + # 清理输出目录 + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + replace_data = { + "__init__": "index" if theme == "vitepress" else "README", + ".py" : ".md", + } + + for pyfile_path in file_list: + if any(ignored_path.replace("\\", "/") in pyfile_path.replace("\\", "/") for ignored_path in ignored_paths): + continue + + no_module_name_pyfile_path = get_relative_path(module_folder, pyfile_path) # 去头路径 + + # markdown相对路径 + rel_md_path = pyfile_path if with_top else no_module_name_pyfile_path + for rk, rv in replace_data.items(): + rel_md_path = rel_md_path.replace(rk, rv) + + abs_md_path = os.path.join(output_dir, rel_md_path) + + # 获取模块信息 + ast_parser = AstParser(open(pyfile_path, "r", encoding="utf-8").read()) + + # 生成markdown + + front_matter = { + "title": pyfile_path.replace("\\", "/"). + replace("/", "."). + replace(".py", ""). + replace(".__init__", ""), + + } + + md_content = generate(ast_parser, lang=lang, frontmatter=front_matter) + print(f"Generate {pyfile_path} -> {abs_md_path}") + file_data[abs_md_path] = md_content + + for fn, content in file_data.items(): + write_to_file(content, fn) diff --git a/litedoc/style/__init__.py b/litedoc/style/__init__.py new file mode 100644 index 00000000..fb9c65ff --- /dev/null +++ b/litedoc/style/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午3:39 +@Author : snowykami +@Email : snowykami@outlook.com +@File : __init__.py.py +@Software: PyCharm +""" \ No newline at end of file diff --git a/litedoc/style/markdown.py b/litedoc/style/markdown.py new file mode 100644 index 00000000..32b97445 --- /dev/null +++ b/litedoc/style/markdown.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午3:39 +@Author : snowykami +@Email : snowykami@outlook.com +@File : markdown.py +@Software: PyCharm +""" +from typing import Optional + +from litedoc.syntax.astparser import AstParser +from litedoc.syntax.node import * +from litedoc.i18n import get_text + + +def generate(parser: AstParser, lang: str, frontmatter: Optional[dict] = None, style: str = "google") -> str: + """ + Generate markdown style document from ast + You can modify this function to generate markdown style that enjoys you + Args: + parser: + lang: language + frontmatter: + style: style of docs + Returns: + markdown style document + """ + if frontmatter is not None: + md = "---\n" + for k, v in frontmatter.items(): + md += f"{k}: {v}\n" + md += "---\n" + else: + md = "" + + # var > func > class + + """遍历函数""" + for func in parser.functions: + if func.name.startswith("_"): + continue + md += func.markdown(lang) + + """遍历类""" + + for cls in parser.classes: + md += cls.markdown(lang) + + """遍历变量""" + for var in parser.variables: + md += f"### ***var*** `{var.name} = {var.value}`\n\n" + if var.type != TypeHint.NO_TYPEHINT: + md += f"- **{get_text(lang, 'type')}**: `{var.type}`\n\n" + + if var.docs is not None: + md += f"- **{get_text(lang, 'desc')}**: {var.docs}\n\n" + + return md diff --git a/litedoc/syntax/astparser.py b/litedoc/syntax/astparser.py new file mode 100644 index 00000000..fb416fe8 --- /dev/null +++ b/litedoc/syntax/astparser.py @@ -0,0 +1,300 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午2:13 +@Author : snowykami +@Email : snowykami@outlook.com +@File : astparser.py +@Software: PyCharm +""" +import ast +import inspect + +from .node import * +from ..docstring.parser import parse + +class AstParser: + def __init__(self, code: str, style: str = "google"): + """ + 从代码解析AST + Args: + code: 代码 + style: 注释风格 + """ + self.style = style + self.code = code + self.tree = ast.parse(code) + + self.classes: list[ClassNode] = [] + self.functions: list[FunctionNode] = [] + self.variables: list[AssignNode] = [] + + self.parse() + + @staticmethod + def clear_quotes(s: str) -> str: + """ + 去除类型注解中的引号 + Args: + s: + Returns: + """ + return s.replace("'", "").replace('"', "") + + def get_line_content(self, lineno: int, ignore_index_out: bool = True) -> str: + """获取代码行内容 + Args: + lineno: 行号 + ignore_index_out: 是否忽略索引越界 + Returns: + 代码行内容 + """ + if ignore_index_out: + if lineno < 1 or lineno > len(self.code.split("\n")): + return "" + return self.code.split("\n")[lineno - 1] + + @staticmethod + def match_line_docs(linecontent: str) -> str: + """匹配行内注释 + Args: + linecontent: 行内容 + Returns: + 文档字符串 + """ + in_string = False + string_char = '' + for i, char in enumerate(linecontent): + if char in ('"', "'"): + if in_string: + if char == string_char: + in_string = False + else: + in_string = True + string_char = char + elif char == '#' and not in_string: + return linecontent[i + 1:].strip() + return "" + + def parse(self): + for node in ast.walk(self.tree): + if isinstance(node, ast.ClassDef): + if not self._is_module_level_class(node): + continue + + class_node = ClassNode( + name=node.name, + docs=parse(ast.get_docstring(node), parser=self.style) if ast.get_docstring(node) else None, + inherits=[ast.unparse(base) for base in node.bases] + ) + self.classes.append(class_node) + + # 继续遍历类内部的函数 + for sub_node in node.body: + if isinstance(sub_node, (ast.FunctionDef, ast.AsyncFunctionDef)): + class_node.methods.append(FunctionNode( + name=sub_node.name, + docs=parse(ast.get_docstring(sub_node), parser=self.style) if ast.get_docstring(sub_node) else None, + posonlyargs=[ + ArgNode( + name=arg.arg, + type=self.clear_quotes(ast.unparse(arg.annotation).strip()) if arg.annotation else TypeHint.NO_TYPEHINT, + ) + for arg in sub_node.args.posonlyargs + ], + args=[ + ArgNode( + name=arg.arg, + type=self.clear_quotes(ast.unparse(arg.annotation).strip()) if arg.annotation else TypeHint.NO_TYPEHINT, + ) + for arg in sub_node.args.args + ], + kwonlyargs=[ + ArgNode( + name=arg.arg, + type=self.clear_quotes(ast.unparse(arg.annotation).strip()) if arg.annotation else TypeHint.NO_TYPEHINT, + ) + for arg in sub_node.args.kwonlyargs + ], + kw_defaults=[ + ConstantNode( + value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT + ) + for default in sub_node.args.kw_defaults + ], + defaults=[ + ConstantNode( + value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT + ) + for default in sub_node.args.defaults + ], + return_=self.clear_quotes(ast.unparse(sub_node.returns).strip()) if sub_node.returns else TypeHint.NO_RETURN, + decorators=[ast.unparse(decorator).strip() for decorator in sub_node.decorator_list], + is_async=isinstance(sub_node, ast.AsyncFunctionDef), + src=ast.unparse(sub_node).strip(), + is_classmethod=True + )) + # elif isinstance(sub_node, (ast.Assign, ast.AnnAssign)): + # if isinstance(sub_node, ast.Assign): + # class_node.attrs.append(AttrNode( + # name=sub_node.targets[0].id, # type: ignore + # type=TypeHint.NO_TYPEHINT, + # value=ast.unparse(sub_node.value).strip() + # )) + # elif isinstance(sub_node, ast.AnnAssign): + # class_node.attrs.append(AttrNode( + # name=sub_node.target.id, + # type=ast.unparse(sub_node.annotation).strip(), + # value=ast.unparse(sub_node.value).strip() if sub_node.value else TypeHint.NO_DEFAULT + # )) + # else: + # raise ValueError(f"Unsupported node type: {type(sub_node)}") + + elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + # 仅打印模块级别的函数 + if not self._is_module_level_function(node): + continue + + self.functions.append(FunctionNode( + name=node.name, + docs=parse(ast.get_docstring(node), parser=self.style) if ast.get_docstring(node) else None, + posonlyargs=[ + ArgNode( + name=arg.arg, + type=self.clear_quotes(ast.unparse(arg.annotation).strip()) if arg.annotation else TypeHint.NO_TYPEHINT, + ) + for arg in node.args.posonlyargs + ], + args=[ + ArgNode( + name=arg.arg, + type=self.clear_quotes(ast.unparse(arg.annotation).strip()) if arg.annotation else TypeHint.NO_TYPEHINT, + ) + for arg, default in zip(node.args.args, node.args.defaults) + ], + kwonlyargs=[ + ArgNode( + name=arg.arg, + type=self.clear_quotes(ast.unparse(arg.annotation).strip()) if arg.annotation else TypeHint.NO_TYPEHINT, + ) + for arg in node.args.kwonlyargs + ], + kw_defaults=[ + ConstantNode( + value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT + ) + for default in node.args.kw_defaults + ], + defaults=[ + ConstantNode( + value=ast.unparse(default).strip() if default else TypeHint.NO_DEFAULT + ) + for default in node.args.defaults + ], + return_=self.clear_quotes(ast.unparse(node.returns).strip()) if node.returns else TypeHint.NO_RETURN, + decorators=[ast.unparse(decorator).strip() for decorator in node.decorator_list], + is_async=isinstance(node, ast.AsyncFunctionDef), + src=ast.unparse(node).strip() + )) + + elif isinstance(node, (ast.Assign, ast.AnnAssign)): + if not self._is_module_level_variable2(node): + continue + else: + pass + lineno = node.lineno + prev_line = self.get_line_content(lineno - 1).strip() + curr_line = self.get_line_content(lineno).strip() + next_line = self.get_line_content(lineno + 1).strip() + + # 获取文档字符串,优先检测下行""" + if next_line.startswith('"""'): + docs = next_line[3:-3] + elif prev_line.startswith('"""'): + docs = prev_line[3:-3] + else: + curr_docs = self.match_line_docs(curr_line) + if curr_docs: + docs = curr_docs + else: + docs = None + + # if isinstance(node, ast.Assign): + # for target in node.targets: + # if isinstance(target, ast.Name): + # self.variables.append(AssignNode( + # name=target.id, + # value=ast.unparse(node.value).strip(), + # type=ast.unparse(node.annotation).strip() if isinstance(node, ast.AnnAssign) else TypeHint.NO_TYPEHINT + # )) + if isinstance(node, ast.AnnAssign): + self.variables.append(AssignNode( + name=node.target.id, + value=ast.unparse(node.value).strip() if node.value else TypeHint.NO_DEFAULT, + type=ast.unparse(node.annotation).strip(), + docs=docs + )) + + def _is_module_level_function(self, node: ast.FunctionDef | ast.AsyncFunctionDef): + for parent in ast.walk(self.tree): + if isinstance(parent, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): + if node in parent.body: + return False + return True + + def _is_module_level_class(self, node: ast.ClassDef): + for parent in ast.walk(self.tree): + if isinstance(parent, ast.ClassDef): + if node in parent.body: + return False + return True + + def _is_module_level_variable(self, node: ast.Assign | ast.AnnAssign): + """在类方法或函数内部的变量不会被记录""" + + # for parent in ast.walk(self.tree): + # if isinstance(parent, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): + # if node in parent.body: + # return False + # else: + # for sub_node in parent.body: + # if isinstance(sub_node, (ast.FunctionDef, ast.AsyncFunctionDef)): + # if node in sub_node.body: + # return False + # return True + # 递归检查 + def _check(_node, _parent): + if isinstance(_parent, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): + if _node in _parent.body: + return False + else: + for sub_node in _parent.body: + if isinstance(sub_node, (ast.FunctionDef, ast.AsyncFunctionDef)): + return _check(_node, sub_node) + return True + + for parent in ast.walk(self.tree): + if not _check(node, parent): + return False + return True + + def _is_module_level_variable2(self, node: ast.Assign | ast.AnnAssign) -> bool: + """ + 检查变量是否在模块级别定义。 + """ + for parent in ast.walk(self.tree): + if isinstance(parent, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): + if node in parent.body: + return False + return True + + def __str__(self): + s = "" + for cls in self.classes: + s += f"class {cls.name}:\n" + for func in self.functions: + s += f"def {func.name}:\n" + for var in self.variables: + s += f"{var.name} = {var.value}\n" + return s diff --git a/litedoc/syntax/node.py b/litedoc/syntax/node.py new file mode 100644 index 00000000..4891b10d --- /dev/null +++ b/litedoc/syntax/node.py @@ -0,0 +1,314 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved + +@Time : 2024/8/28 下午2:14 +@Author : snowykami +@Email : snowykami@outlook.com +@File : node.py +@Software: PyCharm +""" +from typing import Literal, Optional +from enum import Enum + +from pydantic import BaseModel, Field + +from litedoc.docstring.docstring import Docstring +from litedoc.i18n import get_text + + +class TypeHint: + NO_TYPEHINT = "NO_TYPE_HINT" + NO_DEFAULT = "NO_DEFAULT" + NO_RETURN = "NO_RETURN" + + +class AssignNode(BaseModel): + """ + AssignNode is a pydantic model that represents an assignment. + Attributes: + name: str + The name of the assignment. + type: str = "" + The type of the assignment. + value: str + The value of the assignment. + """ + name: str + type: str = "" + value: str + docs: Optional[str] = "" + + +class ArgNode(BaseModel): + """ + ArgNode is a pydantic model that represents an argument. + Attributes: + name: str + The name of the argument. + type: str = "" + The type of the argument. + default: str = "" + The default value of the argument. + """ + name: str + type: str = TypeHint.NO_TYPEHINT + + +class AttrNode(BaseModel): + """ + AttrNode is a pydantic model that represents an attribute. + Attributes: + name: str + The name of the attribute. + type: str = "" + The type of the attribute. + value: str = "" + The value of the attribute + """ + name: str + type: str = "" + value: str = "" + + +class ImportNode(BaseModel): + """ + ImportNode is a pydantic model that represents an import statement. + Attributes: + name: str + The name of the import statement. + as_: str = "" + The alias of the import + """ + name: str + as_: str = "" + + +class ConstantNode(BaseModel): + """ + ConstantNode is a pydantic model that represents a constant. + Attributes: + value: str + The value of the constant. + """ + value: str + + +class FunctionNode(BaseModel): + """ + FunctionNode is a pydantic model that represents a function. + Attributes: + name: str + The name of the function. + docs: str = "" + The docstring of the function. + args: list[ArgNode] = [] + The arguments of the function. + return_: ReturnNode = None + The return value of the function. + decorators: list[str] = [] + The decorators of the function. + is_async: bool = False + Whether the function is asynchronous. + """ + name: str + docs: Optional[Docstring] = None + + posonlyargs: list[ArgNode] = [] + args: list[ArgNode] = [] + kwonlyargs: list[ArgNode] = [] + kw_defaults: list[ConstantNode] = [] + defaults: list[ConstantNode] = [] + + return_: str = TypeHint.NO_RETURN + decorators: list[str] = [] + src: str + is_async: bool = False + is_classmethod: bool = False + + magic_methods: dict[str, str] = { + "__add__" : "+", + "__radd__" : "+", + "__sub__" : "-", + "__rsub__" : "-", + "__mul__" : "*", + "__rmul__" : "*", + "__matmul__" : "@", + "__rmatmul__": "@", + "__mod__" : "%", + "__truediv__": "/", + "__rtruediv__": "/", + "__neg__" : "-", + } # 魔术方法, 例如运算符 + + def is_private(self): + """ + Check if the function or method is private. + Returns: + bool: True if the function or method is private, False otherwise. + """ + return self.name.startswith("_") + + def is_builtin(self): + """ + Check if the function or method is a builtin function or method. + Returns: + bool: True if the function or method is a builtin function or method, False otherwise. + """ + return self.name.startswith("__") and self.name.endswith("__") + + def markdown(self, lang: str, indent: int = 0) -> str: + """ + Args: + indent: int + The number of spaces to indent the markdown. + lang: str + The language of the + Returns: + markdown style document + """ + self.complete_default_args() + PREFIX = "" * indent + # if is_classmethod: + # PREFIX = "- #" + func_type = "func" if not self.is_classmethod else "method" + + md = "" + # 装饰器部分 + if len(self.decorators) > 0: + for decorator in self.decorators: + md += PREFIX + f"### `@{decorator}`\n" + + if self.is_async: + md += PREFIX + f"### *async {func_type}* " + else: + md += PREFIX + f"### *{func_type}* " + + # code start + # 配对位置参数和位置参数默认值,无默认值用TypeHint.NO_DEFAULT + args: list[str] = [] # 可直接", ".join(args)得到位置参数部分 + arg_i = 0 + + if len(self.posonlyargs) > 0: + for arg in self.posonlyargs: + arg_text = f"{arg.name}" + if arg.type != TypeHint.NO_TYPEHINT: + arg_text += f": {arg.type}" + arg_default = self.defaults[arg_i].value + if arg_default != TypeHint.NO_DEFAULT: + arg_text += f" = {arg_default}" + args.append(arg_text) + arg_i += 1 + # 加位置参数分割符 / + args.append("/") + + for arg in self.args: + arg_text = f"{arg.name}" + if arg.type != TypeHint.NO_TYPEHINT: + arg_text += f": {arg.type}" + arg_default = self.defaults[arg_i].value + if arg_default != TypeHint.NO_DEFAULT: + arg_text += f" = {arg_default}" + args.append(arg_text) + arg_i += 1 + + if len(self.kwonlyargs) > 0: + # 加关键字参数分割符 * + args.append("*") + for arg, kw_default in zip(self.kwonlyargs, self.kw_defaults): + arg_text = f"{arg.name}" + if arg.type != TypeHint.NO_TYPEHINT: + arg_text += f": {arg.type}" + if kw_default.value != TypeHint.NO_DEFAULT: + arg_text += f" = {kw_default.value}" + args.append(arg_text) + + """魔法方法""" + if self.name in self.magic_methods: + if len(args) == 2: + md += f"`{args[0]} {self.magic_methods[self.name]} {args[1]}" + elif len(args) == 1: + md += f"`{self.magic_methods[self.name]} {args[0]}" + if self.return_ != TypeHint.NO_RETURN: + md += f" => {self.return_}" + else: + md += f"`{self.name}(" # code start + md += ", ".join(args) + ")" + if self.return_ != TypeHint.NO_RETURN: + md += f" -> {self.return_}" + + md += "`\n\n" # code end + + """此处预留docstring""" + if self.docs is not None: + md += f"\n{self.docs.markdown(lang, indent)}\n" + else: + pass + # 源码展示 + md += PREFIX + f"\n
\n {get_text(lang, 'src')} \n\n```python\n{self.src}\n```\n
\n\n" + + return md + + def complete_default_args(self): + """ + 补全位置参数默认值,用无默认值插入 + Returns: + + """ + num = len(self.args) + len(self.posonlyargs) - len(self.defaults) + self.defaults = [ConstantNode(value=TypeHint.NO_DEFAULT) for _ in range(num)] + self.defaults + + def __str__(self): + return f"def {self.name}({', '.join([f'{arg.name}: {arg.type} = {arg.default}' for arg in self.args])}) -> {self.return_}" + + +class ClassNode(BaseModel): + """ + ClassNode is a pydantic model that represents a class. + Attributes: + name: str + The name of the class. + docs: str = "" + The docstring of the class. + attrs: list[AttrNode] = [] + The attributes of the class. + methods: list[MethodNode] = [] + The methods of the class. + inherits: list["ClassNode"] = [] + The classes that the class inherits from + """ + name: str + docs: Optional[Docstring] = None + attrs: list[AttrNode] = [] + methods: list[FunctionNode] = [] + inherits: list[str] = [] + + def markdown(self, lang: str) -> str: + """ + 返回类的markdown文档 + Args: + lang: str + The language of the + Returns: + markdown style document + """ + hidden_methods = [ + "__str__", + "__repr__", + ] + md = "" + md += f"### **class** `{self.name}" + if len(self.inherits) > 0: + md += f"({', '.join([cls for cls in self.inherits])})" + md += "`\n" + for method in self.methods: + if method.name in hidden_methods: + continue + md += method.markdown(lang, 2) + for attr in self.attrs: + if attr.type == TypeHint.NO_TYPEHINT: + md += f"#### ***attr*** `{attr.name} = {attr.value}`\n\n" + else: + md += f"#### ***attr*** `{attr.name}: {attr.type} = {attr.value}`\n\n" + + return md