📝 文档初步大迁移 vuepress
-> vitepress
This commit is contained in:
@ -1,8 +0,0 @@
|
||||
---
|
||||
title: 开发及贡献
|
||||
index: false
|
||||
icon: laptop-code
|
||||
category: 开发
|
||||
---
|
||||
|
||||
<Catalog />
|
@ -1,3 +0,0 @@
|
||||
---
|
||||
title: liteyuki
|
||||
---
|
@ -1,562 +0,0 @@
|
||||
---
|
||||
title: liteyuki.bot
|
||||
---
|
||||
### *func* `get_bot() -> LiteyukiBot`
|
||||
|
||||
|
||||
|
||||
**Description**: 获取轻雪实例
|
||||
|
||||
|
||||
**Return**: LiteyukiBot: 当前的轻雪实例
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def get_bot() -> LiteyukiBot:
|
||||
"""
|
||||
获取轻雪实例
|
||||
|
||||
Returns:
|
||||
LiteyukiBot: 当前的轻雪实例
|
||||
"""
|
||||
if IS_MAIN_PROCESS:
|
||||
if _BOT_INSTANCE is None:
|
||||
raise RuntimeError('Liteyuki instance not initialized.')
|
||||
return _BOT_INSTANCE
|
||||
else:
|
||||
raise RuntimeError("Can't get bot instance in sub process.")
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `get_config(key: str = None) -> Any`
|
||||
|
||||
|
||||
|
||||
**Description**: 获取配置
|
||||
|
||||
**Arguments**:
|
||||
> - key: 配置键
|
||||
> - default: 默认值
|
||||
|
||||
**Return**: Any: 配置值
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def get_config(key: str, default: Any=None) -> Any:
|
||||
"""
|
||||
获取配置
|
||||
Args:
|
||||
key: 配置键
|
||||
default: 默认值
|
||||
|
||||
Returns:
|
||||
Any: 配置值
|
||||
"""
|
||||
return get_bot().config.get(key, default)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `get_config_with_compat(key: str = None) -> Any`
|
||||
|
||||
|
||||
|
||||
**Description**: 获取配置,兼容旧版本
|
||||
|
||||
**Arguments**:
|
||||
> - key: 配置键
|
||||
> - compat_keys: 兼容键
|
||||
> - default: 默认值
|
||||
|
||||
**Return**: Any: 配置值
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def get_config_with_compat(key: str, compat_keys: tuple[str], default: Any=None) -> Any:
|
||||
"""
|
||||
获取配置,兼容旧版本
|
||||
Args:
|
||||
key: 配置键
|
||||
compat_keys: 兼容键
|
||||
default: 默认值
|
||||
|
||||
Returns:
|
||||
Any: 配置值
|
||||
"""
|
||||
if key in get_bot().config:
|
||||
return get_bot().config[key]
|
||||
for compat_key in compat_keys:
|
||||
if compat_key in get_bot().config:
|
||||
logger.warning(f'Config key "{compat_key}" will be deprecated, use "{key}" instead.')
|
||||
return get_bot().config[compat_key]
|
||||
return default
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `print_logo()`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def print_logo():
|
||||
print('\x1b[34m' + '\n __ ______ ________ ________ __ __ __ __ __ __ ______ \n / | / |/ |/ |/ \\ / |/ | / |/ | / |/ |\n $$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \\ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/ \n $$ | $$ | $$ | $$ |__ $$ \\/$$/ $$ | $$ |$$ |/$$/ $$ | \n $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |$$ $$< $$ | \n $$ | $$ | $$ | $$$$$/ $$$$/ $$ | $$ |$$$$$ \\ $$ | \n $$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \\__$$ |$$ |$$ \\ _$$ |_ \n $$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |\n $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/ \n ' + '\x1b[0m')
|
||||
```
|
||||
</details>
|
||||
|
||||
### **class** `LiteyukiBot`
|
||||
### *method* `__init__(self) -> None`
|
||||
|
||||
|
||||
|
||||
**Description**: 初始化轻雪实例
|
||||
|
||||
**Arguments**:
|
||||
> - *args:
|
||||
> - **kwargs: 配置
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
"""
|
||||
初始化轻雪实例
|
||||
Args:
|
||||
*args:
|
||||
**kwargs: 配置
|
||||
|
||||
"""
|
||||
'常规操作'
|
||||
print_logo()
|
||||
global _BOT_INSTANCE
|
||||
_BOT_INSTANCE = self
|
||||
'配置'
|
||||
self.config: dict[str, Any] = kwargs
|
||||
'初始化'
|
||||
self.init(**self.config)
|
||||
logger.info('Liteyuki is initializing...')
|
||||
'生命周期管理'
|
||||
self.lifespan = Lifespan()
|
||||
self.process_manager: ProcessManager = ProcessManager(lifespan=self.lifespan)
|
||||
'事件循环'
|
||||
self.loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(self.loop)
|
||||
self.stop_event = threading.Event()
|
||||
self.call_restart_count = 0
|
||||
'加载插件加载器'
|
||||
load_plugin('liteyuki.plugins.plugin_loader')
|
||||
'信号处理'
|
||||
signal.signal(signal.SIGINT, self._handle_exit)
|
||||
signal.signal(signal.SIGTERM, self._handle_exit)
|
||||
atexit.register(self.process_manager.terminate_all)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `run(self)`
|
||||
|
||||
|
||||
|
||||
**Description**: 启动逻辑
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def run(self):
|
||||
"""
|
||||
启动逻辑
|
||||
"""
|
||||
self.lifespan.before_start()
|
||||
self.process_manager.start_all()
|
||||
self.lifespan.after_start()
|
||||
self.keep_alive()
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `keep_alive(self)`
|
||||
|
||||
|
||||
|
||||
**Description**: 保持轻雪运行
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def keep_alive(self):
|
||||
"""
|
||||
保持轻雪运行
|
||||
Returns:
|
||||
|
||||
"""
|
||||
try:
|
||||
while not self.stop_event.is_set():
|
||||
time.sleep(0.5)
|
||||
except KeyboardInterrupt:
|
||||
logger.info('Liteyuki is stopping...')
|
||||
self.stop()
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `_handle_exit(self, signum, frame)`
|
||||
|
||||
|
||||
|
||||
**Description**: 信号处理
|
||||
|
||||
**Arguments**:
|
||||
> - signum:
|
||||
> - frame:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def _handle_exit(self, signum, frame):
|
||||
"""
|
||||
信号处理
|
||||
Args:
|
||||
signum:
|
||||
frame:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
logger.info('Received signal, stopping all processes.')
|
||||
self.stop()
|
||||
sys.exit(0)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `restart(self, delay: int = 0)`
|
||||
|
||||
|
||||
|
||||
**Description**: 重启轻雪本体
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def restart(self, delay: int=0):
|
||||
"""
|
||||
重启轻雪本体
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if self.call_restart_count < 1:
|
||||
executable = sys.executable
|
||||
args = sys.argv
|
||||
logger.info('Restarting LiteyukiBot...')
|
||||
time.sleep(delay)
|
||||
if platform.system() == 'Windows':
|
||||
cmd = 'start'
|
||||
elif platform.system() == 'Linux':
|
||||
cmd = 'nohup'
|
||||
elif platform.system() == 'Darwin':
|
||||
cmd = 'open'
|
||||
else:
|
||||
cmd = 'nohup'
|
||||
self.process_manager.terminate_all()
|
||||
threading.Thread(target=os.system, args=(f"{cmd} {executable} {' '.join(args)}",)).start()
|
||||
sys.exit(0)
|
||||
self.call_restart_count += 1
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `restart_process(self, name: Optional[str] = None)`
|
||||
|
||||
|
||||
|
||||
**Description**: 停止轻雪
|
||||
|
||||
**Arguments**:
|
||||
> - name: 进程名称, 默认为None, 所有进程
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def restart_process(self, name: Optional[str]=None):
|
||||
"""
|
||||
停止轻雪
|
||||
Args:
|
||||
name: 进程名称, 默认为None, 所有进程
|
||||
Returns:
|
||||
"""
|
||||
self.lifespan.before_process_shutdown()
|
||||
self.lifespan.before_process_shutdown()
|
||||
if name is not None:
|
||||
chan_active = get_channel(f'{name}-active')
|
||||
chan_active.send(1)
|
||||
else:
|
||||
for process_name in self.process_manager.processes:
|
||||
chan_active = get_channel(f'{process_name}-active')
|
||||
chan_active.send(1)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `init(self)`
|
||||
|
||||
|
||||
|
||||
**Description**: 初始化轻雪, 自动调用
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def init(self, *args, **kwargs):
|
||||
"""
|
||||
初始化轻雪, 自动调用
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.init_logger()
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `init_logger(self)`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def init_logger(self):
|
||||
init_log(config=self.config)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `stop(self)`
|
||||
|
||||
|
||||
|
||||
**Description**: 停止轻雪
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def stop(self):
|
||||
"""
|
||||
停止轻雪
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.stop_event.set()
|
||||
self.loop.stop()
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_before_start(self, func: LIFESPAN_FUNC)`
|
||||
|
||||
|
||||
|
||||
**Description**: 注册启动前的函数
|
||||
|
||||
**Arguments**:
|
||||
> - func:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_before_start(self, func: LIFESPAN_FUNC):
|
||||
"""
|
||||
注册启动前的函数
|
||||
Args:
|
||||
func:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self.lifespan.on_before_start(func)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_after_start(self, func: LIFESPAN_FUNC)`
|
||||
|
||||
|
||||
|
||||
**Description**: 注册启动后的函数
|
||||
|
||||
**Arguments**:
|
||||
> - func:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_after_start(self, func: LIFESPAN_FUNC):
|
||||
"""
|
||||
注册启动后的函数
|
||||
Args:
|
||||
func:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self.lifespan.on_after_start(func)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_after_shutdown(self, func: LIFESPAN_FUNC)`
|
||||
|
||||
|
||||
|
||||
**Description**: 注册停止后的函数:未实现
|
||||
|
||||
**Arguments**:
|
||||
> - func:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_after_shutdown(self, func: LIFESPAN_FUNC):
|
||||
"""
|
||||
注册停止后的函数:未实现
|
||||
Args:
|
||||
func:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self.lifespan.on_after_shutdown(func)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_before_process_shutdown(self, func: LIFESPAN_FUNC)`
|
||||
|
||||
|
||||
|
||||
**Description**: 注册进程停止前的函数,为子进程停止时调用
|
||||
|
||||
**Arguments**:
|
||||
> - func:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_before_process_shutdown(self, func: LIFESPAN_FUNC):
|
||||
"""
|
||||
注册进程停止前的函数,为子进程停止时调用
|
||||
Args:
|
||||
func:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self.lifespan.on_before_process_shutdown(func)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_before_process_restart(self, func: LIFESPAN_FUNC)`
|
||||
|
||||
|
||||
|
||||
**Description**: 注册进程重启前的函数,为子进程重启时调用
|
||||
|
||||
**Arguments**:
|
||||
> - func:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_before_process_restart(self, func: LIFESPAN_FUNC):
|
||||
"""
|
||||
注册进程重启前的函数,为子进程重启时调用
|
||||
Args:
|
||||
func:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self.lifespan.on_before_process_restart(func)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_after_restart(self, func: LIFESPAN_FUNC)`
|
||||
|
||||
|
||||
|
||||
**Description**: 注册重启后的函数:未实现
|
||||
|
||||
**Arguments**:
|
||||
> - func:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_after_restart(self, func: LIFESPAN_FUNC):
|
||||
"""
|
||||
注册重启后的函数:未实现
|
||||
Args:
|
||||
func:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self.lifespan.on_after_restart(func)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_after_nonebot_init(self, func: LIFESPAN_FUNC)`
|
||||
|
||||
|
||||
|
||||
**Description**: 注册nonebot初始化后的函数
|
||||
|
||||
**Arguments**:
|
||||
> - func:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_after_nonebot_init(self, func: LIFESPAN_FUNC):
|
||||
"""
|
||||
注册nonebot初始化后的函数
|
||||
Args:
|
||||
func:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self.lifespan.on_after_nonebot_init(func)
|
||||
```
|
||||
</details>
|
||||
|
||||
### ***var*** `_BOT_INSTANCE = NO_DEFAULT`
|
||||
|
||||
- **Type**: `LiteyukiBot`
|
||||
|
@ -1,423 +0,0 @@
|
||||
---
|
||||
title: liteyuki.bot.lifespan
|
||||
---
|
||||
### **class** `Lifespan`
|
||||
### *method* `__init__(self) -> None`
|
||||
|
||||
|
||||
|
||||
**Description**: 轻雪生命周期管理,启动、停止、重启
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
轻雪生命周期管理,启动、停止、重启
|
||||
"""
|
||||
self.life_flag: int = 0
|
||||
self._before_start_funcs: list[LIFESPAN_FUNC] = []
|
||||
self._after_start_funcs: list[LIFESPAN_FUNC] = []
|
||||
self._before_process_shutdown_funcs: list[LIFESPAN_FUNC] = []
|
||||
self._after_shutdown_funcs: list[LIFESPAN_FUNC] = []
|
||||
self._before_process_restart_funcs: list[LIFESPAN_FUNC] = []
|
||||
self._after_restart_funcs: list[LIFESPAN_FUNC] = []
|
||||
self._after_nonebot_init_funcs: list[LIFESPAN_FUNC] = []
|
||||
```
|
||||
</details>
|
||||
|
||||
### `@staticmethod`
|
||||
### *method* `run_funcs(funcs: list[LIFESPAN_FUNC | PROCESS_LIFESPAN_FUNC]) -> None`
|
||||
|
||||
|
||||
|
||||
**Description**: 运行函数
|
||||
|
||||
**Arguments**:
|
||||
> - funcs:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```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))
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||
|
||||
|
||||
|
||||
**Description**: 注册启动时的函数
|
||||
|
||||
**Arguments**:
|
||||
> - func:
|
||||
|
||||
**Return**: LIFESPAN_FUNC:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_before_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||
"""
|
||||
注册启动时的函数
|
||||
Args:
|
||||
func:
|
||||
Returns:
|
||||
LIFESPAN_FUNC:
|
||||
"""
|
||||
self._before_start_funcs.append(func)
|
||||
return func
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||
|
||||
|
||||
|
||||
**Description**: 注册启动时的函数
|
||||
|
||||
**Arguments**:
|
||||
> - func:
|
||||
|
||||
**Return**: LIFESPAN_FUNC:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_after_start(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||
"""
|
||||
注册启动时的函数
|
||||
Args:
|
||||
func:
|
||||
Returns:
|
||||
LIFESPAN_FUNC:
|
||||
"""
|
||||
self._after_start_funcs.append(func)
|
||||
return func
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||
|
||||
|
||||
|
||||
**Description**: 注册停止前的函数
|
||||
|
||||
**Arguments**:
|
||||
> - func:
|
||||
|
||||
**Return**: LIFESPAN_FUNC:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_before_process_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||
"""
|
||||
注册停止前的函数
|
||||
Args:
|
||||
func:
|
||||
Returns:
|
||||
LIFESPAN_FUNC:
|
||||
"""
|
||||
self._before_process_shutdown_funcs.append(func)
|
||||
return func
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||
|
||||
|
||||
|
||||
**Description**: 注册停止后的函数
|
||||
|
||||
**Arguments**:
|
||||
> - func:
|
||||
|
||||
**Return**: LIFESPAN_FUNC:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_after_shutdown(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||
"""
|
||||
注册停止后的函数
|
||||
Args:
|
||||
func:
|
||||
|
||||
Returns:
|
||||
LIFESPAN_FUNC:
|
||||
|
||||
"""
|
||||
self._after_shutdown_funcs.append(func)
|
||||
return func
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||
|
||||
|
||||
|
||||
**Description**: 注册重启时的函数
|
||||
|
||||
**Arguments**:
|
||||
> - func:
|
||||
|
||||
**Return**: LIFESPAN_FUNC:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_before_process_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||
"""
|
||||
注册重启时的函数
|
||||
Args:
|
||||
func:
|
||||
Returns:
|
||||
LIFESPAN_FUNC:
|
||||
"""
|
||||
self._before_process_restart_funcs.append(func)
|
||||
return func
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC`
|
||||
|
||||
|
||||
|
||||
**Description**: 注册重启后的函数
|
||||
|
||||
**Arguments**:
|
||||
> - func:
|
||||
|
||||
**Return**: LIFESPAN_FUNC:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_after_restart(self, func: LIFESPAN_FUNC) -> LIFESPAN_FUNC:
|
||||
"""
|
||||
注册重启后的函数
|
||||
Args:
|
||||
func:
|
||||
Returns:
|
||||
LIFESPAN_FUNC:
|
||||
"""
|
||||
self._after_restart_funcs.append(func)
|
||||
return func
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_after_nonebot_init(self, func)`
|
||||
|
||||
|
||||
|
||||
**Description**: 注册 NoneBot 初始化后的函数
|
||||
|
||||
**Arguments**:
|
||||
> - func:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_after_nonebot_init(self, func):
|
||||
"""
|
||||
注册 NoneBot 初始化后的函数
|
||||
Args:
|
||||
func:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self._after_nonebot_init_funcs.append(func)
|
||||
return func
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `before_start(self) -> None`
|
||||
|
||||
|
||||
|
||||
**Description**: 启动前
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def before_start(self) -> None:
|
||||
"""
|
||||
启动前
|
||||
Returns:
|
||||
"""
|
||||
logger.debug('Running before_start functions')
|
||||
self.run_funcs(self._before_start_funcs)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `after_start(self) -> None`
|
||||
|
||||
|
||||
|
||||
**Description**: 启动后
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def after_start(self) -> None:
|
||||
"""
|
||||
启动后
|
||||
Returns:
|
||||
"""
|
||||
logger.debug('Running after_start functions')
|
||||
self.run_funcs(self._after_start_funcs)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `before_process_shutdown(self) -> None`
|
||||
|
||||
|
||||
|
||||
**Description**: 停止前
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def before_process_shutdown(self) -> None:
|
||||
"""
|
||||
停止前
|
||||
Returns:
|
||||
"""
|
||||
logger.debug('Running before_shutdown functions')
|
||||
self.run_funcs(self._before_process_shutdown_funcs)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `after_shutdown(self) -> None`
|
||||
|
||||
|
||||
|
||||
**Description**: 停止后
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def after_shutdown(self) -> None:
|
||||
"""
|
||||
停止后
|
||||
Returns:
|
||||
"""
|
||||
logger.debug('Running after_shutdown functions')
|
||||
self.run_funcs(self._after_shutdown_funcs)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `before_process_restart(self) -> None`
|
||||
|
||||
|
||||
|
||||
**Description**: 重启前
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def before_process_restart(self) -> None:
|
||||
"""
|
||||
重启前
|
||||
Returns:
|
||||
"""
|
||||
logger.debug('Running before_restart functions')
|
||||
self.run_funcs(self._before_process_restart_funcs)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `after_restart(self) -> None`
|
||||
|
||||
|
||||
|
||||
**Description**: 重启后
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def after_restart(self) -> None:
|
||||
"""
|
||||
重启后
|
||||
Returns:
|
||||
|
||||
"""
|
||||
logger.debug('Running after_restart functions')
|
||||
self.run_funcs(self._after_restart_funcs)
|
||||
```
|
||||
</details>
|
||||
|
||||
### ***var*** `SYNC_LIFESPAN_FUNC = Callable[[], Any]`
|
||||
|
||||
- **Type**: `TypeAlias`
|
||||
|
||||
### ***var*** `ASYNC_LIFESPAN_FUNC = Callable[[], Awaitable[Any]]`
|
||||
|
||||
- **Type**: `TypeAlias`
|
||||
|
||||
### ***var*** `LIFESPAN_FUNC = SYNC_LIFESPAN_FUNC | ASYNC_LIFESPAN_FUNC`
|
||||
|
||||
- **Type**: `TypeAlias`
|
||||
|
||||
### ***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`
|
||||
|
@ -1,3 +0,0 @@
|
||||
---
|
||||
title: liteyuki.comm
|
||||
---
|
@ -1,555 +0,0 @@
|
||||
---
|
||||
title: liteyuki.comm.channel
|
||||
---
|
||||
### *func* `set_channel()`
|
||||
|
||||
|
||||
|
||||
**Description**: 设置通道实例
|
||||
|
||||
**Arguments**:
|
||||
> - name: 通道名称
|
||||
> - channel: 通道实例
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def set_channel(name: str, channel: Channel):
|
||||
"""
|
||||
设置通道实例
|
||||
Args:
|
||||
name: 通道名称
|
||||
channel: 通道实例
|
||||
"""
|
||||
if not isinstance(channel, Channel):
|
||||
raise TypeError(f'channel_ must be an instance of Channel, {type(channel)} found')
|
||||
if IS_MAIN_PROCESS:
|
||||
_channel[name] = channel
|
||||
else:
|
||||
channel_deliver_passive_channel.send(('set_channel', {'name': name, 'channel_': channel}))
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `set_channels()`
|
||||
|
||||
|
||||
|
||||
**Description**: 设置通道实例
|
||||
|
||||
**Arguments**:
|
||||
> - channels: 通道名称
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def set_channels(channels: dict[str, Channel]):
|
||||
"""
|
||||
设置通道实例
|
||||
Args:
|
||||
channels: 通道名称
|
||||
"""
|
||||
for name, channel in channels.items():
|
||||
set_channel(name, channel)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `get_channel() -> Channel`
|
||||
|
||||
|
||||
|
||||
**Description**: 获取通道实例
|
||||
|
||||
**Arguments**:
|
||||
> - name: 通道名称
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def get_channel(name: str) -> Channel:
|
||||
"""
|
||||
获取通道实例
|
||||
Args:
|
||||
name: 通道名称
|
||||
Returns:
|
||||
"""
|
||||
if IS_MAIN_PROCESS:
|
||||
return _channel[name]
|
||||
else:
|
||||
recv_chan = Channel[Channel[Any]]('recv_chan')
|
||||
channel_deliver_passive_channel.send(('get_channel', {'name': name, 'recv_chan': recv_chan}))
|
||||
return recv_chan.receive()
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `get_channels() -> dict[str, Channel]`
|
||||
|
||||
|
||||
|
||||
**Description**: 获取通道实例
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def get_channels() -> dict[str, Channel]:
|
||||
"""
|
||||
获取通道实例
|
||||
Returns:
|
||||
"""
|
||||
if IS_MAIN_PROCESS:
|
||||
return _channel
|
||||
else:
|
||||
recv_chan = Channel[dict[str, Channel[Any]]]('recv_chan')
|
||||
channel_deliver_passive_channel.send(('get_channels', {'recv_chan': recv_chan}))
|
||||
return recv_chan.receive()
|
||||
```
|
||||
</details>
|
||||
|
||||
### `@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'set_channel')`
|
||||
### *func* `on_set_channel()`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'set_channel')
|
||||
def on_set_channel(data: tuple[str, dict[str, Any]]):
|
||||
name, channel = (data[1]['name'], data[1]['channel_'])
|
||||
set_channel(name, channel)
|
||||
```
|
||||
</details>
|
||||
|
||||
### `@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channel')`
|
||||
### *func* `on_get_channel()`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channel')
|
||||
def on_get_channel(data: tuple[str, dict[str, Any]]):
|
||||
name, recv_chan = (data[1]['name'], data[1]['recv_chan'])
|
||||
recv_chan.send(get_channel(name))
|
||||
```
|
||||
</details>
|
||||
|
||||
### `@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channels')`
|
||||
### *func* `on_get_channels()`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == 'get_channels')
|
||||
def on_get_channels(data: tuple[str, dict[str, Any]]):
|
||||
recv_chan = data[1]['recv_chan']
|
||||
recv_chan.send(get_channels())
|
||||
```
|
||||
</details>
|
||||
|
||||
### **class** `Channel(Generic[T])`
|
||||
### *method* `__init__(self, _id: str = '', type_check: Optional[bool] = None)`
|
||||
|
||||
|
||||
|
||||
**Description**: 初始化通道
|
||||
|
||||
**Arguments**:
|
||||
> - _id: 通道ID
|
||||
> - type_check: 是否开启类型检查, 若为空,则传入泛型默认开启,否则默认关闭
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def __init__(self, _id: str='', type_check: Optional[bool]=None):
|
||||
"""
|
||||
初始化通道
|
||||
Args:
|
||||
_id: 通道ID
|
||||
type_check: 是否开启类型检查, 若为空,则传入泛型默认开启,否则默认关闭
|
||||
"""
|
||||
self.conn_send, self.conn_recv = Pipe()
|
||||
self._closed = False
|
||||
self._on_main_receive_funcs: list[int] = []
|
||||
self._on_sub_receive_funcs: list[int] = []
|
||||
self.name: str = _id
|
||||
self.is_main_receive_loop_running = False
|
||||
self.is_sub_receive_loop_running = False
|
||||
if type_check is None:
|
||||
type_check = self._get_generic_type() is not None
|
||||
elif type_check:
|
||||
if self._get_generic_type() is None:
|
||||
raise TypeError('Type hint is required for enforcing type check.')
|
||||
self.type_check = type_check
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `_get_generic_type(self) -> Optional[type]`
|
||||
|
||||
|
||||
|
||||
**Description**: 获取通道传递泛型类型
|
||||
|
||||
|
||||
**Return**: Optional[type]: 泛型类型
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```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
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `_validate_structure(self, data: Any, structure: type) -> bool`
|
||||
|
||||
|
||||
|
||||
**Description**: 验证数据结构
|
||||
|
||||
**Arguments**:
|
||||
> - data: 数据
|
||||
> - structure: 结构
|
||||
|
||||
**Return**: bool: 是否通过验证
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```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
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `send(self, data: T)`
|
||||
|
||||
|
||||
|
||||
**Description**: 发送数据
|
||||
|
||||
**Arguments**:
|
||||
> - data: 数据
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def send(self, data: T):
|
||||
"""
|
||||
发送数据
|
||||
Args:
|
||||
data: 数据
|
||||
"""
|
||||
if self.type_check:
|
||||
_type = self._get_generic_type()
|
||||
if _type is not None and (not self._validate_structure(data, _type)):
|
||||
raise TypeError(f'Data must be an instance of {_type}, {type(data)} found')
|
||||
if self._closed:
|
||||
raise RuntimeError('Cannot send to a closed channel_')
|
||||
self.conn_send.send(data)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `receive(self) -> T`
|
||||
|
||||
|
||||
|
||||
**Description**: 接收数据
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def receive(self) -> T:
|
||||
"""
|
||||
接收数据
|
||||
Args:
|
||||
"""
|
||||
if self._closed:
|
||||
raise RuntimeError('Cannot receive from a closed channel_')
|
||||
while True:
|
||||
data = self.conn_recv.recv()
|
||||
return data
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `close(self)`
|
||||
|
||||
|
||||
|
||||
**Description**: 关闭通道
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def close(self):
|
||||
"""
|
||||
关闭通道
|
||||
"""
|
||||
self._closed = True
|
||||
self.conn_send.close()
|
||||
self.conn_recv.close()
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_receive(self, filter_func: Optional[FILTER_FUNC] = None) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]`
|
||||
|
||||
|
||||
|
||||
**Description**: 接收数据并执行函数
|
||||
|
||||
**Arguments**:
|
||||
> - filter_func: 过滤函数,为None则不过滤
|
||||
|
||||
**Return**: 装饰器,装饰一个函数在接收到数据后执行
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_receive(self, filter_func: Optional[FILTER_FUNC]=None) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]:
|
||||
"""
|
||||
接收数据并执行函数
|
||||
Args:
|
||||
filter_func: 过滤函数,为None则不过滤
|
||||
Returns:
|
||||
装饰器,装饰一个函数在接收到数据后执行
|
||||
"""
|
||||
if not self.is_sub_receive_loop_running and (not IS_MAIN_PROCESS):
|
||||
threading.Thread(target=self._start_sub_receive_loop, daemon=True).start()
|
||||
if not self.is_main_receive_loop_running and IS_MAIN_PROCESS:
|
||||
threading.Thread(target=self._start_main_receive_loop, daemon=True).start()
|
||||
|
||||
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
|
||||
return decorator
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `_run_on_main_receive_funcs(self, data: Any)`
|
||||
|
||||
|
||||
|
||||
**Description**: 运行接收函数
|
||||
|
||||
**Arguments**:
|
||||
> - data: 数据
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```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))
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `_run_on_sub_receive_funcs(self, data: Any)`
|
||||
|
||||
|
||||
|
||||
**Description**: 运行接收函数
|
||||
|
||||
**Arguments**:
|
||||
> - data: 数据
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```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))
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `_start_main_receive_loop(self)`
|
||||
|
||||
|
||||
|
||||
**Description**: 开始接收数据
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```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)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `_start_sub_receive_loop(self)`
|
||||
|
||||
|
||||
|
||||
**Description**: 开始接收数据
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```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)
|
||||
```
|
||||
</details>
|
||||
|
||||
### ***var*** `SYNC_ON_RECEIVE_FUNC = Callable[[T], Any]`
|
||||
|
||||
- **Type**: `TypeAlias`
|
||||
|
||||
### ***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`
|
||||
|
||||
- **Type**: `TypeAlias`
|
||||
|
||||
### ***var*** `SYNC_FILTER_FUNC = Callable[[T], bool]`
|
||||
|
||||
- **Type**: `TypeAlias`
|
||||
|
||||
### ***var*** `ASYNC_FILTER_FUNC = Callable[[T], Coroutine[Any, Any, bool]]`
|
||||
|
||||
- **Type**: `TypeAlias`
|
||||
|
||||
### ***var*** `FILTER_FUNC = SYNC_FILTER_FUNC | ASYNC_FILTER_FUNC`
|
||||
|
||||
- **Type**: `TypeAlias`
|
||||
|
||||
### ***var*** `_func_id = 0`
|
||||
|
||||
- **Type**: `int`
|
||||
|
||||
### ***var*** `_channel = {}`
|
||||
|
||||
- **Type**: `dict[str, 'Channel']`
|
||||
|
||||
### ***var*** `_callback_funcs = {}`
|
||||
|
||||
- **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]]]`
|
||||
|
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: liteyuki.comm.event
|
||||
---
|
||||
### **class** `Event`
|
||||
### *method* `__init__(self, name: str, data: dict[str, Any])`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def __init__(self, name: str, data: dict[str, Any]):
|
||||
self.name = name
|
||||
self.data = data
|
||||
```
|
||||
</details>
|
||||
|
@ -1,449 +0,0 @@
|
||||
---
|
||||
title: liteyuki.comm.storage
|
||||
---
|
||||
### `@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get')`
|
||||
### *func* `on_get()`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get')
|
||||
def on_get(data: tuple[str, dict[str, Any]]):
|
||||
key = data[1]['key']
|
||||
default = data[1]['default']
|
||||
recv_chan = data[1]['recv_chan']
|
||||
recv_chan.send(shared_memory.get(key, default))
|
||||
```
|
||||
</details>
|
||||
|
||||
### `@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'set')`
|
||||
### *func* `on_set()`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'set')
|
||||
def on_set(data: tuple[str, dict[str, Any]]):
|
||||
key = data[1]['key']
|
||||
value = data[1]['value']
|
||||
shared_memory.set(key, value)
|
||||
```
|
||||
</details>
|
||||
|
||||
### `@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'delete')`
|
||||
### *func* `on_delete()`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'delete')
|
||||
def on_delete(data: tuple[str, dict[str, Any]]):
|
||||
key = data[1]['key']
|
||||
shared_memory.delete(key)
|
||||
```
|
||||
</details>
|
||||
|
||||
### `@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get_all')`
|
||||
### *func* `on_get_all()`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
@shared_memory.passive_chan.on_receive(lambda d: d[0] == 'get_all')
|
||||
def on_get_all(data: tuple[str, dict[str, Any]]):
|
||||
recv_chan = data[1]['recv_chan']
|
||||
recv_chan.send(shared_memory.get_all())
|
||||
```
|
||||
</details>
|
||||
|
||||
### `@channel.publish_channel.on_receive()`
|
||||
### *func* `on_publish()`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
@channel.publish_channel.on_receive()
|
||||
def on_publish(data: tuple[str, Any]):
|
||||
channel_, data = data
|
||||
shared_memory.run_subscriber_receive_funcs(channel_, data)
|
||||
```
|
||||
</details>
|
||||
|
||||
### **class** `Subscriber`
|
||||
### *method* `__init__(self)`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def __init__(self):
|
||||
self._subscribers = {}
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `receive(self) -> Any`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def receive(self) -> Any:
|
||||
pass
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `unsubscribe(self) -> None`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def unsubscribe(self) -> None:
|
||||
pass
|
||||
```
|
||||
</details>
|
||||
|
||||
### **class** `KeyValueStore`
|
||||
### *method* `__init__(self)`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def __init__(self):
|
||||
self._store = {}
|
||||
self.active_chan = Channel[tuple[str, Optional[dict[str, Any]]]](_id='shared_memory-active')
|
||||
self.passive_chan = Channel[tuple[str, Optional[dict[str, Any]]]](_id='shared_memory-passive')
|
||||
self.publish_channel = Channel[tuple[str, Any]](_id='shared_memory-publish')
|
||||
self.is_main_receive_loop_running = False
|
||||
self.is_sub_receive_loop_running = False
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `set(self, key: str, value: Any) -> None`
|
||||
|
||||
|
||||
|
||||
**Description**: 设置键值对
|
||||
|
||||
**Arguments**:
|
||||
> - key: 键
|
||||
> - value: 值
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def set(self, key: str, value: Any) -> None:
|
||||
"""
|
||||
设置键值对
|
||||
Args:
|
||||
key: 键
|
||||
value: 值
|
||||
|
||||
"""
|
||||
if IS_MAIN_PROCESS:
|
||||
lock = _get_lock(key)
|
||||
with lock:
|
||||
self._store[key] = value
|
||||
else:
|
||||
self.passive_chan.send(('set', {'key': key, 'value': value}))
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `get(self, key: str, default: Optional[Any] = None) -> Optional[Any]`
|
||||
|
||||
|
||||
|
||||
**Description**: 获取键值对
|
||||
|
||||
**Arguments**:
|
||||
> - key: 键
|
||||
> - default: 默认值
|
||||
|
||||
**Return**: Any: 值
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def get(self, key: str, default: Optional[Any]=None) -> Optional[Any]:
|
||||
"""
|
||||
获取键值对
|
||||
Args:
|
||||
key: 键
|
||||
default: 默认值
|
||||
|
||||
Returns:
|
||||
Any: 值
|
||||
"""
|
||||
if IS_MAIN_PROCESS:
|
||||
lock = _get_lock(key)
|
||||
with lock:
|
||||
return self._store.get(key, default)
|
||||
else:
|
||||
recv_chan = Channel[Optional[Any]]('recv_chan')
|
||||
self.passive_chan.send(('get', {'key': key, 'default': default, 'recv_chan': recv_chan}))
|
||||
return recv_chan.receive()
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `delete(self, key: str, ignore_key_error: bool = True) -> None`
|
||||
|
||||
|
||||
|
||||
**Description**: 删除键值对
|
||||
|
||||
**Arguments**:
|
||||
> - key: 键
|
||||
> - ignore_key_error: 是否忽略键不存在的错误
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def delete(self, key: str, ignore_key_error: bool=True) -> None:
|
||||
"""
|
||||
删除键值对
|
||||
Args:
|
||||
key: 键
|
||||
ignore_key_error: 是否忽略键不存在的错误
|
||||
|
||||
Returns:
|
||||
"""
|
||||
if IS_MAIN_PROCESS:
|
||||
lock = _get_lock(key)
|
||||
with lock:
|
||||
if key in self._store:
|
||||
try:
|
||||
del self._store[key]
|
||||
del _locks[key]
|
||||
except KeyError as e:
|
||||
if not ignore_key_error:
|
||||
raise e
|
||||
else:
|
||||
self.passive_chan.send(('delete', {'key': key}))
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `get_all(self) -> dict[str, Any]`
|
||||
|
||||
|
||||
|
||||
**Description**: 获取所有键值对
|
||||
|
||||
**Return**: dict[str, Any]: 键值对
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def get_all(self) -> dict[str, Any]:
|
||||
"""
|
||||
获取所有键值对
|
||||
Returns:
|
||||
dict[str, Any]: 键值对
|
||||
"""
|
||||
if IS_MAIN_PROCESS:
|
||||
return self._store
|
||||
else:
|
||||
recv_chan = Channel[dict[str, Any]]('recv_chan')
|
||||
self.passive_chan.send(('get_all', {'recv_chan': recv_chan}))
|
||||
return recv_chan.receive()
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `publish(self, channel_: str, data: Any) -> None`
|
||||
|
||||
|
||||
|
||||
**Description**: 发布消息
|
||||
|
||||
**Arguments**:
|
||||
> - channel_: 频道
|
||||
> - data: 数据
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def publish(self, channel_: str, data: Any) -> None:
|
||||
"""
|
||||
发布消息
|
||||
Args:
|
||||
channel_: 频道
|
||||
data: 数据
|
||||
|
||||
Returns:
|
||||
"""
|
||||
self.active_chan.send(('publish', {'channel': channel_, 'data': data}))
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_subscriber_receive(self, channel_: str) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]`
|
||||
|
||||
|
||||
|
||||
**Description**: 订阅者接收消息时的回调
|
||||
|
||||
**Arguments**:
|
||||
> - channel_: 频道
|
||||
|
||||
**Return**: 装饰器
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_subscriber_receive(self, channel_: str) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]:
|
||||
"""
|
||||
订阅者接收消息时的回调
|
||||
Args:
|
||||
channel_: 频道
|
||||
|
||||
Returns:
|
||||
装饰器
|
||||
"""
|
||||
if IS_MAIN_PROCESS and (not self.is_main_receive_loop_running):
|
||||
threading.Thread(target=self._start_receive_loop, daemon=True).start()
|
||||
shared_memory.is_main_receive_loop_running = True
|
||||
elif not IS_MAIN_PROCESS and (not self.is_sub_receive_loop_running):
|
||||
threading.Thread(target=self._start_receive_loop, daemon=True).start()
|
||||
shared_memory.is_sub_receive_loop_running = True
|
||||
|
||||
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
|
||||
return decorator
|
||||
```
|
||||
</details>
|
||||
|
||||
### `@staticmethod`
|
||||
### *method* `run_subscriber_receive_funcs(channel_: str, data: Any)`
|
||||
|
||||
|
||||
|
||||
**Description**: 运行订阅者接收函数
|
||||
|
||||
**Arguments**:
|
||||
> - channel_: 频道
|
||||
> - data: 数据
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```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_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_in_thread(*[func(data) for func in _on_sub_subscriber_receive_funcs[channel_]])
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `_start_receive_loop(self)`
|
||||
|
||||
|
||||
|
||||
**Description**: 启动发布订阅接收器循环,在主进程中运行,若有子进程订阅则推送给子进程
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```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'])
|
||||
```
|
||||
</details>
|
||||
|
||||
### **class** `GlobalKeyValueStore`
|
||||
### `@classmethod`
|
||||
### *method* `get_instance(cls)`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def get_instance(cls):
|
||||
if cls._instance is None:
|
||||
with cls._lock:
|
||||
if cls._instance is None:
|
||||
cls._instance = KeyValueStore()
|
||||
return cls._instance
|
||||
```
|
||||
</details>
|
||||
|
||||
### ***var*** `_on_main_subscriber_receive_funcs = {}`
|
||||
|
||||
- **Type**: `dict[str, list[ASYNC_ON_RECEIVE_FUNC]]`
|
||||
|
||||
- **Description**: 主进程订阅者接收函数
|
||||
|
||||
### ***var*** `_on_sub_subscriber_receive_funcs = {}`
|
||||
|
||||
- **Type**: `dict[str, list[ASYNC_ON_RECEIVE_FUNC]]`
|
||||
|
||||
- **Description**: 子进程订阅者接收函数
|
||||
|
||||
### ***var*** `shared_memory = GlobalKeyValueStore.get_instance()`
|
||||
|
||||
- **Type**: `KeyValueStore`
|
||||
|
@ -1,196 +0,0 @@
|
||||
---
|
||||
title: liteyuki.config
|
||||
---
|
||||
### *func* `flat_config() -> dict[str, Any]`
|
||||
|
||||
|
||||
|
||||
**Description**: 扁平化配置文件
|
||||
|
||||
{a:{b:{c:1}}} -> {"a.b.c": 1}
|
||||
|
||||
**Arguments**:
|
||||
> - config: 配置项目
|
||||
|
||||
**Return**: 扁平化后的配置文件,但也包含原有的键值对
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def flat_config(config: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
扁平化配置文件
|
||||
|
||||
{a:{b:{c:1}}} -> {"a.b.c": 1}
|
||||
Args:
|
||||
config: 配置项目
|
||||
|
||||
Returns:
|
||||
扁平化后的配置文件,但也包含原有的键值对
|
||||
"""
|
||||
new_config = copy.deepcopy(config)
|
||||
for key, value in config.items():
|
||||
if isinstance(value, dict):
|
||||
for k, v in flat_config(value).items():
|
||||
new_config[f'{key}.{k}'] = v
|
||||
return new_config
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `load_from_yaml() -> dict[str, Any]`
|
||||
|
||||
|
||||
|
||||
**Description**: Load config from yaml file
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
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'))
|
||||
return flat_config(config if config is not None else {})
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `load_from_json() -> dict[str, Any]`
|
||||
|
||||
|
||||
|
||||
**Description**: Load config from json file
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
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'))
|
||||
return flat_config(config if config is not None else {})
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `load_from_toml() -> dict[str, Any]`
|
||||
|
||||
|
||||
|
||||
**Description**: Load config from toml file
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
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'))
|
||||
return flat_config(config if config is not None else {})
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `load_from_files(*, no_warning: bool = False) -> dict[str, Any]`
|
||||
|
||||
|
||||
|
||||
**Description**: 从指定文件加载配置项,会自动识别文件格式
|
||||
默认执行扁平化选项
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def load_from_files(*files: str, no_warning: bool=False) -> dict[str, Any]:
|
||||
"""
|
||||
从指定文件加载配置项,会自动识别文件格式
|
||||
默认执行扁平化选项
|
||||
"""
|
||||
config = {}
|
||||
for file in files:
|
||||
if os.path.exists(file):
|
||||
if file.endswith(('.yaml', 'yml')):
|
||||
config.update(load_from_yaml(file))
|
||||
elif file.endswith('.json'):
|
||||
config.update(load_from_json(file))
|
||||
elif file.endswith('.toml'):
|
||||
config.update(load_from_toml(file))
|
||||
elif not no_warning:
|
||||
logger.warning(f'Unsupported config file format: {file}')
|
||||
elif not no_warning:
|
||||
logger.warning(f'Config file not found: {file}')
|
||||
return config
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `load_configs_from_dirs(*, no_waring: bool = False) -> dict[str, Any]`
|
||||
|
||||
|
||||
|
||||
**Description**: 从目录下加载配置文件,不递归
|
||||
按照读取文件的优先级反向覆盖
|
||||
默认执行扁平化选项
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def load_configs_from_dirs(*directories: str, no_waring: bool=False) -> dict[str, Any]:
|
||||
"""
|
||||
从目录下加载配置文件,不递归
|
||||
按照读取文件的优先级反向覆盖
|
||||
默认执行扁平化选项
|
||||
"""
|
||||
config = {}
|
||||
for directory in directories:
|
||||
if not os.path.exists(directory):
|
||||
if not no_waring:
|
||||
logger.warning(f'Directory not found: {directory}')
|
||||
continue
|
||||
for file in os.listdir(directory):
|
||||
if file.endswith(_SUPPORTED_CONFIG_FORMATS):
|
||||
config.update(load_from_files(os.path.join(directory, file), no_warning=no_waring))
|
||||
return config
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `load_config_in_default(no_waring: bool = False) -> dict[str, Any]`
|
||||
|
||||
|
||||
|
||||
**Description**: 从一个标准的轻雪项目加载配置文件
|
||||
项目目录下的config.*和config目录下的所有配置文件
|
||||
项目目录下的配置文件优先
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def load_config_in_default(no_waring: bool=False) -> dict[str, Any]:
|
||||
"""
|
||||
从一个标准的轻雪项目加载配置文件
|
||||
项目目录下的config.*和config目录下的所有配置文件
|
||||
项目目录下的配置文件优先
|
||||
"""
|
||||
config = load_configs_from_dirs('config', no_waring=no_waring)
|
||||
config.update(load_from_files('config.yaml', 'config.toml', 'config.json', 'config.yml', no_warning=no_waring))
|
||||
return config
|
||||
```
|
||||
</details>
|
||||
|
@ -1,3 +0,0 @@
|
||||
---
|
||||
title: liteyuki.core
|
||||
---
|
@ -1,235 +0,0 @@
|
||||
---
|
||||
title: liteyuki.core.manager
|
||||
---
|
||||
### **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]])`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```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]]):
|
||||
self.active = active
|
||||
self.passive = passive
|
||||
self.channel_deliver_active = channel_deliver_active
|
||||
self.channel_deliver_passive = channel_deliver_passive
|
||||
self.publish = publish
|
||||
```
|
||||
</details>
|
||||
|
||||
### **class** `ProcessManager`
|
||||
### *method* `__init__(self, lifespan: Lifespan)`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def __init__(self, lifespan: 'Lifespan'):
|
||||
self.lifespan = lifespan
|
||||
self.targets: dict[str, tuple[Callable, tuple, dict]] = {}
|
||||
self.processes: dict[str, Process] = {}
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `start(self, name: str)`
|
||||
|
||||
|
||||
|
||||
**Description**: 开启后自动监控进程,并添加到进程字典中
|
||||
|
||||
**Arguments**:
|
||||
> - name:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def start(self, name: str):
|
||||
"""
|
||||
开启后自动监控进程,并添加到进程字典中
|
||||
Args:
|
||||
name:
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if name not in self.targets:
|
||||
raise KeyError(f'Process {name} not found.')
|
||||
chan_active = get_channel(f'{name}-active')
|
||||
|
||||
def _start_process():
|
||||
process = Process(target=self.targets[name][0], args=self.targets[name][1], kwargs=self.targets[name][2], daemon=True)
|
||||
self.processes[name] = process
|
||||
process.start()
|
||||
_start_process()
|
||||
while True:
|
||||
data = chan_active.receive()
|
||||
if data == 0:
|
||||
logger.info(f'Stopping process {name}')
|
||||
self.lifespan.before_process_shutdown()
|
||||
self.terminate(name)
|
||||
break
|
||||
elif data == 1:
|
||||
logger.info(f'Restarting process {name}')
|
||||
self.lifespan.before_process_shutdown()
|
||||
self.lifespan.before_process_restart()
|
||||
self.terminate(name)
|
||||
_start_process()
|
||||
continue
|
||||
else:
|
||||
logger.warning('Unknown data received, ignored.')
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `start_all(self)`
|
||||
|
||||
|
||||
|
||||
**Description**: 启动所有进程
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def start_all(self):
|
||||
"""
|
||||
启动所有进程
|
||||
"""
|
||||
for name in self.targets:
|
||||
threading.Thread(target=self.start, args=(name,), daemon=True).start()
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `add_target(self, name: str, target: TARGET_FUNC, args: tuple = (), kwargs = None)`
|
||||
|
||||
|
||||
|
||||
**Description**: 添加进程
|
||||
|
||||
**Arguments**:
|
||||
> - name: 进程名,用于获取和唯一标识
|
||||
> - target: 进程函数
|
||||
> - args: 进程函数参数
|
||||
> - kwargs: 进程函数关键字参数,通常会默认传入chan_active和chan_passive
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def add_target(self, name: str, target: TARGET_FUNC, args: tuple=(), kwargs=None):
|
||||
"""
|
||||
添加进程
|
||||
Args:
|
||||
name: 进程名,用于获取和唯一标识
|
||||
target: 进程函数
|
||||
args: 进程函数参数
|
||||
kwargs: 进程函数关键字参数,通常会默认传入chan_active和chan_passive
|
||||
"""
|
||||
if kwargs is None:
|
||||
kwargs = {}
|
||||
chan_active: Channel = Channel(_id=f'{name}-active')
|
||||
chan_passive: Channel = Channel(_id=f'{name}-passive')
|
||||
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)
|
||||
self.targets[name] = (_delivery_channel_wrapper, (target, channel_deliver, shared_memory, *args), kwargs)
|
||||
set_channels({f'{name}-active': chan_active, f'{name}-passive': chan_passive})
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `join_all(self)`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def join_all(self):
|
||||
for name, process in self.targets:
|
||||
process.join()
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `terminate(self, name: str)`
|
||||
|
||||
|
||||
|
||||
**Description**: 终止进程并从进程字典中删除
|
||||
|
||||
**Arguments**:
|
||||
> - name:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def terminate(self, name: str):
|
||||
"""
|
||||
终止进程并从进程字典中删除
|
||||
Args:
|
||||
name:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if name not in self.processes:
|
||||
logger.warning(f'Process {name} not found.')
|
||||
return
|
||||
process = self.processes[name]
|
||||
process.terminate()
|
||||
process.join(TIMEOUT)
|
||||
if process.is_alive():
|
||||
process.kill()
|
||||
logger.success(f'Process {name} terminated.')
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `terminate_all(self)`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def terminate_all(self):
|
||||
for name in self.targets:
|
||||
self.terminate(name)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `is_process_alive(self, name: str) -> bool`
|
||||
|
||||
|
||||
|
||||
**Description**: 检查进程是否存活
|
||||
|
||||
**Arguments**:
|
||||
> - name:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def is_process_alive(self, name: str) -> bool:
|
||||
"""
|
||||
检查进程是否存活
|
||||
Args:
|
||||
name:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if name not in self.targets:
|
||||
logger.warning(f'Process {name} not found.')
|
||||
return self.processes[name].is_alive()
|
||||
```
|
||||
</details>
|
||||
|
||||
### ***var*** `TARGET_FUNC = Callable[..., Any]`
|
||||
|
||||
- **Type**: `TypeAlias`
|
||||
|
@ -1,3 +0,0 @@
|
||||
---
|
||||
title: liteyuki.dev
|
||||
---
|
@ -1,152 +0,0 @@
|
||||
---
|
||||
title: liteyuki.dev.observer
|
||||
---
|
||||
### *func* `debounce()`
|
||||
|
||||
|
||||
|
||||
**Description**: 防抖函数
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def debounce(wait):
|
||||
"""
|
||||
防抖函数
|
||||
"""
|
||||
|
||||
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
|
||||
return decorator
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `on_file_system_event(directories: tuple[str] = True, recursive: bool = None) -> Callable[[CALLBACK_FUNC], CALLBACK_FUNC]`
|
||||
|
||||
|
||||
|
||||
**Description**: 注册文件系统变化监听器
|
||||
|
||||
**Arguments**:
|
||||
> - directories: 监听目录们
|
||||
> - recursive: 是否递归监听子目录
|
||||
> - event_filter: 事件过滤器, 返回True则执行回调函数
|
||||
|
||||
**Return**: 装饰器,装饰一个函数在接收到数据后执行
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_file_system_event(directories: tuple[str], recursive: bool=True, event_filter: FILTER_FUNC=None) -> Callable[[CALLBACK_FUNC], CALLBACK_FUNC]:
|
||||
"""
|
||||
注册文件系统变化监听器
|
||||
Args:
|
||||
directories: 监听目录们
|
||||
recursive: 是否递归监听子目录
|
||||
event_filter: 事件过滤器, 返回True则执行回调函数
|
||||
Returns:
|
||||
装饰器,装饰一个函数在接收到数据后执行
|
||||
"""
|
||||
|
||||
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
|
||||
return decorator
|
||||
```
|
||||
</details>
|
||||
|
||||
### **class** `CodeModifiedHandler(FileSystemEventHandler)`
|
||||
### `@debounce(1)`
|
||||
### *method* `on_modified(self, event)`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
@debounce(1)
|
||||
def on_modified(self, event):
|
||||
raise NotImplementedError('on_modified must be implemented')
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_created(self, event)`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_created(self, event):
|
||||
self.on_modified(event)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_deleted(self, event)`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_deleted(self, event):
|
||||
self.on_modified(event)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_moved(self, event)`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_moved(self, event):
|
||||
self.on_modified(event)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `on_any_event(self, event)`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def on_any_event(self, event):
|
||||
self.on_modified(event)
|
||||
```
|
||||
</details>
|
||||
|
||||
### ***var*** `CALLBACK_FUNC = Callable[[FileSystemEvent], None]`
|
||||
|
||||
- **Type**: `TypeAlias`
|
||||
|
||||
- **Description**: 位置1为FileSystemEvent
|
||||
|
||||
### ***var*** `FILTER_FUNC = Callable[[FileSystemEvent], bool]`
|
||||
|
||||
- **Type**: `TypeAlias`
|
||||
|
||||
- **Description**: 位置1为FileSystemEvent
|
||||
|
@ -1,32 +0,0 @@
|
||||
---
|
||||
title: liteyuki.dev.plugin
|
||||
---
|
||||
### *func* `run_plugins()`
|
||||
|
||||
|
||||
|
||||
**Description**: 运行插件,无需手动初始化bot
|
||||
|
||||
**Arguments**:
|
||||
> - module_path: 插件路径,参考`liteyuki.load_plugin`的函数签名
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def run_plugins(*module_path: str | Path):
|
||||
"""
|
||||
运行插件,无需手动初始化bot
|
||||
Args:
|
||||
module_path: 插件路径,参考`liteyuki.load_plugin`的函数签名
|
||||
"""
|
||||
cfg = load_config_in_default()
|
||||
plugins = cfg.get('liteyuki.plugins', [])
|
||||
plugins.extend(module_path)
|
||||
cfg['liteyuki.plugins'] = plugins
|
||||
bot = LiteyukiBot(**cfg)
|
||||
bot.run()
|
||||
```
|
||||
</details>
|
||||
|
@ -1,4 +0,0 @@
|
||||
---
|
||||
title: liteyuki.exception
|
||||
---
|
||||
### **class** `LiteyukiException(BaseException)`
|
@ -1,54 +0,0 @@
|
||||
---
|
||||
title: liteyuki.log
|
||||
---
|
||||
### *func* `get_format() -> str`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def get_format(level: str) -> str:
|
||||
if level == 'DEBUG':
|
||||
return debug_format
|
||||
else:
|
||||
return default_format
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `init_log()`
|
||||
|
||||
|
||||
|
||||
**Description**: 在语言加载完成后执行
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def init_log(config: dict):
|
||||
"""
|
||||
在语言加载完成后执行
|
||||
Returns:
|
||||
|
||||
"""
|
||||
logger.remove()
|
||||
logger.add(sys.stdout, level=0, diagnose=False, format=get_format(config.get('log_level', 'INFO')))
|
||||
show_icon = config.get('log_icon', True)
|
||||
logger.level('DEBUG', color='<blue>', icon=f"{('🐛' if show_icon else '')}DEBUG")
|
||||
logger.level('INFO', color='<normal>', icon=f"{('ℹ️' if show_icon else '')}INFO")
|
||||
logger.level('SUCCESS', color='<green>', icon=f"{('✅' if show_icon else '')}SUCCESS")
|
||||
logger.level('WARNING', color='<yellow>', icon=f"{('⚠️' if show_icon else '')}WARNING")
|
||||
logger.level('ERROR', color='<red>', icon=f"{('⭕' if show_icon else '')}ERROR")
|
||||
```
|
||||
</details>
|
||||
|
||||
### ***var*** `debug_format = '<c>{time:YYYY-MM-DD HH:mm:ss}</c> <lvl>[{level.icon}]</lvl> <c><{name}.{module}.{function}:{line}></c> {message}'`
|
||||
|
||||
- **Type**: `str`
|
||||
|
||||
### ***var*** `default_format = '<c>{time:MM-DD HH:mm:ss}</c> <lvl>[{level.icon}]</lvl> <c><{name}></c> {message}'`
|
||||
|
||||
- **Type**: `str`
|
||||
|
@ -1,3 +0,0 @@
|
||||
---
|
||||
title: liteyuki.message
|
||||
---
|
@ -1,71 +0,0 @@
|
||||
---
|
||||
title: liteyuki.message.event
|
||||
---
|
||||
### **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)`
|
||||
|
||||
|
||||
|
||||
**Description**: 轻雪抽象消息事件
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
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: 附加数据
|
||||
"""
|
||||
if data is None:
|
||||
data = {}
|
||||
self.message_type = message_type
|
||||
self.data = data
|
||||
self.bot_id = bot_id
|
||||
self.message = message
|
||||
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
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `reply(self, message: str | dict[str, Any])`
|
||||
|
||||
|
||||
|
||||
**Description**: 回复消息
|
||||
|
||||
**Arguments**:
|
||||
> - message:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def reply(self, message: str | dict[str, Any]):
|
||||
"""
|
||||
回复消息
|
||||
Args:
|
||||
message:
|
||||
Returns:
|
||||
"""
|
||||
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='_')
|
||||
shared_memory.publish(self.receive_channel, reply_event)
|
||||
```
|
||||
</details>
|
||||
|
@ -1,97 +0,0 @@
|
||||
---
|
||||
title: liteyuki.message.matcher
|
||||
---
|
||||
### **class** `Matcher`
|
||||
### *method* `__init__(self, rule: Rule, priority: int, block: bool)`
|
||||
|
||||
|
||||
|
||||
**Description**: 匹配器
|
||||
|
||||
**Arguments**:
|
||||
> - rule: 规则
|
||||
> - priority: 优先级 >= 0
|
||||
> - block: 是否阻断后续优先级更低的匹配器
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def __init__(self, rule: Rule, priority: int, block: bool):
|
||||
"""
|
||||
匹配器
|
||||
Args:
|
||||
rule: 规则
|
||||
priority: 优先级 >= 0
|
||||
block: 是否阻断后续优先级更低的匹配器
|
||||
"""
|
||||
self.rule = rule
|
||||
self.priority = priority
|
||||
self.block = block
|
||||
self.handlers: list[EventHandler] = []
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `handle(self) -> Callable[[EventHandler], EventHandler]`
|
||||
|
||||
|
||||
|
||||
**Description**: 添加处理函数,装饰器
|
||||
|
||||
**Return**: 装饰器 handler
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def handle(self) -> Callable[[EventHandler], EventHandler]:
|
||||
"""
|
||||
添加处理函数,装饰器
|
||||
Returns:
|
||||
装饰器 handler
|
||||
"""
|
||||
|
||||
def decorator(handler: EventHandler) -> EventHandler:
|
||||
self.handlers.append(handler)
|
||||
return handler
|
||||
return decorator
|
||||
```
|
||||
</details>
|
||||
|
||||
### *async method* `run(self, event: MessageEvent) -> None`
|
||||
|
||||
|
||||
|
||||
**Description**: 运行处理函数
|
||||
|
||||
**Arguments**:
|
||||
> - event:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```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()
|
||||
```
|
||||
</details>
|
||||
|
||||
### ***var*** `EventHandler = Callable[[MessageEvent], Coroutine[None, None, Any]]`
|
||||
|
||||
- **Type**: `TypeAlias`
|
||||
|
@ -1,46 +0,0 @@
|
||||
---
|
||||
title: liteyuki.message.on
|
||||
---
|
||||
### *func* `on_message(rule: Rule = empty_rule, priority: int = 0, block: bool = False) -> Matcher`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
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:
|
||||
_matcher_list.insert(i, matcher)
|
||||
break
|
||||
else:
|
||||
_matcher_list.append(matcher)
|
||||
return matcher
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `on_keywords(keywords: list[str] = empty_rule, rule = 0, priority: int = False) -> Matcher`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```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)
|
||||
```
|
||||
</details>
|
||||
|
||||
### ***var*** `_matcher_list = []`
|
||||
|
||||
- **Type**: `list[Matcher]`
|
||||
|
||||
### ***var*** `_queue = Queue()`
|
||||
|
||||
- **Type**: `Queue`
|
||||
|
@ -1,98 +0,0 @@
|
||||
---
|
||||
title: liteyuki.message.rule
|
||||
---
|
||||
### `@Rule`
|
||||
### *async func* `empty_rule() -> bool`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
@Rule
|
||||
async def empty_rule(event: MessageEvent) -> bool:
|
||||
return True
|
||||
```
|
||||
</details>
|
||||
|
||||
### `@Rule`
|
||||
### *async func* `is_su_rule() -> bool`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
@Rule
|
||||
async def is_su_rule(event: MessageEvent) -> bool:
|
||||
return str(event.user_id) in _superusers
|
||||
```
|
||||
</details>
|
||||
|
||||
### **class** `Rule`
|
||||
### *method* `__init__(self, handler: RuleHandlerFunc)`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def __init__(self, handler: RuleHandlerFunc):
|
||||
self.handler = handler
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `__or__(self, other: Rule) -> Rule`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```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)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *method* `__and__(self, other: Rule) -> Rule`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```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)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *async method* `__call__(self, event: MessageEvent) -> bool`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
async def __call__(self, event: MessageEvent) -> bool:
|
||||
if self.handler is None:
|
||||
return True
|
||||
return await self.handler(event)
|
||||
```
|
||||
</details>
|
||||
|
||||
### ***var*** `_superusers = get_config('liteyuki.superusers', [])`
|
||||
|
||||
- **Type**: `list[str]`
|
||||
|
||||
### ***var*** `RuleHandlerFunc = Callable[[MessageEvent], Coroutine[None, None, bool]]`
|
||||
|
||||
- **Type**: `TypeAlias`
|
||||
|
||||
- **Description**: 规则函数签名
|
||||
|
@ -1,3 +0,0 @@
|
||||
---
|
||||
title: liteyuki.message.session
|
||||
---
|
@ -1,282 +0,0 @@
|
||||
---
|
||||
title: liteyuki.mkdoc
|
||||
---
|
||||
### *func* `get_relative_path() -> str`
|
||||
|
||||
|
||||
|
||||
**Description**: 获取相对路径
|
||||
|
||||
**Arguments**:
|
||||
> - base_path: 基础路径
|
||||
> - target_path: 目标路径
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def get_relative_path(base_path: str, target_path: str) -> str:
|
||||
"""
|
||||
获取相对路径
|
||||
Args:
|
||||
base_path: 基础路径
|
||||
target_path: 目标路径
|
||||
"""
|
||||
return os.path.relpath(target_path, base_path)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `write_to_files()`
|
||||
|
||||
|
||||
|
||||
**Description**: 输出文件
|
||||
|
||||
**Arguments**:
|
||||
> - file_data: 文件数据 相对路径
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def write_to_files(file_data: dict[str, str]):
|
||||
"""
|
||||
输出文件
|
||||
Args:
|
||||
file_data: 文件数据 相对路径
|
||||
"""
|
||||
for rp, data in file_data.items():
|
||||
if not os.path.exists(os.path.dirname(rp)):
|
||||
os.makedirs(os.path.dirname(rp))
|
||||
with open(rp, 'w', encoding='utf-8') as f:
|
||||
f.write(data)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `get_file_list()`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
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
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `get_module_info_normal(file_path: str = True) -> ModuleInfo`
|
||||
|
||||
|
||||
|
||||
**Description**: 获取函数和类
|
||||
|
||||
**Arguments**:
|
||||
> - file_path: Python 文件路径
|
||||
> - ignore_private: 忽略私有函数和类
|
||||
|
||||
**Return**: 模块信息
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def get_module_info_normal(file_path: str, ignore_private: bool=True) -> ModuleInfo:
|
||||
"""
|
||||
获取函数和类
|
||||
Args:
|
||||
file_path: Python 文件路径
|
||||
ignore_private: 忽略私有函数和类
|
||||
Returns:
|
||||
模块信息
|
||||
"""
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
file_content = file.read()
|
||||
tree = ast.parse(file_content)
|
||||
dot_sep_module_path = file_path.replace(os.sep, '.').replace('.py', '').replace('.pyi', '')
|
||||
module_docstring = ast.get_docstring(tree)
|
||||
module_info = ModuleInfo(module_path=dot_sep_module_path, functions=[], classes=[], attributes=[], docstring=module_docstring if module_docstring else '')
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
if not any((isinstance(parent, ast.ClassDef) for parent in ast.iter_child_nodes(node))) and (not ignore_private or not node.name.startswith('_')):
|
||||
if node.args.args:
|
||||
first_arg = node.args.args[0]
|
||||
if first_arg.arg in ('self', 'cls'):
|
||||
continue
|
||||
function_docstring = ast.get_docstring(node)
|
||||
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))
|
||||
module_info.functions.append(func_info)
|
||||
elif isinstance(node, ast.ClassDef):
|
||||
class_docstring = ast.get_docstring(node)
|
||||
class_info = ClassInfo(name=node.name, docstring=class_docstring if class_docstring else '', methods=[], attributes=[], inherit=[ast.unparse(base) for base in node.bases])
|
||||
for class_node in node.body:
|
||||
if isinstance(class_node, ast.FunctionDef) and (not ignore_private or not class_node.name.startswith('_') or class_node.name == '__init__'):
|
||||
method_docstring = ast.get_docstring(class_node)
|
||||
def_type = DefType.METHOD
|
||||
if class_node.decorator_list:
|
||||
if any((isinstance(decorator, ast.Name) and decorator.id == 'staticmethod' for decorator in class_node.decorator_list)):
|
||||
def_type = DefType.STATIC_METHOD
|
||||
elif any((isinstance(decorator, ast.Name) and decorator.id == 'classmethod' for decorator in class_node.decorator_list)):
|
||||
def_type = DefType.CLASS_METHOD
|
||||
elif any((isinstance(decorator, ast.Name) and decorator.id == 'property' for decorator in class_node.decorator_list)):
|
||||
def_type = DefType.PROPERTY
|
||||
class_info.methods.append(FunctionInfo(name=class_node.name, args=[(arg.arg, ast.unparse(arg.annotation) if arg.annotation else NO_TYPE_ANY) for arg in class_node.args.args], return_type=ast.unparse(class_node.returns) if class_node.returns else 'None', docstring=method_docstring if method_docstring else '', type=def_type, is_async=isinstance(class_node, ast.AsyncFunctionDef), source_code=ast.unparse(class_node)))
|
||||
elif isinstance(class_node, ast.Assign):
|
||||
for target in class_node.targets:
|
||||
if isinstance(target, ast.Name):
|
||||
class_info.attributes.append(AttributeInfo(name=target.id, type=ast.unparse(class_node.value)))
|
||||
module_info.classes.append(class_info)
|
||||
elif isinstance(node, ast.Assign):
|
||||
if not any((isinstance(parent, (ast.ClassDef, ast.FunctionDef)) for parent in ast.iter_child_nodes(node))):
|
||||
for target in node.targets:
|
||||
if isinstance(target, ast.Name) and (not ignore_private or not target.id.startswith('_')):
|
||||
attr_type = NO_TYPE_HINT
|
||||
if isinstance(node.value, ast.AnnAssign) and node.value.annotation:
|
||||
attr_type = ast.unparse(node.value.annotation)
|
||||
module_info.attributes.append(AttributeInfo(name=target.id, type=attr_type, value=ast.unparse(node.value) if node.value else None))
|
||||
return module_info
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `generate_markdown(module_info: ModuleInfo = None, front_matter = 'zh-CN') -> str`
|
||||
|
||||
|
||||
|
||||
**Description**: 生成模块的Markdown
|
||||
你可在此自定义生成的Markdown格式
|
||||
|
||||
**Arguments**:
|
||||
> - module_info: 模块信息
|
||||
> - front_matter: 自定义选项title, index, icon, category
|
||||
> - lang: 语言
|
||||
|
||||
**Return**: Markdown 字符串
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
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 字符串
|
||||
"""
|
||||
content = ''
|
||||
front_matter = '---\n' + '\n'.join([f'{k}: {v}' for k, v in front_matter.items()]) + '\n---\n\n'
|
||||
content += front_matter
|
||||
for func in module_info.functions:
|
||||
args_with_type = [f'{arg[0]}: {arg[1]}' if arg[1] else arg[0] for arg in func.args]
|
||||
content += f"### ***{('async ' if func.is_async else '')}def*** `{func.name}({', '.join(args_with_type)}) -> {func.return_type}`\n\n"
|
||||
func.docstring = func.docstring.replace('\n', '\n\n')
|
||||
content += f'{func.docstring}\n\n'
|
||||
content += f'<details>\n<summary>源代码</summary>\n\n```python\n{func.source_code}\n```\n</details>\n\n'
|
||||
for cls in module_info.classes:
|
||||
if cls.inherit:
|
||||
inherit = f"({', '.join(cls.inherit)})" if cls.inherit else ''
|
||||
content += f'### ***class*** `{cls.name}{inherit}`\n\n'
|
||||
else:
|
||||
content += f'### ***class*** `{cls.name}`\n\n'
|
||||
cls.docstring = cls.docstring.replace('\n', '\n\n')
|
||||
content += f'{cls.docstring}\n\n'
|
||||
for method in cls.methods:
|
||||
if method.type != DefType.METHOD:
|
||||
args_with_type = [f'{arg[0]}: {arg[1]}' if arg[1] else arg[0] for arg in method.args]
|
||||
content += f'###   ***@{method.type.value}***\n'
|
||||
else:
|
||||
args_with_type = [f'{arg[0]}: {arg[1]}' if arg[1] and arg[0] != 'self' else arg[0] for arg in method.args]
|
||||
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'
|
||||
if lang == 'zh-CN':
|
||||
TEXT_SOURCE_CODE = '源代码'
|
||||
else:
|
||||
TEXT_SOURCE_CODE = 'Source Code'
|
||||
content += f'<details>\n<summary>{TEXT_SOURCE_CODE}</summary>\n\n```python\n{method.source_code}\n```\n</details>\n\n'
|
||||
for attr in cls.attributes:
|
||||
content += f'###   ***attr*** `{attr.name}: {attr.type}`\n\n'
|
||||
for attr in module_info.attributes:
|
||||
if attr.type == NO_TYPE_HINT:
|
||||
content += f'### ***var*** `{attr.name} = {attr.value}`\n\n'
|
||||
else:
|
||||
content += f'### ***var*** `{attr.name}: {attr.type} = {attr.value}`\n\n'
|
||||
attr.docstring = attr.docstring.replace('\n', '\n\n')
|
||||
content += f'{attr.docstring}\n\n'
|
||||
return content
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `generate_docs(module_folder: str = False, output_dir: str = 'zh-CN', with_top: bool = None)`
|
||||
|
||||
|
||||
|
||||
**Description**: 生成文档
|
||||
|
||||
**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: 语言
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def generate_docs(module_folder: str, output_dir: str, with_top: bool=False, lang: str='zh-CN', ignored_paths=None):
|
||||
"""
|
||||
生成文档
|
||||
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: 语言
|
||||
"""
|
||||
if ignored_paths is None:
|
||||
ignored_paths = []
|
||||
file_data: dict[str, str] = {}
|
||||
file_list = get_file_list(module_folder)
|
||||
shutil.rmtree(output_dir, ignore_errors=True)
|
||||
os.mkdir(output_dir)
|
||||
replace_data = {'__init__': '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)
|
||||
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)
|
||||
module_info = get_module_info_normal(pyfile_path)
|
||||
if 'README' in abs_md_path:
|
||||
front_matter = {'title': module_info.module_path.replace('.__init__', '').replace('_', '\\n'), 'index': 'true', 'icon': 'laptop-code', 'category': 'API'}
|
||||
else:
|
||||
front_matter = {'title': module_info.module_path.replace('_', '\\n'), 'order': '1', 'icon': 'laptop-code', 'category': 'API'}
|
||||
md_content = generate_markdown(module_info, front_matter)
|
||||
print(f'Generate {pyfile_path} -> {abs_md_path}')
|
||||
file_data[abs_md_path] = md_content
|
||||
write_to_files(file_data)
|
||||
```
|
||||
</details>
|
||||
|
||||
### **class** `DefType(Enum)`
|
||||
### **class** `FunctionInfo(BaseModel)`
|
||||
### **class** `AttributeInfo(BaseModel)`
|
||||
### **class** `ClassInfo(BaseModel)`
|
||||
### **class** `ModuleInfo(BaseModel)`
|
@ -1,26 +0,0 @@
|
||||
---
|
||||
title: liteyuki.plugin
|
||||
---
|
||||
### *func* `get_loaded_plugins() -> dict[str, Plugin]`
|
||||
|
||||
|
||||
|
||||
**Description**: 获取已加载的插件
|
||||
|
||||
**Return**: dict[str, Plugin]: 插件字典
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def get_loaded_plugins() -> dict[str, Plugin]:
|
||||
"""
|
||||
获取已加载的插件
|
||||
Returns:
|
||||
dict[str, Plugin]: 插件字典
|
||||
"""
|
||||
return _plugins
|
||||
```
|
||||
</details>
|
||||
|
@ -1,162 +0,0 @@
|
||||
---
|
||||
title: liteyuki.plugin.load
|
||||
---
|
||||
### *func* `load_plugin() -> Optional[Plugin]`
|
||||
|
||||
|
||||
|
||||
**Description**: 加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
|
||||
|
||||
|
||||
**Arguments**:
|
||||
> - module_path: 插件名称 `path.to.your.plugin`
|
||||
> - 或插件路径 `pathlib.Path(path/to/your/plugin)`:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def load_plugin(module_path: str | Path) -> Optional[Plugin]:
|
||||
"""加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
|
||||
|
||||
参数:
|
||||
module_path: 插件名称 `path.to.your.plugin`
|
||||
或插件路径 `pathlib.Path(path/to/your/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)
|
||||
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:
|
||||
logger.opt(colors=True).success(f'Failed to load liteyuki plugin "<r>{module_path}</r>"')
|
||||
traceback.print_exc()
|
||||
return None
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `load_plugins(*, ignore_warning: bool = True) -> set[Plugin]`
|
||||
|
||||
|
||||
|
||||
**Description**: 导入文件夹下多个插件
|
||||
|
||||
|
||||
**Arguments**:
|
||||
> - plugin_dir: 文件夹路径
|
||||
> - ignore_warning: 是否忽略警告,通常是目录不存在或目录为空
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def load_plugins(*plugin_dir: str, ignore_warning: bool=True) -> set[Plugin]:
|
||||
"""导入文件夹下多个插件
|
||||
|
||||
参数:
|
||||
plugin_dir: 文件夹路径
|
||||
ignore_warning: 是否忽略警告,通常是目录不存在或目录为空
|
||||
"""
|
||||
plugins = set()
|
||||
for dir_path in plugin_dir:
|
||||
if not os.path.exists(dir_path):
|
||||
if not ignore_warning:
|
||||
logger.warning(f"Plugins dir '{dir_path}' does not exist.")
|
||||
continue
|
||||
if not os.listdir(dir_path):
|
||||
if not ignore_warning:
|
||||
logger.warning(f"Plugins dir '{dir_path}' is empty.")
|
||||
continue
|
||||
if not os.path.isdir(dir_path):
|
||||
if not ignore_warning:
|
||||
logger.warning(f"Plugins dir '{dir_path}' is not a directory.")
|
||||
continue
|
||||
for f in os.listdir(dir_path):
|
||||
path = Path(os.path.join(dir_path, f))
|
||||
module_name = None
|
||||
if os.path.isfile(path) and f.endswith('.py') and (f != '__init__.py'):
|
||||
module_name = f'{path_to_module_name(Path(dir_path))}.{f[:-3]}'
|
||||
elif os.path.isdir(path) and os.path.exists(os.path.join(path, '__init__.py')):
|
||||
module_name = path_to_module_name(path)
|
||||
if module_name:
|
||||
load_plugin(module_name)
|
||||
if _plugins.get(module_name):
|
||||
plugins.add(_plugins[module_name])
|
||||
return plugins
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `format_display_name() -> str`
|
||||
|
||||
|
||||
|
||||
**Description**: 设置插件名称颜色,根据不同类型插件设置颜色
|
||||
|
||||
**Arguments**:
|
||||
> - display_name: 插件名称
|
||||
> - plugin_type: 插件类型
|
||||
|
||||
**Return**: str: 设置后的插件名称 <y>name</y>
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def format_display_name(display_name: str, plugin_type: PluginType) -> str:
|
||||
"""
|
||||
设置插件名称颜色,根据不同类型插件设置颜色
|
||||
Args:
|
||||
display_name: 插件名称
|
||||
plugin_type: 插件类型
|
||||
|
||||
Returns:
|
||||
str: 设置后的插件名称 <y>name</y>
|
||||
"""
|
||||
color = 'y'
|
||||
match plugin_type:
|
||||
case PluginType.APPLICATION:
|
||||
color = 'm'
|
||||
case PluginType.TEST:
|
||||
color = 'g'
|
||||
case PluginType.MODULE:
|
||||
color = 'e'
|
||||
case PluginType.SERVICE:
|
||||
color = 'c'
|
||||
return f'<{color}>{display_name} [{plugin_type.name}]</{color}>'
|
||||
```
|
||||
</details>
|
||||
|
||||
### ***var*** `_plugins = {}`
|
||||
|
||||
- **Type**: `dict[str, Plugin]`
|
||||
|
||||
### ***var*** `metadata = module.__dict__['__plugin_metadata__']`
|
||||
|
||||
- **Type**: `'PluginMetadata'`
|
||||
|
||||
### ***var*** `metadata = module.__dict__['__liteyuki_plugin_meta__']`
|
||||
|
||||
- **Type**: `'PluginMetadata'`
|
||||
|
||||
### ***var*** `metadata = module.__dict__['__plugin_meta__']`
|
||||
|
||||
- **Type**: `'PluginMetadata'`
|
||||
|
@ -1,3 +0,0 @@
|
||||
---
|
||||
title: liteyuki.plugin.manager
|
||||
---
|
@ -1,18 +0,0 @@
|
||||
---
|
||||
title: liteyuki.plugin.model
|
||||
---
|
||||
### **class** `PluginType(Enum)`
|
||||
### **class** `PluginMetadata(BaseModel)`
|
||||
### **class** `Plugin(BaseModel)`
|
||||
### *method* `__hash__(self)`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def __hash__(self):
|
||||
return hash(self.module_name)
|
||||
```
|
||||
</details>
|
||||
|
@ -1,17 +0,0 @@
|
||||
---
|
||||
title: liteyuki.plugins.liteecho
|
||||
---
|
||||
### `@on_startswith(['liteecho'], rule=is_su_rule).handle()`
|
||||
### *async func* `liteecho()`
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
@on_startswith(['liteecho'], rule=is_su_rule).handle()
|
||||
async def liteecho(event: MessageEvent):
|
||||
event.reply(event.raw_message.strip()[8:].strip())
|
||||
```
|
||||
</details>
|
||||
|
@ -1,25 +0,0 @@
|
||||
---
|
||||
title: liteyuki.plugins.plugin_loader
|
||||
---
|
||||
### *func* `default_plugins_loader()`
|
||||
|
||||
|
||||
|
||||
**Description**: 默认插件加载器,应在初始化时调用
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```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)
|
||||
```
|
||||
</details>
|
||||
|
@ -1,168 +0,0 @@
|
||||
---
|
||||
title: liteyuki.utils
|
||||
---
|
||||
### *func* `is_coroutine_callable() -> bool`
|
||||
|
||||
|
||||
|
||||
**Description**: 判断是否为协程可调用对象
|
||||
|
||||
**Arguments**:
|
||||
> - call: 可调用对象
|
||||
|
||||
**Return**: bool: 是否为协程可调用对象
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def is_coroutine_callable(call: Callable[..., Any]) -> bool:
|
||||
"""
|
||||
判断是否为协程可调用对象
|
||||
Args:
|
||||
call: 可调用对象
|
||||
Returns:
|
||||
bool: 是否为协程可调用对象
|
||||
"""
|
||||
if inspect.isroutine(call):
|
||||
return inspect.iscoroutinefunction(call)
|
||||
if inspect.isclass(call):
|
||||
return False
|
||||
func_ = getattr(call, '__call__', None)
|
||||
return inspect.iscoroutinefunction(func_)
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `run_coroutine()`
|
||||
|
||||
|
||||
|
||||
**Description**: 运行协程
|
||||
|
||||
**Arguments**:
|
||||
> - coro:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def run_coroutine(*coro: Coroutine):
|
||||
"""
|
||||
运行协程
|
||||
Args:
|
||||
coro:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
for c in coro:
|
||||
asyncio.ensure_future(c)
|
||||
else:
|
||||
for c in coro:
|
||||
loop.run_until_complete(c)
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(asyncio.gather(*coro))
|
||||
loop.close()
|
||||
except Exception as e:
|
||||
logger.error(f'Exception occurred: {e}')
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `run_coroutine_in_thread()`
|
||||
|
||||
|
||||
|
||||
**Description**: 在新线程中运行协程
|
||||
|
||||
**Arguments**:
|
||||
> - coro:
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def run_coroutine_in_thread(*coro: Coroutine):
|
||||
"""
|
||||
在新线程中运行协程
|
||||
Args:
|
||||
coro:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
threading.Thread(target=run_coroutine, args=coro, daemon=True).start()
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `path_to_module_name() -> str`
|
||||
|
||||
|
||||
|
||||
**Description**: 转换路径为模块名
|
||||
|
||||
**Arguments**:
|
||||
> - path: 路径a/b/c/d -> a.b.c.d
|
||||
|
||||
**Return**: str: 模块名
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def path_to_module_name(path: Path) -> str:
|
||||
"""
|
||||
转换路径为模块名
|
||||
Args:
|
||||
path: 路径a/b/c/d -> a.b.c.d
|
||||
Returns:
|
||||
str: 模块名
|
||||
"""
|
||||
rel_path = path.resolve().relative_to(Path.cwd().resolve())
|
||||
if rel_path.stem == '__init__':
|
||||
return '.'.join(rel_path.parts[:-1])
|
||||
else:
|
||||
return '.'.join(rel_path.parts[:-1] + (rel_path.stem,))
|
||||
```
|
||||
</details>
|
||||
|
||||
### *func* `async_wrapper() -> Callable[..., Coroutine]`
|
||||
|
||||
|
||||
|
||||
**Description**: 异步包装器
|
||||
|
||||
**Arguments**:
|
||||
> - func: Sync Callable
|
||||
|
||||
**Return**: Coroutine: Asynchronous Callable
|
||||
|
||||
|
||||
<details>
|
||||
<summary> <b>Source code</b> </summary>
|
||||
|
||||
```python
|
||||
def async_wrapper(func: Callable[..., Any]) -> Callable[..., Coroutine]:
|
||||
"""
|
||||
异步包装器
|
||||
Args:
|
||||
func: Sync Callable
|
||||
Returns:
|
||||
Coroutine: Asynchronous Callable
|
||||
"""
|
||||
|
||||
async def wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
wrapper.__signature__ = inspect.signature(func)
|
||||
return wrapper
|
||||
```
|
||||
</details>
|
||||
|
@ -1,99 +0,0 @@
|
||||
---
|
||||
title: 进程通信
|
||||
icon: exchange-alt
|
||||
order: 4
|
||||
category: 开发
|
||||
---
|
||||
|
||||
## **通道通信**
|
||||
|
||||
### 简介
|
||||
|
||||
轻雪运行在主进程 MainProcess 里,其他插件框架进程是伴随的子进程,因此无法通过内存共享和直接对象传递的方式进行通信,轻雪提供了一个通道`Channel`用于跨进程通信,你可以通过`Channel`发送消息给其他进程,也可以监听其他进程的消息。
|
||||
|
||||
例如子进程接收到用户信息需要重启机器人,这时可以通过通道对主进程发送消息,主进程接收到消息后重启对应子进程。
|
||||
|
||||
### 示例
|
||||
|
||||
通道是全双工的,有两种接收模式,但一个通道只能使用一种,即被动模式和主动模式,被动模式由`chan.on_receive()`装饰回调函数实现,主动模式需调用`chan.receive()`实现
|
||||
|
||||
- 创建子进程的同时会初始化一个被动通道和一个主动通道,且通道标识为`{process_name}-active`和`{process_name}-passive`,
|
||||
- 主进程中通过`get_channel`函数获取通道对象
|
||||
- 子进程中导入单例`active_channel`及`passive_channel`即可
|
||||
|
||||
> 在轻雪插件中(主进程中)
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from liteyuki.comm import get_channel, Channel
|
||||
from liteyuki import get_bot
|
||||
|
||||
# get_channel函数获取通道对象,参数为调用set_channel时的通道标识
|
||||
channel_passive = get_channel("nonebot-passive") # 获取被动通道
|
||||
channel_active = get_channel("nonebot-active") # 获取主动通道
|
||||
liteyuki_bot = get_bot()
|
||||
|
||||
|
||||
# 注册一个函数在轻雪启动后运行
|
||||
@liteyuki_bot.on_after_start
|
||||
async def send_data():
|
||||
while True:
|
||||
channel_passive.send("I am liteyuki main process passive")
|
||||
channel_active.send("I am liteyuki main process active")
|
||||
await asyncio.sleep(3) # 每3秒发送一次消息
|
||||
```
|
||||
|
||||
> 在子进程中(例如NoneBot插件中)
|
||||
|
||||
```python
|
||||
from nonebot import get_driver
|
||||
from liteyuki.comm import active_channel, passive_channel # 子进程中获取通道直接导入进程全局单例即可
|
||||
from liteyuki.log import logger
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
|
||||
# 被动模式,通过装饰器注册一个函数在接收到消息时运行,每次接收到字符串数据时都会运行
|
||||
@passive_channel.on_receive(filter_func=lambda data: isinstance(data, str))
|
||||
async def on_passive_receive(data):
|
||||
logger.info(f"Passive receive: {data}")
|
||||
|
||||
|
||||
# 注册一个函数在NoneBot启动后运行
|
||||
@driver.on_startup
|
||||
def on_startup():
|
||||
while True:
|
||||
data = active_channel.receive()
|
||||
logger.info(f"Active receive: {data}")
|
||||
```
|
||||
|
||||
> 启动后控制台输出
|
||||
|
||||
```log
|
||||
0000-00-00 00:00:00 [ℹ️信息] Passive receive: I am liteyuki main process passive
|
||||
0000-00-00 00:00:00 [ℹ️信息] Active receive: I am liteyuki main process active
|
||||
0000-00-00 00:00:03 [ℹ️信息] Passive receive: I am liteyuki main process passive
|
||||
0000-00-00 00:00:03 [ℹ️信息] Active receive: I am liteyuki main process active
|
||||
...
|
||||
```
|
||||
|
||||
## **共享内存通信**
|
||||
|
||||
### 简介
|
||||
|
||||
- 相比于普通进程通信,内存共享使得代码编写更加简洁,轻雪框架提供了一个内存共享通信的接口,你可以通过`storage`模块实现内存共享通信,该模块封装通道实现
|
||||
- 内存共享是线程安全的,你可以在多个线程中读写共享内存,线程锁会自动保护共享内存的读写操作
|
||||
|
||||
### 示例
|
||||
|
||||
> 在任意进程中均可使用
|
||||
|
||||
```python
|
||||
from liteyuki.comm.storage import shared_memory
|
||||
|
||||
shared_memory.set("key", "value") # 设置共享内存
|
||||
value = shared_memory.get("key") # 获取共享内存
|
||||
```
|
||||
|
||||
源代码:[liteyuki/comm/storage.py](https://github.com/LiteyukiStudio/LiteyukiBot/blob/main/liteyuki/comm/storage.py)
|
@ -1,74 +0,0 @@
|
||||
---
|
||||
title: 轻雪函数
|
||||
icon: code
|
||||
order: 2
|
||||
category: 开发
|
||||
---
|
||||
|
||||
## **轻雪函数**
|
||||
|
||||
轻雪函数 Liteyuki Function 是轻雪的一个功能,它允许你在轻雪中运行一些自定义的由数据驱动的命令,类似于Minecraft的mcfunction,属于资源包的一部分,但需单独起篇幅.
|
||||
|
||||
### **函数文件**
|
||||
|
||||
函数文件放在资源包的`functions`目录下,文件名以`.mcfunction` `.lyfunction` `.lyf`结尾,例如`test.mcfunction`,文件内容为一系列的命令,每行一个命令,支持单行注释`#`(编辑时的语法高亮可采取`shell`格式),例如:
|
||||
|
||||
```shell
|
||||
# 在发信器输出"hello world"
|
||||
cmd echo hello world
|
||||
|
||||
# 如果你想同时输出多行内容可以尝试换行符(Python格式)
|
||||
cmd echo hello world\nLiteyuki bot
|
||||
```
|
||||
|
||||
也支持句末注释,例如:
|
||||
```shell
|
||||
cmd echo hello world # 输出"hello world"
|
||||
```
|
||||
|
||||
### **命令文档**
|
||||
|
||||
```shell
|
||||
var <var1=value1> [var2=value2] ... # 定义变量
|
||||
cmd <command> # 在设备上执行命令
|
||||
api <api_name> [var=value...] # 调用Bot API
|
||||
function <func_name> # 调用函数,可递归
|
||||
sleep <time> # 异步等待,单位s
|
||||
nohup <command> # 使用新的task执行命令,即不等待
|
||||
end # 结束函数关键字,包括子task
|
||||
await # 等待所有异步任务结束,若函数中启动了其他task,需要在最后调用,否则task对象会被销毁
|
||||
```
|
||||
|
||||
|
||||
#### **示例**
|
||||
|
||||
```shell
|
||||
# 疯狂戳好友
|
||||
# 使用 /function poke user_id=123456 执行
|
||||
# 每隔0.2s戳两次,无限戳,会触发最大递归深度限制
|
||||
# 若要戳20s后停止,则需要删除await,添加sleep 20和end
|
||||
api friend_poke user_id=user_id
|
||||
api friend_poke user_id=user_id
|
||||
sleep 0.2
|
||||
nohup function poke
|
||||
await
|
||||
```
|
||||
|
||||
### **API**
|
||||
|
||||
理论上所有基于onebotv11的api都可调用,不同Adapter api也有差别.
|
||||
|
||||
[Onebot v11 API文档](https://283375.github.io/onebot_v11_vitepress/api/index.html)
|
||||
|
||||
### **结束关键字**
|
||||
|
||||
由于LiteyukiBot基于异步运行, 所以在编写lyfunction时也要注意异步的调用,避免出现"单线程走到底"的情况是效率提升的关键.
|
||||
|
||||
`await` 异步任务结束关键字,用于结束当前已完成function的执行
|
||||
|
||||
> [!warning]
|
||||
> 但若出现非单function的情况,有一个task任务没有完成而await被执行了,那么当前所有函数包的task都会被截停销毁
|
||||
|
||||
|
||||
> [!tip]
|
||||
> 编写轻雪函数推荐你使用VS Code插件[Liteyuki Function](https://github.com/LiteyukiStudio/lyfunctionTextmate)实现语法高亮
|
@ -1,82 +0,0 @@
|
||||
---
|
||||
title: 轻雪插件开发
|
||||
icon: laptop-code
|
||||
order: 3
|
||||
category: 开发
|
||||
---
|
||||
|
||||
## 简介
|
||||
|
||||
轻雪插件是轻雪内置的一部分功能,运行在主进程中,可以很高程度地扩展轻雪的功能
|
||||
|
||||
## 开始
|
||||
|
||||
### 创建插件
|
||||
|
||||
一个`.py`文件或一个包含`__init__.py`的文件夹即可被识别为插件
|
||||
|
||||
首先创建一个文件夹,例如`watchdog_plugin`,并在其中创建一个`__init__.py`文件,即可创建一个插件
|
||||
|
||||
`__init__.py`
|
||||
```python
|
||||
from liteyuki.plugin import PluginMetadata, PluginType
|
||||
from .watch_dog import * # 导入逻辑部分
|
||||
|
||||
# 定义插件元数据
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="NoneDog", # 插件名称
|
||||
version="1.0.0", # 插件版本
|
||||
description="A simple plugin for nonebot developer", # 插件描述
|
||||
type=PluginType.SERVICE # 插件类型
|
||||
)
|
||||
|
||||
# 你的插件代码
|
||||
...
|
||||
```
|
||||
|
||||
### 编写逻辑部分
|
||||
|
||||
轻雪主进程不涉及聊天部分,因此插件主要是一些后台任务或者与聊天机器人的通信
|
||||
以下我们会编写一个简单的插件,用于开发NoneBot时进行文件系统变更重载
|
||||
`watch_dog.py`
|
||||
```python
|
||||
import os
|
||||
from liteyuki.dev import observer # 导入文件系统观察器
|
||||
from liteyuki import get_bot, logger # 导入轻雪Bot和日志
|
||||
from watchdog.events import FileSystemEvent # 导入文件系统事件
|
||||
|
||||
liteyuki = get_bot() # 获取唯一的轻雪Bot实例
|
||||
|
||||
exclude_extensions = (".pyc", ".pyo") # 排除的文件扩展名
|
||||
|
||||
|
||||
# 用observer的on_file_system_event装饰器监听文件系统事件
|
||||
@observer.on_file_system_event(
|
||||
directories=("src/nonebot_plugins",),
|
||||
event_filter=lambda event: not event.src_path.endswith(exclude_extensions) and ("__pycache__" not in event.src_path) and os.path.isfile(event.src_path)
|
||||
)
|
||||
def restart_nonebot_process(event: FileSystemEvent):
|
||||
logger.debug(f"File {event.src_path} changed, reloading nonebot...")
|
||||
liteyuki.restart_process("nonebot") # 调用重启进程方法
|
||||
```
|
||||
|
||||
### 加载插件
|
||||
|
||||
#### 方法1
|
||||
|
||||
- 在配置文件中的`liteyuki.plugins`中添加你的插件路径,例如`watchdog_plugin`,重启轻雪即可加载插件。
|
||||
|
||||
#### 方法2
|
||||
|
||||
- 使用开发工具快速运行插件,无需手动创建实例
|
||||
- 创建入口文件,例如`main.py`,并在其中写入以下代码
|
||||
|
||||
```python
|
||||
from liteyuki.dev.plugin import run_plugins
|
||||
|
||||
run_plugins("watchdog_plugin")
|
||||
```
|
||||
|
||||
然后运行`python main.py`即可启动插件
|
||||
|
||||
启用插件后,我们在src/nonebot_plugins下创建一个文件,例如`test.py`,并在其中写入一些代码,保存后轻雪会自动重载NoneBot进程
|
@ -1,53 +0,0 @@
|
||||
---
|
||||
title: 资源包开发
|
||||
icon: box
|
||||
order: 1
|
||||
category: 开发
|
||||
---
|
||||
|
||||
## 简介
|
||||
|
||||
资源包,亦可根据用途称为主题包、字体包、语言包等,它允许你一定程度上自定义轻雪的外观,并且不用修改源代码
|
||||
|
||||
- [资源/主题商店](/store/)提供了一些资源包供你选择,你也可以自己制作资源包
|
||||
- 资源包的制作很简单,如果你接触过`Minecraft`的资源包,那么你能够很快就上手,仅需按照原有路径进行文件替换即可,讲起打包成一个新的资源包。
|
||||
- 部分内容制作需要一点点前端基础,例如`html`,`css`
|
||||
- 轻雪原版资源包请查看`LiteyukiBot/liteyuki/resources`,可以在此基础上进行修改
|
||||
- 欢迎各位投稿资源包到轻雪资源商店
|
||||
|
||||
请注意,主题包中的html渲染使用Js来规定数据的渲染位置,请确保您所编写的html代码能被Bot解析,否则会导致渲染失败或渲染结果不理想/异常/错位等无法预料的事情发生。推荐在编写html时同时更改对应Js代码,以避免出现无法预料的问题。
|
||||
|
||||
---
|
||||
|
||||
## 加载资源包
|
||||
|
||||
- 资源包通常是以`.zip`格式压缩的,只需要将其解压到根目录`resources`目录下即可,注意不要嵌套文件夹,正常的路径应该是这样的
|
||||
|
||||
```shell
|
||||
main.py
|
||||
resources
|
||||
└─resource_pack_1
|
||||
├─metadata.yml
|
||||
├─templates
|
||||
└───...
|
||||
└─resource_pack_2
|
||||
├─metadata.yml
|
||||
└─...
|
||||
```
|
||||
|
||||
- 你自己制作的资源包也应该遵循这个规则,并且应该在`metadata.yml`中填写一些信息
|
||||
- 若没有`metadata.yml`文件,则该文件夹不会被识别为资源包
|
||||
|
||||
```yaml
|
||||
name: "资源包名称"
|
||||
version: "1.0.0"
|
||||
description: "资源包描述"
|
||||
# 你可以自定义一些信息,但请保证以上三个字段
|
||||
...
|
||||
```
|
||||
|
||||
- 资源包加载遵循一个优先级,即后加载的资源包会覆盖前面的资源包,例如,你在A包中定义了一个`index.html`文件,B包也定义了一个`index.html`文件,那么加载B包后,A包中的`index.html`文件会被覆盖
|
||||
- 对于不同资源包的不同文件,是可以相对引用的,例如你在A中定义了`templates/index.html`,在B中定义了`templates/style.css`,可以在A的`index.html`中用`./style.css`相对路径引用B中的css
|
||||
|
||||
> [!tip]
|
||||
> 资源包的结构会随着轻雪的更新而有变动,第三方资源包开发者需要注意版本兼容性,同时用户也应该自行选择可用的资源包
|
Reference in New Issue
Block a user