mirror of
https://github.com/LiteyukiStudio/LiteyukiBot.git
synced 2025-10-06 05:46:24 +00:00
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
5e454bc971 | |||
70bfb0fcee | |||
13b95c2732 | |||
ef5866343d | |||
d5ccd105a2 | |||
20ad8dc53f | |||
de9c91d8bd | |||
6b64a0c379 | |||
f117da7ff3 | |||
f548a07230 | |||
e2e53c21fa | |||
3eaf23a56b | |||
4a5dd1f727 | |||
c2cb416b4e | |||
5cd528d5e9 | |||
980fca650b | |||
9c525141f6 | |||
3d218a0e8d | |||
db385f597b | |||
98a9d6413a |
5
.github/workflows/pypi-publish.yml
vendored
5
.github/workflows/pypi-publish.yml
vendored
@@ -1,8 +1,9 @@
|
|||||||
name: Publish
|
name: Publish
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
push:
|
||||||
types: [published]
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pypi-publish:
|
pypi-publish:
|
||||||
|
@@ -25,6 +25,10 @@
|
|||||||
### 感谢
|
### 感谢
|
||||||
- 所有贡献者们
|
- 所有贡献者们
|
||||||
|
|
||||||
|
### 参考及鸣谢
|
||||||
|
- [nonebot-plugin-uninfo](https://github.com/RF-Tar-Railt/nonebot-plugin-uninfo)为会话部分用户信息提供了参考
|
||||||
|
- [nonebot-plugin-alconna](https://github.com/nonebot/plugin-alconna/)为消息部分提供了参考
|
||||||
|
|
||||||
|
|
||||||
[OneBot]: https://img.shields.io/badge/OneBot-11/12-blue?style=for-the-badge
|
[OneBot]: https://img.shields.io/badge/OneBot-11/12-blue?style=for-the-badge
|
||||||
|
|
||||||
|
@@ -6,3 +6,4 @@ nonebot:
|
|||||||
default_language: zh
|
default_language: zh
|
||||||
driver: ~fastapi+~httpx+~websockets
|
driver: ~fastapi+~httpx+~websockets
|
||||||
alconna_use_command_start: true
|
alconna_use_command_start: true
|
||||||
|
gotify_token: "empty token"
|
@@ -9,13 +9,23 @@ order: 1
|
|||||||
1. Install [`Git`](https://git-scm.com/download/) and [`Python3.10+`](https://www.python.org/downloads/release/python-31010/) Environment.
|
1. Install [`Git`](https://git-scm.com/download/) and [`Python3.10+`](https://www.python.org/downloads/release/python-31010/) Environment.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone the project
|
# Clone Repo
|
||||||
git clone https://github.com/LiteyukiStudio/LiteyukiBot --depth=1
|
git clone https://github.com/LiteyukiStudio/LiteyukiBot --depth=1
|
||||||
# change directory
|
|
||||||
|
# Change directory
|
||||||
cd LiteyukiBot
|
cd LiteyukiBot
|
||||||
# install dependencies
|
|
||||||
|
# Create virtual environment
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
|
# Activate virtual environment
|
||||||
|
.\venv\Scripts\activate # Windows
|
||||||
|
source venv/bin/activate # Linux
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
# start the bot!
|
|
||||||
|
# Run Liteyuki
|
||||||
python main.py
|
python main.py
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -37,9 +47,6 @@ python main.py
|
|||||||
> If you are using Windows, please use the absolute project directory `/path/to/LiteyukiBot` instead of `$(pwd)` <br>
|
> If you are using Windows, please use the absolute project directory `/path/to/LiteyukiBot` instead of `$(pwd)` <br>
|
||||||
> If you have modified the port number, please replace `20216:20216` with your port number
|
> If you have modified the port number, please replace `20216:20216` with your port number
|
||||||
|
|
||||||
## **Use TRSS Script**
|
|
||||||
[TRSS_Liteyuki Management Script](https://timerainstarsky.github.io/TRSS_Liteyuki/), which provides a more convenient way to manage LiteyukiBot, recommended to use `Arch Linux`
|
|
||||||
|
|
||||||
|
|
||||||
## **Device Requirements**
|
## **Device Requirements**
|
||||||
- Windows system version minimum `Windows10+`/`Windows Server 2019+`
|
- Windows system version minimum `Windows10+`/`Windows Server 2019+`
|
||||||
|
2248
docs/pnpm-lock.yaml
generated
2248
docs/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ order: 2
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
nonebot:
|
nonebot:
|
||||||
# Nonebot机器人的配置,以前的最外层配置项仍可为Nonebot服务,但是部分内容会被覆盖,请尽快迁移
|
# Nonebot机器人的配置,6.3.10版本后,NoneBot下配置已迁移至nonebot键下,不再使用外层配置,但是部分内容会被覆盖,请尽快迁移
|
||||||
command_start: [ "/", "" ] # 指令前缀,若没有""空命令头,请开启alconna_use_command_start保证alconna解析正常
|
command_start: [ "/", "" ] # 指令前缀,若没有""空命令头,请开启alconna_use_command_start保证alconna解析正常
|
||||||
host: 127.0.0.1 # 监听地址,默认为本机,若要接收外部请求请填写0.0.0.0
|
host: 127.0.0.1 # 监听地址,默认为本机,若要接收外部请求请填写0.0.0.0
|
||||||
port: 20216 # 绑定端口
|
port: 20216 # 绑定端口
|
||||||
|
@@ -2,11 +2,13 @@
|
|||||||
title: 安装
|
title: 安装
|
||||||
order: 1
|
order: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
# 安装
|
# 安装
|
||||||
|
|
||||||
## **常规部署**
|
## **常规部署**
|
||||||
|
|
||||||
1. 安装 [`Git`](https://git-scm.com/download/) 和 [`Python3.10+`](https://www.python.org/downloads/release/python-31010/) 环境
|
1. 安装 [`Git`](https://git-scm.com/download/) 和 [
|
||||||
|
`Python3.10+`](https://www.python.org/downloads/release/python-31010/) 环境
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 克隆项目到本地,轻雪使用Git进行版本管理,该步骤为必要项
|
# 克隆项目到本地,轻雪使用Git进行版本管理,该步骤为必要项
|
||||||
@@ -14,15 +16,21 @@ git clone https://github.com/LiteyukiStudio/LiteyukiBot --depth=1 # 若你不能
|
|||||||
|
|
||||||
# 切换到Bot目录下
|
# 切换到Bot目录下
|
||||||
cd LiteyukiBot
|
cd LiteyukiBot
|
||||||
|
|
||||||
|
# 创建虚拟环境
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
|
# 激活虚拟环境
|
||||||
|
.\venv\Scripts\activate # Windows
|
||||||
|
source venv/bin/activate # Linux
|
||||||
|
|
||||||
# 安装依赖
|
# 安装依赖
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
# 启动Bot
|
# 启动Bot
|
||||||
python main.py
|
python main.py
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!tip]
|
|
||||||
> 推荐使用虚拟环境来运行轻雪,以避免依赖冲突,你可以使用`python -m venv .venv`来创建虚拟环境,然后使用`.venv\Scripts\activate`来激活虚拟环境(Linux下使用`source .venv/bin/activate`激活)
|
|
||||||
|
|
||||||
## **使用Docker构建**
|
## **使用Docker构建**
|
||||||
|
|
||||||
1. 安装 [`Docker`](https://docs.docker.com/get-docker/)
|
1. 安装 [`Docker`](https://docs.docker.com/get-docker/)
|
||||||
@@ -35,10 +43,6 @@ python main.py
|
|||||||
> Windows请使用项目绝对目录`/path/to/LiteyukiBot`代替`$(pwd)` <br>
|
> Windows请使用项目绝对目录`/path/to/LiteyukiBot`代替`$(pwd)` <br>
|
||||||
> 若你修改了端口号请将`20216:20216`中的`20216`替换为你的端口号
|
> 若你修改了端口号请将`20216:20216`中的`20216`替换为你的端口号
|
||||||
|
|
||||||
## **使用TRSS Scripts部署**
|
|
||||||
[TRSS_Liteyuki轻雪机器人管理脚本](https://timerainstarsky.github.io/TRSS_Liteyuki/),该功能由TRSS提供支持,不是LiteyukiBot官方提供的功能,推荐使用`Arch Linux`
|
|
||||||
|
|
||||||
|
|
||||||
## **装置要求**
|
## **装置要求**
|
||||||
|
|
||||||
- Windows系统版本最低`Windows10+`/`Windows Server 2019+`
|
- Windows系统版本最低`Windows10+`/`Windows Server 2019+`
|
||||||
@@ -48,7 +52,8 @@ python main.py
|
|||||||
- 硬盘: 至少`1GB`空间
|
- 硬盘: 至少`1GB`空间
|
||||||
|
|
||||||
> [!warning]
|
> [!warning]
|
||||||
> 如果装置上有多个环境,请使用`path/to/python -m pip install -r requirements.txt`来安装依赖,`path/to/python`为你的Python可执行文件路径
|
> 如果装置上有多个环境,请使用`path/to/python -m pip install -r requirements.txt`来安装依赖,`path/to/python`
|
||||||
|
> 为你的Python可执行文件路径
|
||||||
|
|
||||||
> [!warning]
|
> [!warning]
|
||||||
> 轻雪的更新功能依赖Git,如果你没有安装Git直接下载源代码运行,你将无法使用更新功能
|
> 轻雪的更新功能依赖Git,如果你没有安装Git直接下载源代码运行,你将无法使用更新功能
|
||||||
|
@@ -1,25 +1,28 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import atexit
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import signal
|
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from liteyuki.bot.lifespan import (LIFESPAN_FUNC, Lifespan, PROCESS_LIFESPAN_FUNC)
|
from magicoca import Chan
|
||||||
|
|
||||||
|
from liteyuki.bot.lifespan import LIFESPAN_FUNC, Lifespan, PROCESS_LIFESPAN_FUNC
|
||||||
from liteyuki.comm.channel import get_channel
|
from liteyuki.comm.channel import get_channel
|
||||||
from liteyuki.core.manager import ProcessManager
|
from liteyuki.core.manager import ProcessManager
|
||||||
|
# new version
|
||||||
|
from liteyuki.core.manager import sub_process_manager
|
||||||
from liteyuki.log import init_log, logger
|
from liteyuki.log import init_log, logger
|
||||||
from liteyuki.plugin import load_plugin
|
from liteyuki.plugin import load_plugin
|
||||||
|
from liteyuki.session import message_handler_thread
|
||||||
from liteyuki.utils import IS_MAIN_PROCESS
|
from liteyuki.utils import IS_MAIN_PROCESS
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"LiteyukiBot",
|
"LiteyukiBot",
|
||||||
"get_bot",
|
"get_bot",
|
||||||
"get_config",
|
"get_config",
|
||||||
"get_config_with_compat",
|
"get_config_with_compat",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -30,6 +33,10 @@ class LiteyukiBot:
|
|||||||
Args:
|
Args:
|
||||||
**kwargs: 配置
|
**kwargs: 配置
|
||||||
"""
|
"""
|
||||||
|
"""总通道"""
|
||||||
|
self.i_chan = Chan[Any]() # 外部输入通道
|
||||||
|
self.o_chan = Chan[Any]() # 外部输出通道
|
||||||
|
|
||||||
"""常规操作"""
|
"""常规操作"""
|
||||||
print_logo()
|
print_logo()
|
||||||
global _BOT_INSTANCE
|
global _BOT_INSTANCE
|
||||||
@@ -60,8 +67,9 @@ class LiteyukiBot:
|
|||||||
启动逻辑
|
启动逻辑
|
||||||
"""
|
"""
|
||||||
await self.lifespan.before_start() # 启动前钩子
|
await self.lifespan.before_start() # 启动前钩子
|
||||||
|
sub_process_manager.start_all()
|
||||||
await self.lifespan.after_start() # 启动后钩子
|
await self.lifespan.after_start() # 启动后钩子
|
||||||
await self.keep_alive()
|
message_handler_thread([_.ctx.sub_chan for _ in sub_process_manager.processes.values()])
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
@@ -73,19 +81,7 @@ class LiteyukiBot:
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logger.opt(colors=True).info("<y>Liteyuki is stopping...</y>")
|
logger.opt(colors=True).info("<y>Liteyuki is stopping...</y>")
|
||||||
self.stop()
|
self.stop()
|
||||||
logger.opt(colors=True).info("<y>Liteyuki is stopped...</y>")
|
logger.opt(colors=True).info("<y>Liteyuki is stopped !</y>")
|
||||||
|
|
||||||
async def keep_alive(self):
|
|
||||||
"""
|
|
||||||
保持轻雪运行
|
|
||||||
"""
|
|
||||||
logger.info("Liteyuki is keeping alive...")
|
|
||||||
try:
|
|
||||||
while not self.stop_event.is_set():
|
|
||||||
await asyncio.sleep(0.1)
|
|
||||||
except Exception:
|
|
||||||
logger.info("Liteyuki is exiting...")
|
|
||||||
self.stop()
|
|
||||||
|
|
||||||
def restart(self, delay: int = 0):
|
def restart(self, delay: int = 0):
|
||||||
"""
|
"""
|
||||||
@@ -108,7 +104,11 @@ class LiteyukiBot:
|
|||||||
cmd = "nohup"
|
cmd = "nohup"
|
||||||
self.process_manager.terminate_all()
|
self.process_manager.terminate_all()
|
||||||
# 进程退出后重启
|
# 进程退出后重启
|
||||||
threading.Thread(target=os.system, args=(f"{cmd} {executable} {' '.join(args)}",), daemon=True).start()
|
threading.Thread(
|
||||||
|
target=os.system,
|
||||||
|
args=(f"{cmd} {executable} {' '.join(args)}",),
|
||||||
|
daemon=True,
|
||||||
|
).start()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
self.call_restart_count += 1
|
self.call_restart_count += 1
|
||||||
|
|
||||||
@@ -189,7 +189,9 @@ class LiteyukiBot:
|
|||||||
"""
|
"""
|
||||||
return self.lifespan.on_before_process_shutdown(func)
|
return self.lifespan.on_before_process_shutdown(func)
|
||||||
|
|
||||||
def on_before_process_restart(self, func: PROCESS_LIFESPAN_FUNC) -> PROCESS_LIFESPAN_FUNC:
|
def on_before_process_restart(
|
||||||
|
self, func: PROCESS_LIFESPAN_FUNC
|
||||||
|
) -> PROCESS_LIFESPAN_FUNC:
|
||||||
"""
|
"""
|
||||||
注册进程重启前的函数,为子进程重启时调用
|
注册进程重启前的函数,为子进程重启时调用
|
||||||
Args:
|
Args:
|
||||||
@@ -211,7 +213,7 @@ class LiteyukiBot:
|
|||||||
return self.lifespan.on_after_restart(func)
|
return self.lifespan.on_after_restart(func)
|
||||||
|
|
||||||
|
|
||||||
_BOT_INSTANCE: LiteyukiBot
|
_BOT_INSTANCE: LiteyukiBot | None = None
|
||||||
|
|
||||||
|
|
||||||
def get_bot() -> LiteyukiBot:
|
def get_bot() -> LiteyukiBot:
|
||||||
@@ -241,7 +243,9 @@ def get_config(key: str, default: Any = None) -> Any:
|
|||||||
return get_bot().config.get(key, default)
|
return get_bot().config.get(key, default)
|
||||||
|
|
||||||
|
|
||||||
def get_config_with_compat(key: str, compat_keys: tuple[str], default: Any = None) -> Any:
|
def get_config_with_compat(
|
||||||
|
key: str, compat_keys: tuple[str], default: Any = None
|
||||||
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
获取配置,兼容旧版本
|
获取配置,兼容旧版本
|
||||||
Args:
|
Args:
|
||||||
@@ -256,14 +260,18 @@ def get_config_with_compat(key: str, compat_keys: tuple[str], default: Any = Non
|
|||||||
return get_bot().config[key]
|
return get_bot().config[key]
|
||||||
for compat_key in compat_keys:
|
for compat_key in compat_keys:
|
||||||
if compat_key in get_bot().config:
|
if compat_key in get_bot().config:
|
||||||
logger.warning(f"Config key \"{compat_key}\" will be deprecated, use \"{key}\" instead.")
|
logger.warning(
|
||||||
|
f'Config key "{compat_key}" will be deprecated, use "{key}" instead.'
|
||||||
|
)
|
||||||
return get_bot().config[compat_key]
|
return get_bot().config[compat_key]
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
def print_logo():
|
def print_logo():
|
||||||
"""@litedoc-hide"""
|
"""@litedoc-hide"""
|
||||||
print("\033[34m" + r"""
|
print(
|
||||||
|
"\033[34m"
|
||||||
|
+ r"""
|
||||||
__ ______ ________ ________ __ __ __ __ __ __ ______
|
__ ______ ________ ________ __ __ __ __ __ __ ______
|
||||||
/ | / |/ |/ |/ \ / |/ | / |/ | / |/ |
|
/ | / |/ |/ |/ \ / |/ | / |/ | / |/ |
|
||||||
$$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/
|
$$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/
|
||||||
@@ -273,4 +281,6 @@ def print_logo():
|
|||||||
$$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_
|
$$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_
|
||||||
$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |
|
$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |
|
||||||
$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
|
$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
|
||||||
""" + "\033[0m")
|
"""
|
||||||
|
+ "\033[0m"
|
||||||
|
)
|
||||||
|
@@ -4,20 +4,31 @@
|
|||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
from multiprocessing import Pipe
|
from multiprocessing import Pipe
|
||||||
from typing import Any, Callable, Coroutine, Generic, Optional, TypeAlias, TypeVar, get_args
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Callable,
|
||||||
|
Coroutine,
|
||||||
|
Generic,
|
||||||
|
Optional,
|
||||||
|
TypeAlias,
|
||||||
|
TypeVar,
|
||||||
|
get_args,
|
||||||
|
)
|
||||||
|
|
||||||
from liteyuki.log import logger
|
from liteyuki.log import logger
|
||||||
from liteyuki.utils import IS_MAIN_PROCESS, is_coroutine_callable
|
from liteyuki.utils import IS_MAIN_PROCESS, is_coroutine_callable
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
SYNC_ON_RECEIVE_FUNC: TypeAlias = Callable[[T], Any] # 同步接收函数
|
SYNC_ON_RECEIVE_FUNC: TypeAlias = Callable[[T], Any] # 同步接收函数
|
||||||
ASYNC_ON_RECEIVE_FUNC: TypeAlias = Callable[[T], Coroutine[Any, Any, Any]] # 异步接收函数
|
ASYNC_ON_RECEIVE_FUNC: TypeAlias = Callable[
|
||||||
ON_RECEIVE_FUNC: TypeAlias = SYNC_ON_RECEIVE_FUNC | ASYNC_ON_RECEIVE_FUNC # 接收函数
|
[T], Coroutine[Any, Any, Any]
|
||||||
|
] # 异步接收函数
|
||||||
|
ON_RECEIVE_FUNC: TypeAlias = SYNC_ON_RECEIVE_FUNC | ASYNC_ON_RECEIVE_FUNC # 接收函数
|
||||||
|
|
||||||
SYNC_FILTER_FUNC: TypeAlias = Callable[[T], bool] # 同步过滤函数
|
SYNC_FILTER_FUNC: TypeAlias = Callable[[T], bool] # 同步过滤函数
|
||||||
ASYNC_FILTER_FUNC: TypeAlias = Callable[[T], Coroutine[Any, Any, bool]] # 异步过滤函数
|
ASYNC_FILTER_FUNC: TypeAlias = Callable[[T], Coroutine[Any, Any, bool]] # 异步过滤函数
|
||||||
FILTER_FUNC: TypeAlias = SYNC_FILTER_FUNC | ASYNC_FILTER_FUNC # 过滤函数
|
FILTER_FUNC: TypeAlias = SYNC_FILTER_FUNC | ASYNC_FILTER_FUNC # 过滤函数
|
||||||
|
|
||||||
_func_id: int = 0
|
_func_id: int = 0
|
||||||
_channel: dict[str, "Channel"] = {}
|
_channel: dict[str, "Channel"] = {}
|
||||||
@@ -39,7 +50,9 @@ class Channel(Generic[T]):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
self.conn_send, self.conn_recv = Pipe()
|
self.conn_send, self.conn_recv = Pipe()
|
||||||
self._conn_send_inner, self._conn_recv_inner = Pipe() # 内部通道,用于子进程通信
|
self._conn_send_inner, self._conn_recv_inner = (
|
||||||
|
Pipe()
|
||||||
|
) # 内部通道,用于子进程通信
|
||||||
self._closed = False
|
self._closed = False
|
||||||
self._on_main_receive_func_ids: list[int] = []
|
self._on_main_receive_func_ids: list[int] = []
|
||||||
self._on_sub_receive_func_ids: list[int] = []
|
self._on_sub_receive_func_ids: list[int] = []
|
||||||
@@ -62,9 +75,6 @@ class Channel(Generic[T]):
|
|||||||
if name in _channel:
|
if name in _channel:
|
||||||
raise ValueError(f"Channel {name} already exists")
|
raise ValueError(f"Channel {name} already exists")
|
||||||
_channel[name] = self
|
_channel[name] = self
|
||||||
logger.debug(f"Channel {name} initialized in main process")
|
|
||||||
else:
|
|
||||||
logger.debug(f"Channel {name} initialized in sub process, should manually set in main process")
|
|
||||||
|
|
||||||
def _get_generic_type(self) -> Optional[type]:
|
def _get_generic_type(self) -> Optional[type]:
|
||||||
"""
|
"""
|
||||||
@@ -72,7 +82,7 @@ class Channel(Generic[T]):
|
|||||||
Returns:
|
Returns:
|
||||||
Optional[type]: 泛型类型
|
Optional[type]: 泛型类型
|
||||||
"""
|
"""
|
||||||
if hasattr(self, '__orig_class__'):
|
if hasattr(self, "__orig_class__"):
|
||||||
return get_args(self.__orig_class__)[0]
|
return get_args(self.__orig_class__)[0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -98,7 +108,10 @@ class Channel(Generic[T]):
|
|||||||
elif isinstance(structure, dict):
|
elif isinstance(structure, dict):
|
||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
return False
|
return False
|
||||||
return all(k in data and self._validate_structure(data[k], structure[k]) for k in structure)
|
return all(
|
||||||
|
k in data and self._validate_structure(data[k], structure[k])
|
||||||
|
for k in structure
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -113,10 +126,12 @@ class Channel(Generic[T]):
|
|||||||
if self.type_check:
|
if self.type_check:
|
||||||
_type = self._get_generic_type()
|
_type = self._get_generic_type()
|
||||||
if _type is not None and not self._validate_structure(data, _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")
|
raise TypeError(
|
||||||
|
f"Data must be an instance of {_type}, {type(data)} found"
|
||||||
|
)
|
||||||
|
|
||||||
if self._closed:
|
if self._closed:
|
||||||
raise RuntimeError("Cannot send to a closed channel_")
|
raise RuntimeError("Cannot send to a closed channel")
|
||||||
self.conn_send.send(data)
|
self.conn_send.send(data)
|
||||||
|
|
||||||
def receive(self) -> T:
|
def receive(self) -> T:
|
||||||
@@ -126,7 +141,7 @@ class Channel(Generic[T]):
|
|||||||
T: 数据
|
T: 数据
|
||||||
"""
|
"""
|
||||||
if self._closed:
|
if self._closed:
|
||||||
raise RuntimeError("Cannot receive from a closed channel_")
|
raise RuntimeError("Cannot receive from a closed channel")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
data = self.conn_recv.recv()
|
data = self.conn_recv.recv()
|
||||||
@@ -142,7 +157,9 @@ class Channel(Generic[T]):
|
|||||||
data = await loop.run_in_executor(None, self.receive)
|
data = await loop.run_in_executor(None, self.receive)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def on_receive(self, filter_func: Optional[FILTER_FUNC] = None) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]:
|
def on_receive(
|
||||||
|
self, filter_func: Optional[FILTER_FUNC] = None
|
||||||
|
) -> Callable[[Callable[[T], Any]], Callable[[T], Any]]:
|
||||||
"""
|
"""
|
||||||
接收数据并执行函数
|
接收数据并执行函数
|
||||||
Args:
|
Args:
|
||||||
@@ -187,37 +204,52 @@ class Channel(Generic[T]):
|
|||||||
data: 数据
|
data: 数据
|
||||||
"""
|
"""
|
||||||
if IS_MAIN_PROCESS:
|
if IS_MAIN_PROCESS:
|
||||||
[asyncio.create_task(_callback_funcs[func_id](data)) for func_id in self._on_main_receive_func_ids]
|
[
|
||||||
|
asyncio.create_task(_callback_funcs[func_id](data))
|
||||||
|
for func_id in self._on_main_receive_func_ids
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
[asyncio.create_task(_callback_funcs[func_id](data)) for func_id in self._on_sub_receive_func_ids]
|
[
|
||||||
|
asyncio.create_task(_callback_funcs[func_id](data))
|
||||||
|
for func_id in self._on_sub_receive_func_ids
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
"""子进程可用的主动和被动通道"""
|
"""子进程可用的主动和被动通道"""
|
||||||
active_channel: Channel = Channel(name="active_channel") # 主动通道
|
active_channel: Channel = Channel(name="active_channel") # 主动通道
|
||||||
passive_channel: Channel = Channel(name="passive_channel") # 被动通道
|
passive_channel: Channel = Channel(name="passive_channel") # 被动通道
|
||||||
publish_channel: Channel[tuple[str, dict[str, Any]]] = Channel(name="publish_channel") # 发布通道
|
publish_channel: Channel[tuple[str, dict[str, Any]]] = Channel(
|
||||||
|
name="publish_channel"
|
||||||
|
) # 发布通道
|
||||||
"""通道传递通道,主进程创建单例,子进程初始化时实例化"""
|
"""通道传递通道,主进程创建单例,子进程初始化时实例化"""
|
||||||
channel_deliver_active_channel: Channel[Channel[Any]] # 主动通道传递通道
|
channel_deliver_active_channel: Channel[Channel[Any]] # 主动通道传递通道
|
||||||
channel_deliver_passive_channel: Channel[tuple[str, dict[str, Any]]] # 被动通道传递通道
|
channel_deliver_passive_channel: Channel[tuple[str, dict[str, Any]]] # 被动通道传递通道
|
||||||
|
|
||||||
if IS_MAIN_PROCESS:
|
if IS_MAIN_PROCESS:
|
||||||
channel_deliver_active_channel = Channel(name="channel_deliver_active_channel") # 主动通道传递通道
|
channel_deliver_active_channel = Channel(
|
||||||
channel_deliver_passive_channel = Channel(name="channel_deliver_passive_channel") # 被动通道传递通道
|
name="channel_deliver_active_channel"
|
||||||
|
) # 主动通道传递通道
|
||||||
|
channel_deliver_passive_channel = Channel(
|
||||||
|
name="channel_deliver_passive_channel"
|
||||||
|
) # 被动通道传递通道
|
||||||
|
|
||||||
|
@channel_deliver_passive_channel.on_receive(
|
||||||
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == "set_channel")
|
filter_func=lambda data: data[0] == "set_channel"
|
||||||
|
)
|
||||||
def on_set_channel(data: tuple[str, dict[str, Any]]):
|
def on_set_channel(data: tuple[str, dict[str, Any]]):
|
||||||
name, channel = data[1]["name"], data[1]["channel_"]
|
name, channel = data[1]["name"], data[1]["channel_"]
|
||||||
set_channel(name, channel)
|
set_channel(name, channel)
|
||||||
|
|
||||||
|
@channel_deliver_passive_channel.on_receive(
|
||||||
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == "get_channel")
|
filter_func=lambda data: data[0] == "get_channel"
|
||||||
|
)
|
||||||
def on_get_channel(data: tuple[str, dict[str, Any]]):
|
def on_get_channel(data: tuple[str, dict[str, Any]]):
|
||||||
name, recv_chan = data[1]["name"], data[1]["recv_chan"]
|
name, recv_chan = data[1]["name"], data[1]["recv_chan"]
|
||||||
recv_chan.send(get_channel(name))
|
recv_chan.send(get_channel(name))
|
||||||
|
|
||||||
|
@channel_deliver_passive_channel.on_receive(
|
||||||
@channel_deliver_passive_channel.on_receive(filter_func=lambda data: data[0] == "get_channels")
|
filter_func=lambda data: data[0] == "get_channels"
|
||||||
|
)
|
||||||
def on_get_channels(data: tuple[str, dict[str, Any]]):
|
def on_get_channels(data: tuple[str, dict[str, Any]]):
|
||||||
recv_chan = data[1]["recv_chan"]
|
recv_chan = data[1]["recv_chan"]
|
||||||
recv_chan.send(get_channels())
|
recv_chan.send(get_channels())
|
||||||
@@ -231,7 +263,9 @@ def set_channel(name: str, channel: "Channel"):
|
|||||||
channel ([`Channel`](#class-channel-generic-t)): 通道实例
|
channel ([`Channel`](#class-channel-generic-t)): 通道实例
|
||||||
"""
|
"""
|
||||||
if not isinstance(channel, Channel):
|
if not isinstance(channel, Channel):
|
||||||
raise TypeError(f"channel_ must be an instance of Channel, {type(channel)} found")
|
raise TypeError(
|
||||||
|
f"channel_ must be an instance of Channel, {type(channel)} found"
|
||||||
|
)
|
||||||
|
|
||||||
if IS_MAIN_PROCESS:
|
if IS_MAIN_PROCESS:
|
||||||
if name in _channel:
|
if name in _channel:
|
||||||
@@ -241,10 +275,11 @@ def set_channel(name: str, channel: "Channel"):
|
|||||||
# 请求主进程设置通道
|
# 请求主进程设置通道
|
||||||
channel_deliver_passive_channel.send(
|
channel_deliver_passive_channel.send(
|
||||||
(
|
(
|
||||||
"set_channel", {
|
"set_channel",
|
||||||
"name" : name,
|
{
|
||||||
"channel_": channel,
|
"name": name,
|
||||||
}
|
"channel_": channel,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -273,13 +308,7 @@ def get_channel(name: str) -> "Channel":
|
|||||||
else:
|
else:
|
||||||
recv_chan = Channel[Channel[Any]]("recv_chan")
|
recv_chan = Channel[Channel[Any]]("recv_chan")
|
||||||
channel_deliver_passive_channel.send(
|
channel_deliver_passive_channel.send(
|
||||||
(
|
("get_channel", {"name": name, "recv_chan": recv_chan})
|
||||||
"get_channel",
|
|
||||||
{
|
|
||||||
"name" : name,
|
|
||||||
"recv_chan": recv_chan
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return recv_chan.receive()
|
return recv_chan.receive()
|
||||||
|
|
||||||
@@ -294,12 +323,5 @@ def get_channels() -> dict[str, "Channel"]:
|
|||||||
return _channel
|
return _channel
|
||||||
else:
|
else:
|
||||||
recv_chan = Channel[dict[str, Channel[Any]]]("recv_chan")
|
recv_chan = Channel[dict[str, Channel[Any]]]("recv_chan")
|
||||||
channel_deliver_passive_channel.send(
|
channel_deliver_passive_channel.send(("get_channels", {"recv_chan": recv_chan}))
|
||||||
(
|
|
||||||
"get_channels",
|
|
||||||
{
|
|
||||||
"recv_chan": recv_chan
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return recv_chan.receive()
|
return recv_chan.receive()
|
||||||
|
@@ -1,26 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
本模块用于实现RPC(基于IPC)通信
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import TypeAlias, Callable, Any
|
|
||||||
|
|
||||||
from liteyuki.comm.channel import Channel
|
|
||||||
|
|
||||||
ON_CALLING_FUNC: TypeAlias = Callable[[tuple, dict], Any]
|
|
||||||
|
|
||||||
|
|
||||||
class RPC:
|
|
||||||
"""
|
|
||||||
RPC类
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, on_calling: ON_CALLING_FUNC) -> None:
|
|
||||||
self.on_calling = on_calling
|
|
||||||
|
|
||||||
def call(self, args: tuple, kwargs: dict) -> Any:
|
|
||||||
"""
|
|
||||||
调用
|
|
||||||
"""
|
|
||||||
# 获取self.calling函数名
|
|
||||||
return self.on_calling(args, kwargs)
|
|
@@ -1,48 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
基于socket的通道
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class SocksChannel:
|
|
||||||
"""
|
|
||||||
通道类,可以在进程间和进程内通信,双向但同时只能有一个发送者和一个接收者
|
|
||||||
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name: str):
|
|
||||||
"""
|
|
||||||
初始化通道
|
|
||||||
Args:
|
|
||||||
name: 通道ID
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._name = name
|
|
||||||
self._conn_send = None
|
|
||||||
self._conn_recv = None
|
|
||||||
self._closed = False
|
|
||||||
|
|
||||||
def send(self, data):
|
|
||||||
"""
|
|
||||||
发送数据
|
|
||||||
Args:
|
|
||||||
data: 数据
|
|
||||||
"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
def receive(self):
|
|
||||||
"""
|
|
||||||
接收数据
|
|
||||||
Returns:
|
|
||||||
data: 数据
|
|
||||||
"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""
|
|
||||||
关闭通道
|
|
||||||
"""
|
|
||||||
|
|
||||||
pass
|
|
@@ -118,6 +118,8 @@ def load_config_in_default(no_waring: bool = False) -> dict[str, Any]:
|
|||||||
从一个标准的轻雪项目加载配置文件
|
从一个标准的轻雪项目加载配置文件
|
||||||
项目目录下的config.*和config目录下的所有配置文件
|
项目目录下的config.*和config目录下的所有配置文件
|
||||||
项目目录下的配置文件优先
|
项目目录下的配置文件优先
|
||||||
|
Args:
|
||||||
|
no_waring: 是否关闭警告
|
||||||
"""
|
"""
|
||||||
config = load_configs_from_dirs("config", no_waring=no_waring)
|
config = load_configs_from_dirs("config", no_waring=no_waring)
|
||||||
config.update(
|
config.update(
|
||||||
|
@@ -14,6 +14,9 @@ import threading
|
|||||||
from multiprocessing import Process
|
from multiprocessing import Process
|
||||||
from typing import Any, Callable, TYPE_CHECKING, TypeAlias
|
from typing import Any, Callable, TYPE_CHECKING, TypeAlias
|
||||||
|
|
||||||
|
from croterline.context import Context
|
||||||
|
from croterline.process import SubProcess, ProcessFuncType
|
||||||
|
|
||||||
from liteyuki.log import logger
|
from liteyuki.log import logger
|
||||||
from liteyuki.utils import IS_MAIN_PROCESS
|
from liteyuki.utils import IS_MAIN_PROCESS
|
||||||
|
|
||||||
@@ -26,7 +29,10 @@ from liteyuki.comm import Channel
|
|||||||
if IS_MAIN_PROCESS:
|
if IS_MAIN_PROCESS:
|
||||||
from liteyuki.comm.channel import get_channel, publish_channel, get_channels
|
from liteyuki.comm.channel import get_channel, publish_channel, get_channels
|
||||||
from liteyuki.comm.storage import shared_memory
|
from liteyuki.comm.storage import shared_memory
|
||||||
from liteyuki.comm.channel import channel_deliver_active_channel, channel_deliver_passive_channel
|
from liteyuki.comm.channel import (
|
||||||
|
channel_deliver_active_channel,
|
||||||
|
channel_deliver_passive_channel,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
from liteyuki.comm import channel
|
from liteyuki.comm import channel
|
||||||
from liteyuki.comm import storage
|
from liteyuki.comm import storage
|
||||||
@@ -34,20 +40,18 @@ else:
|
|||||||
TARGET_FUNC: TypeAlias = Callable[..., Any]
|
TARGET_FUNC: TypeAlias = Callable[..., Any]
|
||||||
TIMEOUT = 10
|
TIMEOUT = 10
|
||||||
|
|
||||||
__all__ = [
|
__all__ = ["ProcessManager", "sub_process_manager"]
|
||||||
"ProcessManager"
|
|
||||||
]
|
|
||||||
multiprocessing.set_start_method("spawn", force=True)
|
multiprocessing.set_start_method("spawn", force=True)
|
||||||
|
|
||||||
|
|
||||||
class ChannelDeliver:
|
class ChannelDeliver:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
active: Channel[Any],
|
active: Channel[Any],
|
||||||
passive: Channel[Any],
|
passive: Channel[Any],
|
||||||
channel_deliver_active: Channel[Channel[Any]],
|
channel_deliver_active: Channel[Channel[Any]],
|
||||||
channel_deliver_passive: Channel[tuple[str, dict]],
|
channel_deliver_passive: Channel[tuple[str, dict]],
|
||||||
publish: Channel[tuple[str, Any]],
|
publish: Channel[tuple[str, Any]],
|
||||||
):
|
):
|
||||||
self.active = active
|
self.active = active
|
||||||
self.passive = passive
|
self.passive = passive
|
||||||
@@ -57,7 +61,9 @@ class ChannelDeliver:
|
|||||||
|
|
||||||
|
|
||||||
# 函数处理一些跨进程通道的
|
# 函数处理一些跨进程通道的
|
||||||
def _delivery_channel_wrapper(func: TARGET_FUNC, cd: ChannelDeliver, sm: "KeyValueStore", *args, **kwargs):
|
def _delivery_channel_wrapper(
|
||||||
|
func: TARGET_FUNC, cd: ChannelDeliver, sm: "KeyValueStore", *args, **kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
子进程入口函数
|
子进程入口函数
|
||||||
处理一些操作
|
处理一些操作
|
||||||
@@ -68,8 +74,12 @@ def _delivery_channel_wrapper(func: TARGET_FUNC, cd: ChannelDeliver, sm: "KeyVal
|
|||||||
|
|
||||||
channel.active_channel = cd.active # 子进程主动通道
|
channel.active_channel = cd.active # 子进程主动通道
|
||||||
channel.passive_channel = cd.passive # 子进程被动通道
|
channel.passive_channel = cd.passive # 子进程被动通道
|
||||||
channel.channel_deliver_active_channel = cd.channel_deliver_active # 子进程通道传递主动通道
|
channel.channel_deliver_active_channel = (
|
||||||
channel.channel_deliver_passive_channel = cd.channel_deliver_passive # 子进程通道传递被动通道
|
cd.channel_deliver_active
|
||||||
|
) # 子进程通道传递主动通道
|
||||||
|
channel.channel_deliver_passive_channel = (
|
||||||
|
cd.channel_deliver_passive
|
||||||
|
) # 子进程通道传递被动通道
|
||||||
channel.publish_channel = cd.publish # 子进程发布通道
|
channel.publish_channel = cd.publish # 子进程发布通道
|
||||||
|
|
||||||
# 给子进程创建共享内存实例
|
# 给子进程创建共享内存实例
|
||||||
@@ -102,8 +112,12 @@ class ProcessManager:
|
|||||||
chan_active = get_channel(f"{name}-active")
|
chan_active = get_channel(f"{name}-active")
|
||||||
|
|
||||||
def _start_process():
|
def _start_process():
|
||||||
process = Process(target=self.targets[name][0], args=self.targets[name][1],
|
process = Process(
|
||||||
kwargs=self.targets[name][2], daemon=True)
|
target=self.targets[name][0],
|
||||||
|
args=self.targets[name][1],
|
||||||
|
kwargs=self.targets[name][2],
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
self.processes[name] = process
|
self.processes[name] = process
|
||||||
process.start()
|
process.start()
|
||||||
|
|
||||||
@@ -133,7 +147,9 @@ class ProcessManager:
|
|||||||
|
|
||||||
for name in self.targets:
|
for name in self.targets:
|
||||||
logger.debug(f"Starting process {name}")
|
logger.debug(f"Starting process {name}")
|
||||||
threading.Thread(target=self._run_process, args=(name, ), daemon=True).start()
|
threading.Thread(
|
||||||
|
target=self._run_process, args=(name,), daemon=True
|
||||||
|
).start()
|
||||||
|
|
||||||
def add_target(self, name: str, target: TARGET_FUNC, args: tuple = (), kwargs=None):
|
def add_target(self, name: str, target: TARGET_FUNC, args: tuple = (), kwargs=None):
|
||||||
"""
|
"""
|
||||||
@@ -154,10 +170,14 @@ class ProcessManager:
|
|||||||
passive=chan_passive,
|
passive=chan_passive,
|
||||||
channel_deliver_active=channel_deliver_active_channel,
|
channel_deliver_active=channel_deliver_active_channel,
|
||||||
channel_deliver_passive=channel_deliver_passive_channel,
|
channel_deliver_passive=channel_deliver_passive_channel,
|
||||||
publish=publish_channel
|
publish=publish_channel,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.targets[name] = (_delivery_channel_wrapper, (target, channel_deliver, shared_memory, *args), kwargs)
|
self.targets[name] = (
|
||||||
|
_delivery_channel_wrapper,
|
||||||
|
(target, channel_deliver, shared_memory, *args),
|
||||||
|
kwargs,
|
||||||
|
)
|
||||||
# 主进程通道
|
# 主进程通道
|
||||||
|
|
||||||
def join_all(self):
|
def join_all(self):
|
||||||
@@ -199,3 +219,79 @@ class ProcessManager:
|
|||||||
if name not in self.targets:
|
if name not in self.targets:
|
||||||
logger.warning(f"Process {name} not found.")
|
logger.warning(f"Process {name} not found.")
|
||||||
return self.processes[name].is_alive()
|
return self.processes[name].is_alive()
|
||||||
|
|
||||||
|
|
||||||
|
# new version
|
||||||
|
|
||||||
|
|
||||||
|
class _SubProcessManager:
|
||||||
|
"""
|
||||||
|
子进程管理器
|
||||||
|
若要子进程间通信,请先在子进程A中发送通信事件给主进程,包含当前进程信息及上下文信息,主进程再将信息发送给子进程B,子进程B再根据信息进行操作
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.processes: dict[str, SubProcess] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def add(self, name: str, func: ProcessFuncType, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
添加子进程
|
||||||
|
Args:
|
||||||
|
func: 子进程函数
|
||||||
|
name: 子进程名称
|
||||||
|
args: 子进程函数参数
|
||||||
|
kwargs: 子进程函数关键字参数
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
self.processes[name] = SubProcess(name, func, *args, **kwargs)
|
||||||
|
|
||||||
|
def start(self, name: str):
|
||||||
|
"""
|
||||||
|
启动指定子进程
|
||||||
|
Args:
|
||||||
|
name: 子进程名称
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
if name not in self.processes:
|
||||||
|
raise KeyError(f"Process {name} not found.")
|
||||||
|
self.processes[name].start()
|
||||||
|
|
||||||
|
def start_all(self):
|
||||||
|
"""
|
||||||
|
启动所有子进程
|
||||||
|
"""
|
||||||
|
for name, process in self.processes.items():
|
||||||
|
process.start()
|
||||||
|
logger.debug(f"Starting process {name}")
|
||||||
|
|
||||||
|
def terminate(self, name: str):
|
||||||
|
"""
|
||||||
|
终止指定子进程
|
||||||
|
Args:
|
||||||
|
name: 子进程名称
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
if name not in self.processes:
|
||||||
|
raise KeyError(f"Process {name} not found.")
|
||||||
|
self.processes[name].terminate()
|
||||||
|
|
||||||
|
def terminate_all(self):
|
||||||
|
"""
|
||||||
|
终止所有子进程
|
||||||
|
"""
|
||||||
|
for name, process in self.processes.items():
|
||||||
|
process.terminate()
|
||||||
|
logger.debug(f"Terminating process {name}")
|
||||||
|
|
||||||
|
def get_process(self, name: str) -> SubProcess | None:
|
||||||
|
"""
|
||||||
|
获取指定子进程
|
||||||
|
Args:
|
||||||
|
name: 子进程名称
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
return self.processes.get(name, None)
|
||||||
|
|
||||||
|
|
||||||
|
sub_process_manager = _SubProcessManager()
|
||||||
|
@@ -12,26 +12,34 @@ import sys
|
|||||||
|
|
||||||
import loguru
|
import loguru
|
||||||
|
|
||||||
logger = loguru.logger
|
logger = loguru.logger.bind()
|
||||||
|
|
||||||
# DEBUG日志格式
|
|
||||||
debug_format: str = (
|
debug_format: str = (
|
||||||
"<c>{time:YYYY-MM-DD HH:mm:ss}</c> "
|
"<c>{time:YYYY-MM-DD HH:mm:ss}</c> "
|
||||||
"<lvl>[{level.icon}]</lvl> "
|
"<lvl>[{level.icon}{level}]</lvl> "
|
||||||
"<c><{name}.{module}.{function}:{line}></c> "
|
"<c><{name}.{module}.{function}:{line}></c> "
|
||||||
"{message}"
|
"{message}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 默认日志格式
|
# 默认日志格式
|
||||||
default_format: str = (
|
default_format: str = (
|
||||||
"<c>{time:MM-DD HH:mm:ss}</c> "
|
"<c>{time:MM-DD HH:mm:ss}</c> "
|
||||||
"<lvl>[{level.icon}]</lvl> "
|
"<lvl>[{level.icon}{level}]</lvl> "
|
||||||
"<c><{name}></c> "
|
"<c><{name}></c> "
|
||||||
"{message}"
|
"{message}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_format(level: str) -> str:
|
def get_format(level: str) -> str:
|
||||||
|
"""
|
||||||
|
获取日志格式
|
||||||
|
Args:
|
||||||
|
level: 日志等级
|
||||||
|
|
||||||
|
Returns: 日志格式
|
||||||
|
|
||||||
|
"""
|
||||||
|
# DEBUG日志格式
|
||||||
|
|
||||||
if level == "DEBUG":
|
if level == "DEBUG":
|
||||||
return debug_format
|
return debug_format
|
||||||
else:
|
else:
|
||||||
@@ -41,23 +49,27 @@ def get_format(level: str) -> str:
|
|||||||
def init_log(config: dict):
|
def init_log(config: dict):
|
||||||
"""
|
"""
|
||||||
在语言加载完成后执行
|
在语言加载完成后执行
|
||||||
Returns:
|
Args:
|
||||||
|
config: 配置
|
||||||
"""
|
"""
|
||||||
|
global logger
|
||||||
|
level = config.get("log_level", "DEBUG")
|
||||||
logger.remove()
|
logger.remove()
|
||||||
logger.add(
|
logger.add(
|
||||||
sys.stdout,
|
sys.stdout,
|
||||||
level=0,
|
level=level,
|
||||||
diagnose=False,
|
diagnose=False,
|
||||||
format=get_format(config.get("log_level", "INFO")),
|
format=get_format(level),
|
||||||
)
|
)
|
||||||
show_icon = config.get("log_icon", True)
|
show_icon = config.get("log_icon", True)
|
||||||
logger.level("DEBUG", color="<blue>", icon=f"{'🐛' if show_icon else ''}DEBUG")
|
logger.level("DEBUG", color="<blue>", icon=f"{'🐛' if show_icon else ''}")
|
||||||
logger.level("INFO", color="<normal>", icon=f"{'ℹ️' if show_icon else ''}INFO")
|
logger.level("INFO", color="<normal>", icon=f"{'ℹ️' if show_icon else ''}")
|
||||||
logger.level("SUCCESS", color="<green>", icon=f"{'✅' if show_icon else ''}SUCCESS")
|
logger.level("SUCCESS", color="<green>", icon=f"{'✅' if show_icon else ''}")
|
||||||
logger.level("WARNING", color="<yellow>", icon=f"{'⚠️' if show_icon else ''}WARNING")
|
logger.level("WARNING", color="<yellow>", icon=f"{'⚠️' if show_icon else ''}")
|
||||||
logger.level("ERROR", color="<red>", icon=f"{'⭕' if show_icon else ''}ERROR")
|
logger.level("ERROR", color="<red>", icon=f"{'⭕' if show_icon else ''}")
|
||||||
|
logger.level("CRITICAL", color="<red>", icon=f"{'❌' if show_icon else ''}")
|
||||||
|
logger.level("TRACE", color="<cyan>", icon=f"{'🔍' if show_icon else ''}")
|
||||||
|
|
||||||
|
logger.bind()
|
||||||
|
|
||||||
init_log(config={})
|
init_log(config={"log_level": "DEBUG", "log_icon": True})
|
@@ -1,10 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|
||||||
|
|
||||||
@Time : 2024/8/19 下午10:44
|
|
||||||
@Author : snowykami
|
|
||||||
@Email : snowykami@outlook.com
|
|
||||||
@File : __init__.py.py
|
|
||||||
@Software: PyCharm
|
|
||||||
"""
|
|
@@ -1,10 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|
||||||
|
|
||||||
@Time : 2024/8/19 下午10:47
|
|
||||||
@Author : snowykami
|
|
||||||
@Email : snowykami@outlook.com
|
|
||||||
@File : session.py
|
|
||||||
@Software: PyCharm
|
|
||||||
"""
|
|
@@ -60,7 +60,6 @@ def load_plugin(module_path: str | Path) -> Optional[Plugin]:
|
|||||||
f"{metadata.name}({module.__name__.split('.')[-1]})", metadata.type
|
f"{metadata.name}({module.__name__.split('.')[-1]})", metadata.type
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
logger.opt(colors=True).warning(
|
logger.opt(colors=True).warning(
|
||||||
f'The metadata of Liteyuki plugin "{module.__name__}" is not specified, use empty.'
|
f'The metadata of Liteyuki plugin "{module.__name__}" is not specified, use empty.'
|
||||||
)
|
)
|
||||||
|
@@ -9,9 +9,9 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from liteyuki.message.on import on_startswith
|
from liteyuki.session.on import on_startswith
|
||||||
from liteyuki.message.event import MessageEvent
|
from liteyuki.session.event import MessageEvent
|
||||||
from liteyuki.message.rule import is_su_rule
|
from liteyuki.session.rule import is_su_rule
|
||||||
|
|
||||||
|
|
||||||
@on_startswith(["liteecho"], rule=is_su_rule).handle()
|
@on_startswith(["liteecho"], rule=is_su_rule).handle()
|
||||||
|
21
liteyuki/session/__init__.py
Normal file
21
liteyuki/session/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
该模块参考并引用了nonebot-plugin-alconna的消息段定义
|
||||||
|
"""
|
||||||
|
from typing import Any
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
from magicoca import Chan, select
|
||||||
|
|
||||||
|
from liteyuki.log import logger
|
||||||
|
|
||||||
|
|
||||||
|
def message_handler_thread(i_chans: Iterable[Chan[Any]]):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
i_chans: 多路输入管道组
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
for msg in select(*i_chans):
|
||||||
|
logger.debug(f"Recv from anybot {msg}")
|
||||||
|
logger.info(f"Recv from anybot {msg}")
|
@@ -11,7 +11,6 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from liteyuki import Channel
|
from liteyuki import Channel
|
||||||
from liteyuki.comm.storage import shared_memory
|
|
||||||
|
|
||||||
|
|
||||||
class MessageEvent:
|
class MessageEvent:
|
@@ -11,8 +11,8 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
import traceback
|
import traceback
|
||||||
from typing import Any, TypeAlias, Callable, Coroutine
|
from typing import Any, TypeAlias, Callable, Coroutine
|
||||||
|
|
||||||
from liteyuki.message.event import MessageEvent
|
from liteyuki.session.event import MessageEvent
|
||||||
from liteyuki.message.rule import Rule
|
from liteyuki.session.rule import Rule
|
||||||
|
|
||||||
EventHandler: TypeAlias = Callable[[MessageEvent], Coroutine[None, None, Any]]
|
EventHandler: TypeAlias = Callable[[MessageEvent], Coroutine[None, None, Any]]
|
||||||
|
|
51
liteyuki/session/models.py
Normal file
51
liteyuki/session/models.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class User(BaseModel):
|
||||||
|
"""
|
||||||
|
用户信息
|
||||||
|
Attributes:
|
||||||
|
id: 用户ID
|
||||||
|
name: 用户名
|
||||||
|
nick: 用户昵称
|
||||||
|
avatar: 用户头像图链接
|
||||||
|
"""
|
||||||
|
id: str
|
||||||
|
name: str | None
|
||||||
|
nick: str | None
|
||||||
|
avatar: str | None
|
||||||
|
|
||||||
|
class Scene(BaseModel):
|
||||||
|
"""
|
||||||
|
场景信息
|
||||||
|
Attributes:
|
||||||
|
id: 场景ID
|
||||||
|
type: 场景类型
|
||||||
|
name: 场景名
|
||||||
|
avatar: 场景头像图链接
|
||||||
|
parent: 父场景
|
||||||
|
"""
|
||||||
|
id: str
|
||||||
|
type: str
|
||||||
|
name: str | None
|
||||||
|
avatar: str | None
|
||||||
|
parent: "Scene | None"
|
||||||
|
|
||||||
|
class Session(BaseModel):
|
||||||
|
"""
|
||||||
|
会话信息
|
||||||
|
Attributes:
|
||||||
|
self_id: 机器人ID
|
||||||
|
adapter: 适配器ID
|
||||||
|
scope: 会话范围
|
||||||
|
scene: 场景信息
|
||||||
|
user: 用户信息
|
||||||
|
member: 成员信息,仅频道及群聊有效
|
||||||
|
operator: 操作者信息,仅频道及群聊有效
|
||||||
|
"""
|
||||||
|
self_id: str
|
||||||
|
adapter: str
|
||||||
|
scope: str
|
||||||
|
scene: Scene
|
||||||
|
user: User
|
||||||
|
member: "Member | None"
|
||||||
|
operator: "Member | None"
|
@@ -11,33 +11,13 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
|
||||||
from liteyuki.comm.storage import shared_memory
|
from liteyuki.session.event import MessageEvent
|
||||||
from liteyuki.log import logger
|
from liteyuki.session.matcher import Matcher
|
||||||
from liteyuki.message.event import MessageEvent
|
from liteyuki.session.rule import Rule, empty_rule
|
||||||
from liteyuki.message.matcher import Matcher
|
|
||||||
from liteyuki.message.rule import Rule, empty_rule
|
|
||||||
|
|
||||||
_matcher_list: list[Matcher] = []
|
_matcher_list: list[Matcher] = []
|
||||||
_queue: Queue = Queue()
|
_queue: Queue = Queue()
|
||||||
|
|
||||||
|
|
||||||
@shared_memory.on_subscriber_receive("event_to_liteyuki")
|
|
||||||
async def _(event: MessageEvent):
|
|
||||||
print("AA")
|
|
||||||
current_priority = -1
|
|
||||||
for i, matcher in enumerate(_matcher_list):
|
|
||||||
logger.info(f"Running matcher {matcher} for event: {event}")
|
|
||||||
await matcher.run(event)
|
|
||||||
# 同优先级不阻断,不同优先级阻断
|
|
||||||
if current_priority != matcher.priority:
|
|
||||||
current_priority = matcher.priority
|
|
||||||
if matcher.block:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
logger.info(f"No matcher matched for event: {event}")
|
|
||||||
print("BB")
|
|
||||||
|
|
||||||
|
|
||||||
def add_matcher(matcher: Matcher):
|
def add_matcher(matcher: Matcher):
|
||||||
for i, m in enumerate(_matcher_list):
|
for i, m in enumerate(_matcher_list):
|
||||||
if m.priority < matcher.priority:
|
if m.priority < matcher.priority:
|
@@ -11,7 +11,7 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
import inspect
|
import inspect
|
||||||
from typing import Optional, TypeAlias, Callable, Coroutine
|
from typing import Optional, TypeAlias, Callable, Coroutine
|
||||||
|
|
||||||
from liteyuki.message.event import MessageEvent
|
from liteyuki.session.event import MessageEvent
|
||||||
from liteyuki import get_config
|
from liteyuki import get_config
|
||||||
|
|
||||||
_superusers: list[str] = get_config("liteyuki.superusers", [])
|
_superusers: list[str] = get_config("liteyuki.superusers", [])
|
515
pdm.lock
generated
Normal file
515
pdm.lock
generated
Normal file
@@ -0,0 +1,515 @@
|
|||||||
|
# This file is @generated by PDM.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
groups = ["default", "dev"]
|
||||||
|
strategy = ["inherit_metadata"]
|
||||||
|
lock_version = "4.5.0"
|
||||||
|
content_hash = "sha256:b79773f31b417a430bc17fa770a0ff937c3ed0a3d1ac18950fdf4d5c405a28af"
|
||||||
|
|
||||||
|
[[metadata.targets]]
|
||||||
|
requires_python = ">=3.10"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-types"
|
||||||
|
version = "0.7.0"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "Reusable constraint types to use with typing.Annotated"
|
||||||
|
groups = ["default"]
|
||||||
|
dependencies = [
|
||||||
|
"typing-extensions>=4.0.0; python_version < \"3.9\"",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
|
||||||
|
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "black"
|
||||||
|
version = "24.10.0"
|
||||||
|
requires_python = ">=3.9"
|
||||||
|
summary = "The uncompromising code formatter."
|
||||||
|
groups = ["dev"]
|
||||||
|
dependencies = [
|
||||||
|
"click>=8.0.0",
|
||||||
|
"mypy-extensions>=0.4.3",
|
||||||
|
"packaging>=22.0",
|
||||||
|
"pathspec>=0.9.0",
|
||||||
|
"platformdirs>=2",
|
||||||
|
"tomli>=1.1.0; python_version < \"3.11\"",
|
||||||
|
"typing-extensions>=4.0.1; python_version < \"3.11\"",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"},
|
||||||
|
{file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"},
|
||||||
|
{file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"},
|
||||||
|
{file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"},
|
||||||
|
{file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"},
|
||||||
|
{file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"},
|
||||||
|
{file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"},
|
||||||
|
{file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"},
|
||||||
|
{file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"},
|
||||||
|
{file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"},
|
||||||
|
{file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"},
|
||||||
|
{file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"},
|
||||||
|
{file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"},
|
||||||
|
{file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"},
|
||||||
|
{file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"},
|
||||||
|
{file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"},
|
||||||
|
{file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"},
|
||||||
|
{file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.1.7"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "Composable command line interface toolkit"
|
||||||
|
groups = ["dev"]
|
||||||
|
dependencies = [
|
||||||
|
"colorama; platform_system == \"Windows\"",
|
||||||
|
"importlib-metadata; python_version < \"3.8\"",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
|
||||||
|
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
|
summary = "Cross-platform colored terminal text."
|
||||||
|
groups = ["default", "dev"]
|
||||||
|
marker = "sys_platform == \"win32\" or platform_system == \"Windows\""
|
||||||
|
files = [
|
||||||
|
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||||
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "croterline"
|
||||||
|
version = "1.0.9"
|
||||||
|
requires_python = ">=3.10"
|
||||||
|
summary = "Default template for PDM package"
|
||||||
|
groups = ["default"]
|
||||||
|
dependencies = [
|
||||||
|
"magicoca>=1.0.6",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "croterline-1.0.9-py3-none-any.whl", hash = "sha256:d2d62bd8038ea0a845175b4a2ac127e4c4fcdb8dab5d4a04804c8af0a30986eb"},
|
||||||
|
{file = "croterline-1.0.9.tar.gz", hash = "sha256:417a3aa2b18ee362f6b22939d918db378dceb1992b0231f3b80cbbdde4cb2f0a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "exceptiongroup"
|
||||||
|
version = "1.2.2"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "Backport of PEP 654 (exception groups)"
|
||||||
|
groups = ["dev"]
|
||||||
|
marker = "python_version < \"3.11\""
|
||||||
|
files = [
|
||||||
|
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
||||||
|
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.0.0"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "brain-dead simple config-ini parsing"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
||||||
|
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "loguru"
|
||||||
|
version = "0.7.2"
|
||||||
|
requires_python = ">=3.5"
|
||||||
|
summary = "Python logging made (stupidly) simple"
|
||||||
|
groups = ["default"]
|
||||||
|
dependencies = [
|
||||||
|
"aiocontextvars>=0.2.0; python_version < \"3.7\"",
|
||||||
|
"colorama>=0.3.4; sys_platform == \"win32\"",
|
||||||
|
"win32-setctime>=1.0.0; sys_platform == \"win32\"",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"},
|
||||||
|
{file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "magicoca"
|
||||||
|
version = "1.0.6"
|
||||||
|
requires_python = ">=3.10"
|
||||||
|
summary = "A communication library for Python"
|
||||||
|
groups = ["default"]
|
||||||
|
files = [
|
||||||
|
{file = "magicoca-1.0.6-py3-none-any.whl", hash = "sha256:3c62d6c3e87b3b5a13e0fd5b5fb674374355326684ab4421f2ac8a4c5127455e"},
|
||||||
|
{file = "magicoca-1.0.6.tar.gz", hash = "sha256:e32ec57d67e64232dc7681c5095166e6a0ee3d27048d7115aa2b09ce4e7d9ff6"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy"
|
||||||
|
version = "1.12.1"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "Optional static typing for Python"
|
||||||
|
groups = ["dev"]
|
||||||
|
dependencies = [
|
||||||
|
"mypy-extensions>=1.0.0",
|
||||||
|
"tomli>=1.1.0; python_version < \"3.11\"",
|
||||||
|
"typing-extensions>=4.6.0",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "mypy-1.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3d7d4371829184e22fda4015278fbfdef0327a4b955a483012bd2d423a788801"},
|
||||||
|
{file = "mypy-1.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f59f1dfbf497d473201356966e353ef09d4daec48caeacc0254db8ef633a28a5"},
|
||||||
|
{file = "mypy-1.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b947097fae68004b8328c55161ac9db7d3566abfef72d9d41b47a021c2fba6b1"},
|
||||||
|
{file = "mypy-1.12.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96af62050971c5241afb4701c15189ea9507db89ad07794a4ee7b4e092dc0627"},
|
||||||
|
{file = "mypy-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:d90da248f4c2dba6c44ddcfea94bb361e491962f05f41990ff24dbd09969ce20"},
|
||||||
|
{file = "mypy-1.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1230048fec1380faf240be6385e709c8570604d2d27ec6ca7e573e3bc09c3735"},
|
||||||
|
{file = "mypy-1.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:02dcfe270c6ea13338210908f8cadc8d31af0f04cee8ca996438fe6a97b4ec66"},
|
||||||
|
{file = "mypy-1.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a437c9102a6a252d9e3a63edc191a3aed5f2fcb786d614722ee3f4472e33f6"},
|
||||||
|
{file = "mypy-1.12.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:186e0c8346efc027ee1f9acf5ca734425fc4f7dc2b60144f0fbe27cc19dc7931"},
|
||||||
|
{file = "mypy-1.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:673ba1140a478b50e6d265c03391702fa11a5c5aff3f54d69a62a48da32cb811"},
|
||||||
|
{file = "mypy-1.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9fb83a7be97c498176fb7486cafbb81decccaef1ac339d837c377b0ce3743a7f"},
|
||||||
|
{file = "mypy-1.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:389e307e333879c571029d5b93932cf838b811d3f5395ed1ad05086b52148fb0"},
|
||||||
|
{file = "mypy-1.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b2048a95a21f7a9ebc9fbd075a4fcd310410d078aa0228dbbad7f71335e042"},
|
||||||
|
{file = "mypy-1.12.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5932370ccf7ebf83f79d1c157a5929d7ea36313027b0d70a488493dc1b179"},
|
||||||
|
{file = "mypy-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:19bf51f87a295e7ab2894f1d8167622b063492d754e69c3c2fed6563268cb42a"},
|
||||||
|
{file = "mypy-1.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d34167d43613ffb1d6c6cdc0cc043bb106cac0aa5d6a4171f77ab92a3c758bcc"},
|
||||||
|
{file = "mypy-1.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:427878aa54f2e2c5d8db31fa9010c599ed9f994b3b49e64ae9cd9990c40bd635"},
|
||||||
|
{file = "mypy-1.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5fcde63ea2c9f69d6be859a1e6dd35955e87fa81de95bc240143cf00de1f7f81"},
|
||||||
|
{file = "mypy-1.12.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d54d840f6c052929f4a3d2aab2066af0f45a020b085fe0e40d4583db52aab4e4"},
|
||||||
|
{file = "mypy-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:20db6eb1ca3d1de8ece00033b12f793f1ea9da767334b7e8c626a4872090cf02"},
|
||||||
|
{file = "mypy-1.12.1-py3-none-any.whl", hash = "sha256:ce561a09e3bb9863ab77edf29ae3a50e65685ad74bba1431278185b7e5d5486e"},
|
||||||
|
{file = "mypy-1.12.1.tar.gz", hash = "sha256:f5b3936f7a6d0e8280c9bdef94c7ce4847f5cdfc258fbb2c29a8c1711e8bb96d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-extensions"
|
||||||
|
version = "1.0.0"
|
||||||
|
requires_python = ">=3.5"
|
||||||
|
summary = "Type system extensions for programs checked with the mypy type checker."
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
|
||||||
|
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "24.1"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "Core utilities for Python packages"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
|
||||||
|
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathspec"
|
||||||
|
version = "0.12.1"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "Utility library for gitignore style pattern matching of file paths."
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
|
||||||
|
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pdm-backend"
|
||||||
|
version = "2.4.2"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "The build backend used by PDM that supports latest packaging standards"
|
||||||
|
groups = ["default"]
|
||||||
|
dependencies = [
|
||||||
|
"importlib-metadata>=3.6; python_version < \"3.10\"",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "pdm_backend-2.4.2-py3-none-any.whl", hash = "sha256:8537a3273b19d6448eb07a4a1a92dedc0b60935344a037729ada7be33b5f71ad"},
|
||||||
|
{file = "pdm_backend-2.4.2.tar.gz", hash = "sha256:1f833e527ae172f34b4b84e2fcf1f65859a2a5ca746e496d8313b3ea6539969f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "platformdirs"
|
||||||
|
version = "4.3.6"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
|
||||||
|
{file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.5.0"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "plugin and hook calling mechanisms for python"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
|
||||||
|
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic"
|
||||||
|
version = "2.9.2"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "Data validation using Python type hints"
|
||||||
|
groups = ["default"]
|
||||||
|
dependencies = [
|
||||||
|
"annotated-types>=0.6.0",
|
||||||
|
"pydantic-core==2.23.4",
|
||||||
|
"typing-extensions>=4.12.2; python_version >= \"3.13\"",
|
||||||
|
"typing-extensions>=4.6.1; python_version < \"3.13\"",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"},
|
||||||
|
{file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-core"
|
||||||
|
version = "2.23.4"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "Core functionality for Pydantic validation and serialization"
|
||||||
|
groups = ["default"]
|
||||||
|
dependencies = [
|
||||||
|
"typing-extensions!=4.7.0,>=4.6.0",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"},
|
||||||
|
{file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"},
|
||||||
|
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"},
|
||||||
|
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"},
|
||||||
|
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"},
|
||||||
|
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"},
|
||||||
|
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"},
|
||||||
|
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"},
|
||||||
|
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"},
|
||||||
|
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"},
|
||||||
|
{file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "8.3.3"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "pytest: simple powerful testing with Python"
|
||||||
|
groups = ["dev"]
|
||||||
|
dependencies = [
|
||||||
|
"colorama; sys_platform == \"win32\"",
|
||||||
|
"exceptiongroup>=1.0.0rc8; python_version < \"3.11\"",
|
||||||
|
"iniconfig",
|
||||||
|
"packaging",
|
||||||
|
"pluggy<2,>=1.5",
|
||||||
|
"tomli>=1; python_version < \"3.11\"",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
|
||||||
|
{file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyyaml"
|
||||||
|
version = "6.0.2"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "YAML parser and emitter for Python"
|
||||||
|
groups = ["default"]
|
||||||
|
files = [
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
|
||||||
|
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.10.2"
|
||||||
|
requires_python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
summary = "Python Library for Tom's Obvious, Minimal Language"
|
||||||
|
groups = ["default"]
|
||||||
|
files = [
|
||||||
|
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||||
|
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tomli"
|
||||||
|
version = "2.0.2"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "A lil' TOML parser"
|
||||||
|
groups = ["dev"]
|
||||||
|
marker = "python_version < \"3.11\""
|
||||||
|
files = [
|
||||||
|
{file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"},
|
||||||
|
{file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.12.2"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "Backported and Experimental Type Hints for Python 3.8+"
|
||||||
|
groups = ["default", "dev"]
|
||||||
|
files = [
|
||||||
|
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||||
|
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uv"
|
||||||
|
version = "0.4.24"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "An extremely fast Python package and project manager, written in Rust."
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "uv-0.4.24-py3-none-linux_armv6l.whl", hash = "sha256:bbc24b232c5e874741d863c5bec2257533db86f91381f1a101872028a0502ec9"},
|
||||||
|
{file = "uv-0.4.24-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8d467d4c4746127b2121d6f67686957a2b5431935d26767aa02fa4516694293"},
|
||||||
|
{file = "uv-0.4.24-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7d076875e9fa4d8cda44d3e51c9b47efc578db830535c62f25884772bfa265bc"},
|
||||||
|
{file = "uv-0.4.24-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:c40f75df1f2c45a7f67fcc69d80231760f6a017b7c8e889a16e21348651a34d7"},
|
||||||
|
{file = "uv-0.4.24-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b459913d8ba6edba2c4b299e87fccfbd7fca4b2e2abe5fd4fa0da56147e19fc8"},
|
||||||
|
{file = "uv-0.4.24-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e3ce0350e74b3dba6854789dd253faeab2fdf8e84f2671b68573070bb40ff17"},
|
||||||
|
{file = "uv-0.4.24-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d274f7ddc013697fb52962632bc7e77889a6ec87d2cd12316d218686cfece3d4"},
|
||||||
|
{file = "uv-0.4.24-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ef6914a7294ac7df5bd15b21652cbe61d1c12a0f29a94d178dce6192f858092"},
|
||||||
|
{file = "uv-0.4.24-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d8e5f66a8756d4908121cb59189e6f9992fdbd0f9c26a5a30a069b94f8acab3"},
|
||||||
|
{file = "uv-0.4.24-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3ea6780e3451c81ce1635656abcd8a47e43f1b0f02542c433b4b6dd459df8e"},
|
||||||
|
{file = "uv-0.4.24-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:beaff8fdaad3bcd781a8d28b60843b8d1cd2a04229847dc314c1bb7e0bb39ca2"},
|
||||||
|
{file = "uv-0.4.24-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:70a76cb5b8a459d6f6931becf2b5689599382c2512341d566ce335b8304c44e8"},
|
||||||
|
{file = "uv-0.4.24-py3-none-musllinux_1_1_i686.whl", hash = "sha256:29c514752873c1be259afd82b975e528ec6783564a306fd24deee0cccb2dc566"},
|
||||||
|
{file = "uv-0.4.24-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:c03a411f1b86ce7de25d6271d90358ba2d33e87b4922dc5378c4c07674909363"},
|
||||||
|
{file = "uv-0.4.24-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:a03bc4b2ca2236eece97fffb8b5605b7a2248cd8a4b9a9c67955ad08756a1ceb"},
|
||||||
|
{file = "uv-0.4.24-py3-none-win32.whl", hash = "sha256:a97c347af12deb687c09fed82dc829efd6e5fbc4d76a38e98b2eaa2b065e4cfe"},
|
||||||
|
{file = "uv-0.4.24-py3-none-win_amd64.whl", hash = "sha256:ec0570f5e2e4dbfd83a89e9a55d5f033050d749f684bd0e7d4c327fd49f89b12"},
|
||||||
|
{file = "uv-0.4.24.tar.gz", hash = "sha256:f71a00f10cfa15b4f4f0184a67da19f35c48683bba9bb49cebe9c206f1b2bc1f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "watchdog"
|
||||||
|
version = "5.0.3"
|
||||||
|
requires_python = ">=3.9"
|
||||||
|
summary = "Filesystem events monitoring"
|
||||||
|
groups = ["default"]
|
||||||
|
files = [
|
||||||
|
{file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:85527b882f3facda0579bce9d743ff7f10c3e1e0db0a0d0e28170a7d0e5ce2ea"},
|
||||||
|
{file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:53adf73dcdc0ef04f7735066b4a57a4cd3e49ef135daae41d77395f0b5b692cb"},
|
||||||
|
{file = "watchdog-5.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e25adddab85f674acac303cf1f5835951345a56c5f7f582987d266679979c75b"},
|
||||||
|
{file = "watchdog-5.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f01f4a3565a387080dc49bdd1fefe4ecc77f894991b88ef927edbfa45eb10818"},
|
||||||
|
{file = "watchdog-5.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91b522adc25614cdeaf91f7897800b82c13b4b8ac68a42ca959f992f6990c490"},
|
||||||
|
{file = "watchdog-5.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d52db5beb5e476e6853da2e2d24dbbbed6797b449c8bf7ea118a4ee0d2c9040e"},
|
||||||
|
{file = "watchdog-5.0.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94d11b07c64f63f49876e0ab8042ae034674c8653bfcdaa8c4b32e71cfff87e8"},
|
||||||
|
{file = "watchdog-5.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:349c9488e1d85d0a58e8cb14222d2c51cbc801ce11ac3936ab4c3af986536926"},
|
||||||
|
{file = "watchdog-5.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:53a3f10b62c2d569e260f96e8d966463dec1a50fa4f1b22aec69e3f91025060e"},
|
||||||
|
{file = "watchdog-5.0.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:950f531ec6e03696a2414b6308f5c6ff9dab7821a768c9d5788b1314e9a46ca7"},
|
||||||
|
{file = "watchdog-5.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae6deb336cba5d71476caa029ceb6e88047fc1dc74b62b7c4012639c0b563906"},
|
||||||
|
{file = "watchdog-5.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1021223c08ba8d2d38d71ec1704496471ffd7be42cfb26b87cd5059323a389a1"},
|
||||||
|
{file = "watchdog-5.0.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:90a67d7857adb1d985aca232cc9905dd5bc4803ed85cfcdcfcf707e52049eda7"},
|
||||||
|
{file = "watchdog-5.0.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:720ef9d3a4f9ca575a780af283c8fd3a0674b307651c1976714745090da5a9e8"},
|
||||||
|
{file = "watchdog-5.0.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dd021efa85970bd4824acacbb922066159d0f9e546389a4743d56919b6758b91"},
|
||||||
|
{file = "watchdog-5.0.3-py3-none-manylinux2014_armv7l.whl", hash = "sha256:78864cc8f23dbee55be34cc1494632a7ba30263951b5b2e8fc8286b95845f82c"},
|
||||||
|
{file = "watchdog-5.0.3-py3-none-manylinux2014_i686.whl", hash = "sha256:1e9679245e3ea6498494b3028b90c7b25dbb2abe65c7d07423ecfc2d6218ff7c"},
|
||||||
|
{file = "watchdog-5.0.3-py3-none-manylinux2014_ppc64.whl", hash = "sha256:9413384f26b5d050b6978e6fcd0c1e7f0539be7a4f1a885061473c5deaa57221"},
|
||||||
|
{file = "watchdog-5.0.3-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:294b7a598974b8e2c6123d19ef15de9abcd282b0fbbdbc4d23dfa812959a9e05"},
|
||||||
|
{file = "watchdog-5.0.3-py3-none-manylinux2014_s390x.whl", hash = "sha256:26dd201857d702bdf9d78c273cafcab5871dd29343748524695cecffa44a8d97"},
|
||||||
|
{file = "watchdog-5.0.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:0f9332243355643d567697c3e3fa07330a1d1abf981611654a1f2bf2175612b7"},
|
||||||
|
{file = "watchdog-5.0.3-py3-none-win32.whl", hash = "sha256:c66f80ee5b602a9c7ab66e3c9f36026590a0902db3aea414d59a2f55188c1f49"},
|
||||||
|
{file = "watchdog-5.0.3-py3-none-win_amd64.whl", hash = "sha256:f00b4cf737f568be9665563347a910f8bdc76f88c2970121c86243c8cfdf90e9"},
|
||||||
|
{file = "watchdog-5.0.3-py3-none-win_ia64.whl", hash = "sha256:49f4d36cb315c25ea0d946e018c01bb028048023b9e103d3d3943f58e109dd45"},
|
||||||
|
{file = "watchdog-5.0.3.tar.gz", hash = "sha256:108f42a7f0345042a854d4d0ad0834b741d421330d5f575b81cb27b883500176"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "win32-setctime"
|
||||||
|
version = "1.1.0"
|
||||||
|
requires_python = ">=3.5"
|
||||||
|
summary = "A small Python utility to set file creation time on Windows"
|
||||||
|
groups = ["default"]
|
||||||
|
marker = "sys_platform == \"win32\""
|
||||||
|
files = [
|
||||||
|
{file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
|
||||||
|
{file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
|
||||||
|
]
|
@@ -10,17 +10,18 @@ readme = "README.md"
|
|||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "snowykami", email = "snowykami@outlook.com" },
|
{ name = "snowykami", email = "snowykami@outlook.com" },
|
||||||
{ name = "LiteyukiStudio", email = "studio@liteyuki.icu" },
|
|
||||||
]
|
]
|
||||||
license = { text = "MIT&LSO" }
|
license = { text = "MIT&LSO" }
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"loguru~=0.7.2",
|
"loguru~=0.7.2",
|
||||||
"pydantic==2.8.2",
|
"pydantic>=2.9.2",
|
||||||
"PyYAML==6.0.2",
|
"PyYAML>=6.0.2",
|
||||||
"toml==0.10.2",
|
"toml>=0.10.2",
|
||||||
"watchdog==4.0.1",
|
"watchdog>=4.0.1",
|
||||||
"pdm-backend==2.3.3"
|
"pdm-backend>=2.3.3",
|
||||||
|
"magicoca>=1.0.6",
|
||||||
|
"croterline>=1.0.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
@@ -38,5 +39,14 @@ includes = ["liteyuki/", "LICENSE", "README.md"]
|
|||||||
excludes = ["tests/", "docs/", "src/"]
|
excludes = ["tests/", "docs/", "src/"]
|
||||||
|
|
||||||
[tool.pdm.version]
|
[tool.pdm.version]
|
||||||
source = "file"
|
source = "scm"
|
||||||
path = "liteyuki/__init__.py"
|
tag_filter = "v*"
|
||||||
|
tag_regex = '^v(?:\D*)?(?P<version>([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*((a|b|c|rc)(0|[1-9][0-9]*))?(\.post(0|[1-9][0-9]*))?(\.dev(0|[1-9][0-9]*))?$)$'
|
||||||
|
|
||||||
|
[tool.pdm.dev-dependencies]
|
||||||
|
dev = [
|
||||||
|
"pytest>=8.3.3",
|
||||||
|
"black>=24.10.0",
|
||||||
|
"uv>=0.4.20",
|
||||||
|
"mypy>=1.11.2",
|
||||||
|
]
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
# app dependencies
|
||||||
aiohttp>=3.9.3
|
aiohttp>=3.9.3
|
||||||
aiofiles>=23.2.1
|
aiofiles>=23.2.1
|
||||||
colored>=2.2.4
|
colored>=2.2.4
|
||||||
@@ -6,9 +7,10 @@ httpx>=0.27.0
|
|||||||
nonebot-plugin-htmlrender>=0.1.0
|
nonebot-plugin-htmlrender>=0.1.0
|
||||||
nonebot2[fastapi,httpx,websockets]>=2.3.3
|
nonebot2[fastapi,httpx,websockets]>=2.3.3
|
||||||
nonebot-adapter-onebot>=2.4.3
|
nonebot-adapter-onebot>=2.4.3
|
||||||
nonebot-plugin-alconna>=0.46.3
|
nonebot-plugin-alconna>=0.53.1
|
||||||
nonebot_plugin_apscheduler>=0.4.0
|
nonebot_plugin_apscheduler>=0.4.0
|
||||||
nonebot-adapter-satori>=0.11.5
|
nonebot-adapter-satori>=0.11.5
|
||||||
|
mysql-connector-python>=9.1.0
|
||||||
pyppeteer>=2.0.0
|
pyppeteer>=2.0.0
|
||||||
markdown>=3.3.6
|
markdown>=3.3.6
|
||||||
zhDateTime>=1.0.3
|
zhDateTime>=1.0.3
|
||||||
@@ -26,3 +28,11 @@ importlib_metadata>=7.0.2
|
|||||||
watchdog>=4.0.0
|
watchdog>=4.0.0
|
||||||
jieba>=0.42.1
|
jieba>=0.42.1
|
||||||
python-dotenv>=1.0.1
|
python-dotenv>=1.0.1
|
||||||
|
loguru~=0.7.2
|
||||||
|
pydantic~=2.9.2
|
||||||
|
pip~=23.2.1
|
||||||
|
fastapi~=0.115.0
|
||||||
|
|
||||||
|
# liteyuki dependencies
|
||||||
|
croterline>=1.0.7
|
||||||
|
magicoca>=1.0.5
|
@@ -9,8 +9,8 @@ Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|||||||
@Software: PyCharm
|
@Software: PyCharm
|
||||||
"""
|
"""
|
||||||
from liteyuki.plugin import PluginMetadata, PluginType
|
from liteyuki.plugin import PluginMetadata, PluginType
|
||||||
from liteyuki.message.on import on_message
|
from liteyuki.session.on import on_message
|
||||||
from liteyuki.message.event import MessageEvent
|
from liteyuki.session.event import MessageEvent
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="你好轻雪",
|
name="你好轻雪",
|
||||||
|
@@ -1,53 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|
||||||
|
|
||||||
@Time : 2024/8/11 下午5:24
|
|
||||||
@Author : snowykami
|
|
||||||
@Email : snowykami@outlook.com
|
|
||||||
@File : __init__.py.py
|
|
||||||
@Software: PyCharm
|
|
||||||
"""
|
|
||||||
|
|
||||||
import nonebot
|
|
||||||
from liteyuki.utils import IS_MAIN_PROCESS
|
|
||||||
from liteyuki.plugin import PluginMetadata, PluginType
|
|
||||||
from .nb_utils import adapter_manager, driver_manager # type: ignore
|
|
||||||
from liteyuki.log import logger
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="NoneBot2启动器",
|
|
||||||
type=PluginType.APPLICATION,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def nb_run(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
初始化NoneBot并运行在子进程
|
|
||||||
Args:
|
|
||||||
**kwargs:
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
"""
|
|
||||||
# 给子进程传递通道对象
|
|
||||||
kwargs.update(kwargs.get("nonebot", {})) # nonebot配置优先
|
|
||||||
nonebot.init(**kwargs)
|
|
||||||
|
|
||||||
driver_manager.init(config=kwargs)
|
|
||||||
adapter_manager.init(kwargs)
|
|
||||||
adapter_manager.register()
|
|
||||||
|
|
||||||
try:
|
|
||||||
# nonebot.load_plugin("nonebot-plugin-lnpm") # 尝试加载轻雪NoneBot插件加载器(Nonebot插件)
|
|
||||||
nonebot.load_plugin("src.liteyuki_main") # 尝试加载轻雪主插件(Nonebot插件)
|
|
||||||
except Exception as e:
|
|
||||||
pass
|
|
||||||
nonebot.run()
|
|
||||||
|
|
||||||
|
|
||||||
if IS_MAIN_PROCESS:
|
|
||||||
from liteyuki import get_bot
|
|
||||||
from .dev_reloader import *
|
|
||||||
|
|
||||||
liteyuki = get_bot()
|
|
||||||
liteyuki.process_manager.add_target(name="nonebot", target=nb_run, args=(), kwargs=liteyuki.config)
|
|
37
src/liteyuki_plugins/nonebot/__init__.py
Normal file
37
src/liteyuki_plugins/nonebot/__init__.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import os.path
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
from croterline.utils import IsMainProcess
|
||||||
|
|
||||||
|
from liteyuki.core import sub_process_manager
|
||||||
|
from liteyuki.plugin import PluginMetadata, PluginType
|
||||||
|
|
||||||
|
__plugin_meta__ = PluginMetadata(
|
||||||
|
name="NoneBot2启动器",
|
||||||
|
type=PluginType.APPLICATION,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def nb_run(*args, **kwargs):
|
||||||
|
import nonebot
|
||||||
|
|
||||||
|
nonebot.init(**kwargs)
|
||||||
|
|
||||||
|
from .nb_utils import driver_manager, adapter_manager
|
||||||
|
|
||||||
|
driver_manager.init(config=kwargs)
|
||||||
|
adapter_manager.init(kwargs)
|
||||||
|
adapter_manager.register()
|
||||||
|
nonebot.load_plugin(Path(os.path.dirname(__file__)) / "np_main")
|
||||||
|
nonebot.run()
|
||||||
|
|
||||||
|
|
||||||
|
if IsMainProcess:
|
||||||
|
from .dev_reloader import *
|
||||||
|
|
||||||
|
bot = get_bot()
|
||||||
|
|
||||||
|
sub_process_manager.add(
|
||||||
|
name="nonebot", func=nb_run, **bot.config.get("nonebot", {})
|
||||||
|
)
|
@@ -10,15 +10,17 @@ from liteyuki.utils import IS_MAIN_PROCESS
|
|||||||
from watchdog.events import FileSystemEvent
|
from watchdog.events import FileSystemEvent
|
||||||
|
|
||||||
|
|
||||||
liteyuki = get_bot()
|
bot = get_bot()
|
||||||
|
|
||||||
exclude_extensions = (".pyc", ".pyo")
|
exclude_extensions = (".pyc", ".pyo")
|
||||||
|
|
||||||
|
|
||||||
@observer.on_file_system_event(
|
@observer.on_file_system_event(
|
||||||
directories=("src/nonebot_plugins",),
|
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)
|
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):
|
def restart_nonebot_process(event: FileSystemEvent):
|
||||||
logger.debug(f"File {event.src_path} changed, reloading nonebot...")
|
logger.debug(f"File {event.src_path} changed, reloading nonebot...")
|
||||||
liteyuki.restart_process("nonebot")
|
bot.restart_process("nonebot")
|
@@ -10,7 +10,7 @@ from .common import MessageEventModel, msg_db
|
|||||||
from src.utils.base.language import Language
|
from src.utils.base.language import Language
|
||||||
from src.utils.base.resource import get_path
|
from src.utils.base.resource import get_path
|
||||||
from src.utils.message.string_tool import convert_seconds_to_time
|
from src.utils.message.string_tool import convert_seconds_to_time
|
||||||
from ...utils.external.logo import get_group_icon, get_user_icon
|
from src.utils.external.logo import get_group_icon, get_user_icon
|
||||||
|
|
||||||
|
|
||||||
async def count_msg_by_bot_id(bot_id: str) -> int:
|
async def count_msg_by_bot_id(bot_id: str) -> int:
|
@@ -15,7 +15,7 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
from ...utils.base.data_manager import set_memory_data
|
from src.utils.base.data_manager import set_memory_data
|
||||||
|
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
|
|
@@ -3,8 +3,8 @@ import aiohttp
|
|||||||
from .qw_models import *
|
from .qw_models import *
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from ...utils.base.data_manager import get_memory_data
|
from src.utils.base.data_manager import get_memory_data
|
||||||
from ...utils.base.language import Language
|
from src.utils.base.language import Language
|
||||||
|
|
||||||
dev_url = "https://devapi.qweather.com/" # 开发HBa
|
dev_url = "https://devapi.qweather.com/" # 开发HBa
|
||||||
com_url = "https://api.qweather.com/" # 正式环境
|
com_url = "https://api.qweather.com/" # 正式环境
|
@@ -0,0 +1,109 @@
|
|||||||
|
import asyncio
|
||||||
|
import aiohttp
|
||||||
|
from nonebot import logger, on_message, Bot
|
||||||
|
from nonebot.internal.adapter import Event
|
||||||
|
from nonebot.plugin import PluginMetadata
|
||||||
|
from nonebot import get_driver
|
||||||
|
|
||||||
|
from .adapter_ctx import Context
|
||||||
|
from .config import plugin_config, Config, NOTICE, MESSAGE
|
||||||
|
from .gotify import Message, msg_chan, fetch_msg
|
||||||
|
|
||||||
|
__plugin_meta__ = PluginMetadata(
|
||||||
|
name="Gotify",
|
||||||
|
description="使用Gotify API发送消息",
|
||||||
|
usage="后台服务插件,无需用户交互",
|
||||||
|
)
|
||||||
|
|
||||||
|
# on plugin load
|
||||||
|
driver = get_driver()
|
||||||
|
|
||||||
|
async def push_thread():
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
while True:
|
||||||
|
msg = await fetch_msg()
|
||||||
|
try:
|
||||||
|
logger.debug(f"Pushing message: {msg}")
|
||||||
|
async with session.post(
|
||||||
|
url=plugin_config.gotify_url + "/message",
|
||||||
|
params={"token": plugin_config.gotify_token},
|
||||||
|
data={
|
||||||
|
"title": msg.title,
|
||||||
|
"message": msg.message,
|
||||||
|
"priority": msg.priority,
|
||||||
|
},
|
||||||
|
) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
logger.error(
|
||||||
|
f"Push message to server failed: {await resp.text()}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.info(f"Push message to server success: {msg}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Push message to server failed: {e}")
|
||||||
|
|
||||||
|
@driver.on_startup
|
||||||
|
async def start_push_thread():
|
||||||
|
asyncio.create_task(push_thread())
|
||||||
|
logger.info(
|
||||||
|
f"Gotify plugin loaded, server: {plugin_config.gotify_url}, token: {plugin_config.gotify_token}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if MESSAGE in plugin_config.gotify_includes:
|
||||||
|
@on_message(block=False, priority=100).handle()
|
||||||
|
async def _(event: Event):
|
||||||
|
ctx = Context(
|
||||||
|
user_id=event.get_user_id(),
|
||||||
|
nickname="",
|
||||||
|
message=event.get_plaintext(),
|
||||||
|
message_type=event.get_type(),
|
||||||
|
)
|
||||||
|
ctx.handle(event)
|
||||||
|
|
||||||
|
msg_chan << Message(
|
||||||
|
title=plugin_config.gotify_title.format(**ctx.model_dump()),
|
||||||
|
message=plugin_config.gotify_message.format(**ctx.model_dump()),
|
||||||
|
)
|
||||||
|
|
||||||
|
if NOTICE in plugin_config.gotify_includes:
|
||||||
|
@driver.on_startup
|
||||||
|
async def startup():
|
||||||
|
if NOTICE in plugin_config.gotify_includes:
|
||||||
|
msg_chan << Message(
|
||||||
|
title=plugin_config.gotify_nickname,
|
||||||
|
message="Bot started",
|
||||||
|
priority=plugin_config.gotify_priority,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@driver.on_shutdown
|
||||||
|
async def shutdown():
|
||||||
|
if NOTICE in plugin_config.gotify_includes:
|
||||||
|
msg_chan << Message(
|
||||||
|
title=plugin_config.gotify_nickname,
|
||||||
|
message="Bot stopped",
|
||||||
|
priority=plugin_config.gotify_priority,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@driver.on_bot_connect
|
||||||
|
async def bot_connect(bot: Bot):
|
||||||
|
if NOTICE in plugin_config.gotify_includes:
|
||||||
|
msg_chan << Message(
|
||||||
|
title=plugin_config.gotify_nickname,
|
||||||
|
message=f"Bot connected: {bot.self_id}",
|
||||||
|
priority=plugin_config.gotify_priority,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@driver.on_bot_disconnect
|
||||||
|
async def bot_disconnect(bot: Bot):
|
||||||
|
if NOTICE in plugin_config.gotify_includes:
|
||||||
|
msg_chan << Message(
|
||||||
|
title=plugin_config.gotify_nickname,
|
||||||
|
message=f"Bot disconnected: {bot.self_id}",
|
||||||
|
priority=plugin_config.gotify_priority,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -0,0 +1,12 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Context(BaseModel):
|
||||||
|
user_id: str
|
||||||
|
nickname: str
|
||||||
|
message: str
|
||||||
|
message_type: str | None = None
|
||||||
|
|
||||||
|
def handle(self, event):
|
||||||
|
pass
|
||||||
|
|
@@ -0,0 +1,22 @@
|
|||||||
|
from nonebot import get_plugin_config
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
NOTICE = "notice"
|
||||||
|
MESSAGE = "message"
|
||||||
|
|
||||||
|
class Config(BaseModel):
|
||||||
|
# required fields
|
||||||
|
gotify_token: str
|
||||||
|
|
||||||
|
# optional fields
|
||||||
|
gotify_url: str = "http://127.0.0.1:40266"
|
||||||
|
gotify_priority: int = 5
|
||||||
|
gotify_nickname: str = "NoneBot"
|
||||||
|
gotify_title: str = "{message_type}: {nickname}({user_id})"
|
||||||
|
gotify_message: str = "{message}"
|
||||||
|
gotify_includes: list[str, ...] = [NOTICE, MESSAGE]
|
||||||
|
|
||||||
|
|
||||||
|
plugin_config = get_plugin_config(Config)
|
||||||
|
if plugin_config.gotify_url.endswith("/"):
|
||||||
|
plugin_config.gotify_url = plugin_config.gotify_url[:-1]
|
@@ -0,0 +1,20 @@
|
|||||||
|
import asyncio
|
||||||
|
from asyncio import Future
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from magicoca import Chan
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from .config import plugin_config
|
||||||
|
|
||||||
|
msg_chan = Chan["Message"]()
|
||||||
|
|
||||||
|
|
||||||
|
class Message(BaseModel):
|
||||||
|
title: str
|
||||||
|
message: str
|
||||||
|
priority: int = plugin_config.gotify_priority
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_msg() -> Future[Any]:
|
||||||
|
return asyncio.get_event_loop().run_in_executor(func=msg_chan.recv, executor=None)
|
33
src/liteyuki_plugins/nonebot/nonebot_plugins/to_liteyuki.py
Normal file
33
src/liteyuki_plugins/nonebot/nonebot_plugins/to_liteyuki.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||||
|
|
||||||
|
@Time : 2024/8/20 上午5:10
|
||||||
|
@Author : snowykami
|
||||||
|
@Email : snowykami@outlook.com
|
||||||
|
@File : to_liteyuki.py
|
||||||
|
@Software: PyCharm
|
||||||
|
"""
|
||||||
|
|
||||||
|
from croterline.process import get_ctx
|
||||||
|
from nonebot.adapters.onebot.v11 import MessageEvent
|
||||||
|
from nonebot.plugin import PluginMetadata
|
||||||
|
from nonebot.log import logger
|
||||||
|
from nonebot import on_message
|
||||||
|
|
||||||
|
__plugin_meta__ = PluginMetadata(
|
||||||
|
name="轻雪push",
|
||||||
|
description="把消息事件传递给轻雪框架进行处理",
|
||||||
|
usage="用户无需使用",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ctx = get_ctx()
|
||||||
|
|
||||||
|
@on_message(block=False, priority=100).handle()
|
||||||
|
async def _(event: MessageEvent):
|
||||||
|
logger.debug("Pushing message to Liteyuki")
|
||||||
|
ctx.sub_chan << event.raw_message
|
||||||
|
logger.debug("Pushed message to Liteyuki")
|
||||||
|
|
@@ -14,7 +14,7 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..utils.base.language import Language, get_default_lang_code
|
from src.utils.base.language import Language, get_default_lang_code
|
||||||
|
|
||||||
sys_lang = Language(get_default_lang_code())
|
sys_lang = Language(get_default_lang_code())
|
||||||
nonebot.logger.info(sys_lang.get("main.current_language", LANG=sys_lang.get("language.name")))
|
nonebot.logger.info(sys_lang.get("main.current_language", LANG=sys_lang.get("language.name")))
|
@@ -1,9 +1,6 @@
|
|||||||
import time
|
import time
|
||||||
from typing import AnyStr
|
from typing import AnyStr
|
||||||
|
|
||||||
import time
|
|
||||||
from typing import AnyStr
|
|
||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
import pip
|
import pip
|
||||||
from nonebot import get_driver, require
|
from nonebot import get_driver, require
|
||||||
@@ -14,15 +11,15 @@ from nonebot.permission import SUPERUSER
|
|||||||
|
|
||||||
# from src.liteyuki.core import Reloader
|
# from src.liteyuki.core import Reloader
|
||||||
from src.utils import event as event_utils, satori_utils
|
from src.utils import event as event_utils, satori_utils
|
||||||
|
from src.utils.base import reload # type: ignore
|
||||||
from src.utils.base.config import get_config
|
from src.utils.base.config import get_config
|
||||||
from src.utils.base.data_manager import TempConfig, common_db
|
from src.utils.base.data_manager import TempConfig, common_db
|
||||||
from src.utils.base.language import get_user_lang
|
from src.utils.base.language import get_user_lang
|
||||||
|
from src.utils.base.ly_function import get_function # type: ignore
|
||||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||||
|
from src.utils.message.html_tool import md_to_pic
|
||||||
from src.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
|
from src.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
|
||||||
from .api import update_liteyuki # type: ignore
|
from .api import update_liteyuki # type: ignore
|
||||||
from ..utils.base import reload # type: ignore
|
|
||||||
from ..utils.base.ly_function import get_function # type: ignore
|
|
||||||
from ..utils.message.html_tool import md_to_pic
|
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
require("nonebot_plugin_apscheduler")
|
require("nonebot_plugin_apscheduler")
|
@@ -1,31 +1,34 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import os.path
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import nonebot.plugin
|
import nonebot.plugin
|
||||||
from nonebot import get_driver
|
from nonebot import get_driver
|
||||||
from src.utils import init_log
|
|
||||||
from src.utils.base.config import get_config
|
from src.utils.base.config import get_config
|
||||||
from src.utils.base.data_manager import InstalledPlugin, plugin_db
|
from src.utils.base.data_manager import InstalledPlugin, plugin_db
|
||||||
from src.utils.base.resource import load_resources
|
from src.utils.base.resource import load_resources
|
||||||
from src.utils.message.tools import check_for_package
|
from src.utils.message.tools import check_for_package
|
||||||
|
|
||||||
load_resources()
|
load_resources()
|
||||||
init_log()
|
|
||||||
|
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
|
|
||||||
|
|
||||||
@driver.on_startup
|
@driver.on_startup
|
||||||
async def load_plugins():
|
async def load_plugins():
|
||||||
nonebot.plugin.load_plugins("src/nonebot_plugins")
|
nonebot.plugin.load_plugins(os.path.abspath(os.path.join(os.path.dirname(__file__), "../nonebot_plugins")))
|
||||||
# 从数据库读取已安装的插件
|
# 从数据库读取已安装的插件
|
||||||
if not get_config("safe_mode", False):
|
if not get_config("safe_mode", False):
|
||||||
# 安全模式下,不加载插件
|
# 安全模式下,不加载插件
|
||||||
installed_plugins: list[InstalledPlugin] = plugin_db.where_all(InstalledPlugin())
|
installed_plugins: list[InstalledPlugin] = plugin_db.where_all(
|
||||||
|
InstalledPlugin()
|
||||||
|
)
|
||||||
if installed_plugins:
|
if installed_plugins:
|
||||||
for installed_plugin in installed_plugins:
|
for installed_plugin in installed_plugins:
|
||||||
if not check_for_package(installed_plugin.module_name):
|
if not check_for_package(installed_plugin.module_name):
|
||||||
nonebot.logger.error(
|
nonebot.logger.error(
|
||||||
f"{installed_plugin.module_name} not installed, but still in loader index.")
|
f"{installed_plugin.module_name} not installed, but still in loader index."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
nonebot.load_plugin(installed_plugin.module_name)
|
nonebot.load_plugin(installed_plugin.module_name)
|
||||||
nonebot.plugin.load_plugins("plugins")
|
nonebot.plugin.load_plugins("plugins")
|
0
src/liteyuki_plugins/nonebot/np_main/uitls.py
Normal file
0
src/liteyuki_plugins/nonebot/np_main/uitls.py
Normal file
@@ -1,16 +0,0 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
from .auto_update import *
|
|
||||||
|
|
||||||
__author__ = "expliyh"
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="Satori 用户数据自动更新(临时措施)",
|
|
||||||
description="",
|
|
||||||
usage="",
|
|
||||||
type="application",
|
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
|
||||||
extra={
|
|
||||||
"liteyuki": True,
|
|
||||||
"toggleable" : True,
|
|
||||||
"default_enable" : True,
|
|
||||||
}
|
|
||||||
)
|
|
@@ -1,20 +0,0 @@
|
|||||||
import nonebot
|
|
||||||
|
|
||||||
from nonebot.message import event_preprocessor
|
|
||||||
from src.utils.base.ly_typing import T_MessageEvent
|
|
||||||
from src.utils import satori_utils
|
|
||||||
from nonebot.adapters import satori
|
|
||||||
from nonebot_plugin_alconna.typings import Event
|
|
||||||
from src.nonebot_plugins.liteyuki_status.counter_for_satori import satori_counter
|
|
||||||
|
|
||||||
|
|
||||||
@event_preprocessor
|
|
||||||
async def pre_handle(event: Event):
|
|
||||||
if isinstance(event, satori.MessageEvent):
|
|
||||||
if event.user.id == event.self_id:
|
|
||||||
satori_counter.msg_sent += 1
|
|
||||||
else:
|
|
||||||
satori_counter.msg_received += 1
|
|
||||||
if event.user.name is not None:
|
|
||||||
if await satori_utils.user_infos.put(event.user):
|
|
||||||
nonebot.logger.info(f"Satori user {event.user.name}<{event.user.id}> updated")
|
|
@@ -1,17 +0,0 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
from .api import *
|
|
||||||
|
|
||||||
__author__ = "snowykami"
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="联合黑名单(测试中...)",
|
|
||||||
description="",
|
|
||||||
usage="",
|
|
||||||
type="application",
|
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
|
||||||
extra={
|
|
||||||
"liteyuki": True,
|
|
||||||
"toggleable" : True,
|
|
||||||
"default_enable" : True,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
@@ -1,58 +0,0 @@
|
|||||||
import datetime
|
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
import nonebot
|
|
||||||
from nonebot import require
|
|
||||||
from nonebot.exception import IgnoredException
|
|
||||||
from nonebot.message import event_preprocessor
|
|
||||||
from nonebot_plugin_alconna.typings import Event
|
|
||||||
|
|
||||||
require("nonebot_plugin_apscheduler")
|
|
||||||
|
|
||||||
from nonebot_plugin_apscheduler import scheduler
|
|
||||||
|
|
||||||
blacklist_data: dict[str, set[str]] = {}
|
|
||||||
blacklist: set[str] = set()
|
|
||||||
|
|
||||||
|
|
||||||
@scheduler.scheduled_job("interval", minutes=10, next_run_time=datetime.datetime.now())
|
|
||||||
async def update_blacklist():
|
|
||||||
await request_for_blacklist()
|
|
||||||
|
|
||||||
|
|
||||||
async def request_for_blacklist():
|
|
||||||
global blacklist
|
|
||||||
urls = [
|
|
||||||
"https://cdn.liteyuki.icu/static/ubl/"
|
|
||||||
]
|
|
||||||
|
|
||||||
platforms = [
|
|
||||||
"qq"
|
|
||||||
]
|
|
||||||
|
|
||||||
for plat in platforms:
|
|
||||||
for url in urls:
|
|
||||||
url += f"{plat}.txt"
|
|
||||||
async with aiohttp.ClientSession() as client:
|
|
||||||
resp = await client.get(url)
|
|
||||||
blacklist_data[plat] = set((await resp.text()).splitlines())
|
|
||||||
blacklist = get_uni_set()
|
|
||||||
nonebot.logger.info("blacklists updated")
|
|
||||||
|
|
||||||
|
|
||||||
def get_uni_set() -> set:
|
|
||||||
s = set()
|
|
||||||
for new_set in blacklist_data.values():
|
|
||||||
s.update(new_set)
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
@event_preprocessor
|
|
||||||
async def pre_handle(event: Event):
|
|
||||||
try:
|
|
||||||
user_id = str(event.get_user_id())
|
|
||||||
except:
|
|
||||||
return
|
|
||||||
|
|
||||||
if user_id in get_uni_set():
|
|
||||||
raise IgnoredException("UserId in blacklist")
|
|
@@ -1,55 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
|
||||||
|
|
||||||
@Time : 2024/8/20 上午5:10
|
|
||||||
@Author : snowykami
|
|
||||||
@Email : snowykami@outlook.com
|
|
||||||
@File : to_liteyuki.py
|
|
||||||
@Software: PyCharm
|
|
||||||
"""
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from nonebot import Bot, get_bot, on_message, get_driver
|
|
||||||
from nonebot.plugin import PluginMetadata
|
|
||||||
from nonebot.adapters.onebot.v11 import MessageEvent, Bot
|
|
||||||
|
|
||||||
from liteyuki import Channel
|
|
||||||
from liteyuki.comm import get_channel
|
|
||||||
from liteyuki.comm.storage import shared_memory
|
|
||||||
from liteyuki.message.event import MessageEvent as LiteyukiMessageEvent
|
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
|
||||||
name="轻雪push",
|
|
||||||
description="把消息事件传递给轻雪框架进行处理",
|
|
||||||
usage="用户无需使用",
|
|
||||||
)
|
|
||||||
|
|
||||||
recv_channel = Channel[LiteyukiMessageEvent](name="event_to_nonebot")
|
|
||||||
|
|
||||||
|
|
||||||
# @on_message().handle()
|
|
||||||
# async def _(bot: Bot, event: MessageEvent):
|
|
||||||
# liteyuki_event = LiteyukiMessageEvent(
|
|
||||||
# message_type=event.message_type,
|
|
||||||
# message=event.dict()["message"],
|
|
||||||
# raw_message=event.raw_message,
|
|
||||||
# data=event.dict(),
|
|
||||||
# bot_id=bot.self_id,
|
|
||||||
# user_id=str(event.user_id),
|
|
||||||
# session_id=str(event.user_id if event.message_type == "private" else event.group_id),
|
|
||||||
# session_type=event.message_type,
|
|
||||||
# receive_channel=recv_channel,
|
|
||||||
# )
|
|
||||||
# shared_memory.publish("event_to_liteyuki", liteyuki_event)
|
|
||||||
|
|
||||||
|
|
||||||
# @get_driver().on_bot_connect
|
|
||||||
# async def _():
|
|
||||||
# while True:
|
|
||||||
# event = await recv_channel.async_receive()
|
|
||||||
# bot: Bot = get_bot(event.bot_id) # type: ignore
|
|
||||||
# if event.message_type == "private":
|
|
||||||
# await bot.send_private_msg(user_id=int(event.session_id), message=event.data["message"])
|
|
||||||
# elif event.message_type == "group":
|
|
||||||
# await bot.send_group_msg(group_id=int(event.session_id), message=event.data["message"])
|
|
@@ -6,7 +6,6 @@ __NAME__ = "LiteyukiBot"
|
|||||||
__VERSION__ = "6.3.2" # 60201
|
__VERSION__ = "6.3.2" # 60201
|
||||||
|
|
||||||
from src.utils.base.config import load_from_yaml, config
|
from src.utils.base.config import load_from_yaml, config
|
||||||
from src.utils.base.log import init_log
|
|
||||||
from git import Repo
|
from git import Repo
|
||||||
|
|
||||||
major, minor, patch = map(int, __VERSION__.split("."))
|
major, minor, patch = map(int, __VERSION__.split("."))
|
||||||
@@ -20,7 +19,6 @@ def init():
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# 检测python版本是否高于3.10
|
# 检测python版本是否高于3.10
|
||||||
init_log()
|
|
||||||
if sys.version_info < (3, 10):
|
if sys.version_info < (3, 10):
|
||||||
nonebot.logger.error("Requires Python3.10+ to run, please upgrade your Python Environment.")
|
nonebot.logger.error("Requires Python3.10+ to run, please upgrade your Python Environment.")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
@@ -1,79 +1,79 @@
|
|||||||
import sys
|
# import sys
|
||||||
import loguru
|
# import loguru
|
||||||
from typing import TYPE_CHECKING
|
# from typing import TYPE_CHECKING
|
||||||
from .config import load_from_yaml
|
# from .config import load_from_yaml
|
||||||
from .language import Language, get_default_lang_code
|
# from .language import Language, get_default_lang_code
|
||||||
|
#
|
||||||
logger = loguru.logger
|
# logger = loguru.logger
|
||||||
if TYPE_CHECKING:
|
# if TYPE_CHECKING:
|
||||||
# avoid sphinx autodoc resolve annotation failed
|
# # avoid sphinx autodoc resolve annotation failed
|
||||||
# because loguru module do not have `Logger` class actually
|
# # because loguru module do not have `Logger` class actually
|
||||||
from loguru import Record
|
# from loguru import Record
|
||||||
|
#
|
||||||
|
#
|
||||||
def default_filter(record: "Record"):
|
# def default_filter(record: "Record"):
|
||||||
"""默认的日志过滤器,根据 `config.log_level` 配置改变日志等级。"""
|
# """默认的日志过滤器,根据 `config.log_level` 配置改变日志等级。"""
|
||||||
log_level = record["extra"].get("nonebot_log_level", "INFO")
|
# log_level = record["extra"].get("nonebot_log_level", "INFO")
|
||||||
levelno = logger.level(log_level).no if isinstance(log_level, str) else log_level
|
# levelno = logger.level(log_level).no if isinstance(log_level, str) else log_level
|
||||||
return record["level"].no >= levelno
|
# return record["level"].no >= levelno
|
||||||
|
#
|
||||||
|
#
|
||||||
# DEBUG日志格式
|
# # DEBUG日志格式
|
||||||
debug_format: str = (
|
# debug_format: str = (
|
||||||
"<c>{time:YYYY-MM-DD HH:mm:ss}</c> "
|
# "<c>{time:YYYY-MM-DD HH:mm:ss}</c> "
|
||||||
"<lvl>[{level.icon}]</lvl> "
|
# "<lvl>[{level.icon}]</lvl> "
|
||||||
"<c><{name}.{module}.{function}:{line}></c> "
|
# "<c><{name}.{module}.{function}:{line}></c> "
|
||||||
"{message}"
|
# "{message}"
|
||||||
)
|
# )
|
||||||
|
#
|
||||||
# 默认日志格式
|
# # 默认日志格式
|
||||||
default_format: str = (
|
# default_format: str = (
|
||||||
"<c>{time:MM-DD HH:mm:ss}</c> "
|
# "<c>{time:MM-DD HH:mm:ss}</c> "
|
||||||
"<lvl>[{level.icon}]</lvl> "
|
# "<lvl>[{level.icon}]</lvl> "
|
||||||
"<c><{name}></c> "
|
# "<c><{name}></c> "
|
||||||
"{message}"
|
# "{message}"
|
||||||
)
|
# )
|
||||||
|
#
|
||||||
|
#
|
||||||
def get_format(level: str) -> str:
|
# def get_format(level: str) -> str:
|
||||||
if level == "DEBUG":
|
# if level == "DEBUG":
|
||||||
return debug_format
|
# return debug_format
|
||||||
else:
|
# else:
|
||||||
return default_format
|
# return default_format
|
||||||
|
#
|
||||||
|
#
|
||||||
logger = loguru.logger.bind()
|
# logger = loguru.logger.bind()
|
||||||
|
#
|
||||||
|
#
|
||||||
def init_log():
|
# def init_log():
|
||||||
"""
|
# """
|
||||||
在语言加载完成后执行
|
# 在语言加载完成后执行
|
||||||
Returns:
|
# Returns:
|
||||||
|
#
|
||||||
"""
|
# """
|
||||||
global logger
|
# global logger
|
||||||
|
#
|
||||||
config = load_from_yaml("config.yml")
|
# config = load_from_yaml("config.yml")
|
||||||
|
#
|
||||||
logger.remove()
|
# logger.remove()
|
||||||
logger.add(
|
# logger.add(
|
||||||
sys.stdout,
|
# sys.stdout,
|
||||||
level=0,
|
# level=0,
|
||||||
diagnose=False,
|
# diagnose=False,
|
||||||
filter=default_filter,
|
# filter=default_filter,
|
||||||
format=get_format(config.get("log_level", "INFO")),
|
# format=get_format(config.get("log_level", "INFO")),
|
||||||
)
|
# )
|
||||||
show_icon = config.get("log_icon", True)
|
# show_icon = config.get("log_icon", True)
|
||||||
lang = Language(get_default_lang_code())
|
# lang = Language(get_default_lang_code())
|
||||||
|
#
|
||||||
debug = lang.get("log.debug", default="==DEBUG")
|
# debug = lang.get("log.debug", default="==DEBUG")
|
||||||
info = lang.get("log.info", default="===INFO")
|
# info = lang.get("log.info", default="===INFO")
|
||||||
success = lang.get("log.success", default="SUCCESS")
|
# success = lang.get("log.success", default="SUCCESS")
|
||||||
warning = lang.get("log.warning", default="WARNING")
|
# warning = lang.get("log.warning", default="WARNING")
|
||||||
error = lang.get("log.error", default="==ERROR")
|
# error = lang.get("log.error", default="==ERROR")
|
||||||
|
#
|
||||||
logger.level("DEBUG", color="<blue>", icon=f"{'🐛' if show_icon else ''}{debug}")
|
# 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("INFO", color="<normal>", icon=f"{'ℹ️' if show_icon else ''}{info}")
|
||||||
logger.level("SUCCESS", color="<green>", icon=f"{'✅' if show_icon else ''}{success}")
|
# 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("WARNING", color="<yellow>", icon=f"{'⚠️' if show_icon else ''}{warning}")
|
||||||
logger.level("ERROR", color="<red>", icon=f"{'⭕' if show_icon else ''}{error}")
|
# logger.level("ERROR", color="<red>", icon=f"{'⭕' if show_icon else ''}{error}")
|
||||||
|
22
tests/test_ipc.py
Normal file
22
tests/test_ipc.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from liteyuki.comm import Channel as Chan
|
||||||
|
from multiprocessing import Process
|
||||||
|
|
||||||
|
|
||||||
|
def p1(chan: Chan):
|
||||||
|
for i in range(10):
|
||||||
|
chan.send(i)
|
||||||
|
|
||||||
|
|
||||||
|
def p2(chan: Chan):
|
||||||
|
while True:
|
||||||
|
print(chan.recv())
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipc():
|
||||||
|
chan = Chan("Name")
|
||||||
|
|
||||||
|
p1_proc = Process(target=p1, args=(chan,))
|
||||||
|
p2_proc = Process(target=p2, args=(chan,))
|
||||||
|
|
||||||
|
p1_proc.start()
|
||||||
|
p2_proc.start()
|
9
tests/test_logger.py
Normal file
9
tests/test_logger.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from liteyuki import logger
|
||||||
|
|
||||||
|
def test_logger():
|
||||||
|
logger.info('Hello, World!')
|
||||||
|
logger.debug('Hello, World!')
|
||||||
|
logger.warning('Hello, World!')
|
||||||
|
logger.error('Hello, World!')
|
||||||
|
logger.critical('Hello, World!')
|
||||||
|
logger.success("Hello, World!")
|
Reference in New Issue
Block a user