mirror of
https://github.com/LiteyukiStudio/LiteyukiBot.git
synced 2025-05-22 20:41:17 +00:00
🔧 重构配置管理,添加配置文件支持,更新日志系统,优化守护进程,完善测试用例
This commit is contained in:
parent
fec96e694d
commit
56996ef082
@ -1,4 +1,4 @@
|
|||||||
name: Publish
|
name: Liteyuki PyPI Publish
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@ -6,8 +6,8 @@ on:
|
|||||||
- 'v*'
|
- 'v*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pypi-publish:
|
liteyuki-pypi-publish:
|
||||||
name: upload release to PyPI
|
name: upload release to PyPI (Nightly)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -21,3 +21,7 @@ Thumbs.db
|
|||||||
.env.*
|
.env.*
|
||||||
plugins/
|
plugins/
|
||||||
data/
|
data/
|
||||||
|
configs/
|
||||||
|
config.yaml
|
||||||
|
config-dev.yaml
|
||||||
|
config-prod.yaml
|
18
README.md
18
README.md
@ -59,3 +59,21 @@
|
|||||||
[liteyukilab-link]:https://lab.liteyuki.icu/@LiteyukiBot
|
[liteyukilab-link]:https://lab.liteyuki.icu/@LiteyukiBot
|
||||||
|
|
||||||
[banner]: https://socialify.git.ci/LiteyukiStudio/LiteyukiBot/image?description=1&forks=1&issues=1&Plus&pulls=1&stargazers=1&theme=Auto&logo=https%3a%2f%2fcdn.liteyuki.icu%2fstatic%2fsvg%2flylogo-full.svg
|
[banner]: https://socialify.git.ci/LiteyukiStudio/LiteyukiBot/image?description=1&forks=1&issues=1&Plus&pulls=1&stargazers=1&theme=Auto&logo=https%3a%2f%2fcdn.liteyuki.icu%2fstatic%2fsvg%2flylogo-full.svg
|
||||||
|
|
||||||
|
## 开发环境配置
|
||||||
|
|
||||||
|
1. 项目使用uv进行包管理,你也可以使用uv进行环境管理,[安装uv](https://docs.astral.sh/uv/#installation)
|
||||||
|
|
||||||
|
2. 进入项目目录使用uv同步环境和依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv sync --all # 安装包括dev和prod的所有依赖
|
||||||
|
```
|
||||||
|
|
||||||
|
3. VSCode扩展
|
||||||
|
|
||||||
|
- Python
|
||||||
|
- Mypy
|
||||||
|
- Ruff
|
||||||
|
|
||||||
|
4. 环境变量指定ENVIRONMENT=dev或prod或其他,然后加载.env.{}文件,环境变量
|
@ -7,5 +7,6 @@ services:
|
|||||||
- ./plugins:/liteyukibot/plugins
|
- ./plugins:/liteyukibot/plugins
|
||||||
- ./data:/liteyukibot/data
|
- ./data:/liteyukibot/data
|
||||||
- ./configs:/liteyukibot/configs
|
- ./configs:/liteyukibot/configs
|
||||||
|
- ./config.yaml:/liteyukibot/config.yaml
|
||||||
ports:
|
ports:
|
||||||
- "8090:8080"
|
- "8090:8080"
|
@ -11,5 +11,6 @@ services:
|
|||||||
- ./configs:/liteyukibot/configs # 配置目录,包含配置文件
|
- ./configs:/liteyukibot/configs # 配置目录,包含配置文件
|
||||||
- ./data:/liteyukibot/data # 数据目录,包含下载器安装的插件目录
|
- ./data:/liteyukibot/data # 数据目录,包含下载器安装的插件目录
|
||||||
- ./plugins:/liteyukibot/plugins # 外部插件目录,插件开发者可用
|
- ./plugins:/liteyukibot/plugins # 外部插件目录,插件开发者可用
|
||||||
|
- ./config.yaml:/liteyukibot/config.yaml # 配置文件,包含所有配置项
|
||||||
ports:
|
ports:
|
||||||
- "8090:8080"
|
- "8090:8080"
|
@ -1,3 +1,7 @@
|
|||||||
from .daemon import Daemon
|
from .daemon import Daemon
|
||||||
|
from .log import logger
|
||||||
|
|
||||||
__all__ = ["Daemon"]
|
__all__ = [
|
||||||
|
"Daemon",
|
||||||
|
"logger"
|
||||||
|
]
|
@ -1,8 +1,5 @@
|
|||||||
import asyncio
|
import uvicorn
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
import hypercorn
|
|
||||||
import hypercorn.run
|
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
@ -11,10 +8,8 @@ async def root():
|
|||||||
return {"message": "Hello LiteyukiBot"}
|
return {"message": "Hello LiteyukiBot"}
|
||||||
|
|
||||||
|
|
||||||
async def run_app():
|
async def run_app(**kwargs):
|
||||||
"""liteyukibot入口函数
|
"""ASGI app 启动函数,在所有插件加载完后任务启动"""
|
||||||
"""
|
config = uvicorn.Config(app, **kwargs, log_config=None)
|
||||||
hypercorn.run.serve(app, config=hypercorn.Config.from_mapping(
|
server = uvicorn.Server(config)
|
||||||
bind=["localhost:8000"],
|
await server.serve()
|
||||||
workers=1,
|
|
||||||
))
|
|
@ -1,13 +1,13 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import tomllib
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import json
|
|
||||||
import yaml
|
import yaml
|
||||||
import tomllib
|
|
||||||
|
|
||||||
config: dict[str, Any] = {} # 全局配置map
|
type RawConfig = dict[str, Any]
|
||||||
flat_config: dict[str, Any] = {} # 扁平化配置map
|
|
||||||
|
|
||||||
def load_from_yaml(file_path: str) -> dict[str, Any]:
|
def load_from_yaml(file_path: str) -> RawConfig:
|
||||||
"""从yaml文件中加载配置并返回字典
|
"""从yaml文件中加载配置并返回字典
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -19,7 +19,7 @@ def load_from_yaml(file_path: str) -> dict[str, Any]:
|
|||||||
with open(file_path, "r", encoding="utf-8") as file:
|
with open(file_path, "r", encoding="utf-8") as file:
|
||||||
return yaml.safe_load(file)
|
return yaml.safe_load(file)
|
||||||
|
|
||||||
def load_from_json(file_path: str) -> dict[str, Any]:
|
def load_from_json(file_path: str) -> RawConfig:
|
||||||
"""从json文件中加载配置并返回字典
|
"""从json文件中加载配置并返回字典
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -32,7 +32,7 @@ def load_from_json(file_path: str) -> dict[str, Any]:
|
|||||||
with open(file_path, "r", encoding="utf-8") as file:
|
with open(file_path, "r", encoding="utf-8") as file:
|
||||||
return json.load(file)
|
return json.load(file)
|
||||||
|
|
||||||
def load_from_toml(file_path: str) -> dict[str, Any]:
|
def load_from_toml(file_path: str) -> RawConfig:
|
||||||
"""从toml文件中加载配置并返回字典
|
"""从toml文件中加载配置并返回字典
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -44,18 +44,26 @@ def load_from_toml(file_path: str) -> dict[str, Any]:
|
|||||||
with open(file_path, "rb") as file:
|
with open(file_path, "rb") as file:
|
||||||
return tomllib.load(file)
|
return tomllib.load(file)
|
||||||
|
|
||||||
def merge_to_config(new_config: dict[str, Any], warn: bool=True) -> None:
|
def merge_dicts(base: RawConfig, new: RawConfig) -> RawConfig:
|
||||||
"""加载配置到全局配置字典,该函数有副作用,开发者尽量不要在多份配置文件中使用重复的配置项,否则会被覆盖
|
"""递归合并两个字典
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
new_config (dict[str, Any]): 新的字典
|
base (dict[str, Any]): 原始字典
|
||||||
warn (bool, optional): 是否启用重复键警告. 默认 True.
|
new (dict[str, Any]): 新的字典
|
||||||
"""
|
|
||||||
global config, flat_config
|
|
||||||
config.update(new_config)
|
|
||||||
flat_config = flatten_dict(config)
|
|
||||||
|
|
||||||
def flatten_dict(d: dict[str, Any], parent_key: str = '', sep: str = '.') -> dict[str, Any]:
|
Returns:
|
||||||
|
dict[str, Any]: 合并后的字典
|
||||||
|
"""
|
||||||
|
for key, value in new.items():
|
||||||
|
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
|
||||||
|
# 如果当前键对应的值是字典,则递归合并
|
||||||
|
base[key] = merge_dicts(base[key], value)
|
||||||
|
else:
|
||||||
|
# 否则直接更新值
|
||||||
|
base[key] = value
|
||||||
|
return base
|
||||||
|
|
||||||
|
def flatten_dict(d: RawConfig, parent_key: str = '', sep: str = '.') -> RawConfig:
|
||||||
"""将嵌套字典扁平化
|
"""将嵌套字典扁平化
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -89,10 +97,19 @@ def flatten_dict(d: dict[str, Any], parent_key: str = '', sep: str = '.') -> dic
|
|||||||
items.append((new_key, v))
|
items.append((new_key, v))
|
||||||
return dict(items)
|
return dict(items)
|
||||||
|
|
||||||
def load_config_to_global(reset: bool = False) -> None:
|
def load_from_dir(dir_path: str) -> RawConfig:
|
||||||
"""加载配置到全局配置字典
|
"""从目录中加载配置文件
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
reset (bool, optional): 是否重置配置项. 默认 False.
|
dir_path (str): 目录路径
|
||||||
"""
|
"""
|
||||||
|
config: RawConfig = {}
|
||||||
|
for file_name in os.listdir(dir_path):
|
||||||
|
if file_name.endswith(".yaml") or file_name.endswith(".yml"):
|
||||||
|
config = merge_dicts(config, load_from_yaml(os.path.join(dir_path, file_name)) or {})
|
||||||
|
elif file_name.endswith(".json"):
|
||||||
|
config = merge_dicts(config, load_from_json(os.path.join(dir_path, file_name)) or {})
|
||||||
|
elif file_name.endswith(".toml"):
|
||||||
|
config = merge_dicts(config, load_from_toml(os.path.join(dir_path, file_name)) or {})
|
||||||
|
|
||||||
|
return config
|
@ -1,16 +1,71 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from .asgi import run_app
|
||||||
|
from .config import RawConfig, flatten_dict, load_from_dir, merge_dicts
|
||||||
|
from .log import logger, set_level
|
||||||
|
from .utils import pretty_format
|
||||||
|
|
||||||
|
|
||||||
class Daemon:
|
class Daemon:
|
||||||
def __init__(self, **config):
|
"""Liteyuki 的 守护进程
|
||||||
self.config = config
|
"""
|
||||||
|
def __init__(self, **kwargs: RawConfig):
|
||||||
|
"""Liteyuki Daemon Init
|
||||||
|
Args:
|
||||||
|
**kwargs: 其他配置项
|
||||||
|
"""
|
||||||
|
# 加载配置项
|
||||||
|
self.config: RawConfig = kwargs
|
||||||
|
# 获取配置文件目录
|
||||||
|
if isinstance(config_dir := kwargs.get("config_dir", None), str):
|
||||||
|
self.config = merge_dicts(self.config, load_from_dir(config_dir))
|
||||||
|
# 插入扁平化配置
|
||||||
|
self.config = merge_dicts(self.config, flatten_dict(self.config))
|
||||||
|
|
||||||
|
# 初始化日志
|
||||||
|
set_level(self.config.get("log_level", "INFO"))
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"configs: %s" % pretty_format(self.config, indent=2)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
"""liteyukibot入口函数
|
"""liteyukibot事件循环入口
|
||||||
"""
|
"""
|
||||||
pass
|
# load plugins
|
||||||
|
|
||||||
|
# run asgi app
|
||||||
|
asyncio.create_task(
|
||||||
|
run_app(
|
||||||
|
host=self.config.get("host", "127.0.0.1"),
|
||||||
|
port=self.config.get("port", 8080),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# 挂起
|
||||||
|
logger.info("Liteyuki Daemon is running...")
|
||||||
|
await asyncio.Event().wait()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""liteyukibot入口函数
|
"""Daemon入口函数
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
asyncio.run(self._run())
|
asyncio.run(self._run())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("Liteyuki Daemon is exiting...")
|
||||||
|
|
||||||
|
def bind_config[T: BaseModel](self, model: Type[T]) -> T:
|
||||||
|
"""将配置绑定到 Pydantic 模型,推荐使用`pydantic.Field`声明扁平化键字段名
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model (Type[T]): Pydantic 模型类
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
T: 绑定后的模型实例
|
||||||
|
"""
|
||||||
|
if not issubclass(model, BaseModel):
|
||||||
|
raise TypeError("The provided model must be a subclass of BaseModel.")
|
||||||
|
return model(**self.config)
|
39
liteyukibot/log.py
Normal file
39
liteyukibot/log.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import inspect
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from yukilog import default_debug_and_trace_format, default_format, get_logger
|
||||||
|
|
||||||
|
logger = get_logger("INFO")
|
||||||
|
|
||||||
|
class LoguruHandler(logging.Handler):
|
||||||
|
def emit(self, record: logging.LogRecord):
|
||||||
|
try:
|
||||||
|
level = logger.level(record.levelname).name
|
||||||
|
except ValueError:
|
||||||
|
level = str(record.levelno)
|
||||||
|
|
||||||
|
frame, depth = inspect.currentframe(), 0
|
||||||
|
while frame and (depth == 0 or frame.f_code.co_filename == logging.__file__):
|
||||||
|
frame = frame.f_back
|
||||||
|
depth += 1
|
||||||
|
|
||||||
|
logger.opt(depth=depth, exception=record.exc_info).log(
|
||||||
|
level, record.getMessage()
|
||||||
|
)
|
||||||
|
|
||||||
|
# 替换 logging 的全局日志器
|
||||||
|
root_logger = logging.getLogger()
|
||||||
|
root_logger.handlers = [LoguruHandler()] # 只保留 LoguruHandler
|
||||||
|
root_logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
def set_level(level: str):
|
||||||
|
"""设置日志级别
|
||||||
|
|
||||||
|
Args:
|
||||||
|
level (str): 日志级别
|
||||||
|
"""
|
||||||
|
logger.remove()
|
||||||
|
logger.add(sys.stdout, format=default_format if level not in ["DEBUG", "TRACE"] else default_debug_and_trace_format, level=level)
|
||||||
|
logging.getLogger().setLevel(level)
|
24
liteyukibot/utils/__init__.py
Normal file
24
liteyukibot/utils/__init__.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import json
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def pretty_print(obj: Any, indent: int=2) -> None:
|
||||||
|
"""
|
||||||
|
更好地打印对象
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj (Any): 要打印的对象
|
||||||
|
"""
|
||||||
|
print(json.dumps(obj, indent=indent, ensure_ascii=False))
|
||||||
|
|
||||||
|
def pretty_format(obj: Any, indent: int =2 ) -> str:
|
||||||
|
"""
|
||||||
|
更好地格式化对象
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj (Any): 要格式化的对象
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 格式化后的字符串
|
||||||
|
"""
|
||||||
|
return json.dumps(obj, indent=indent, ensure_ascii=False)
|
4
main.py
4
main.py
@ -1,5 +1,5 @@
|
|||||||
from liteyukibot import Daemon
|
from liteyukibot import Daemon, config
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
daemon = Daemon()
|
daemon = Daemon(**config.load_from_yaml("config.yaml") or {})
|
||||||
daemon.run()
|
daemon.run()
|
@ -7,37 +7,46 @@ requires-python = ">=3.12"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"fastapi>=0.115.12",
|
"fastapi>=0.115.12",
|
||||||
"loguru>=0.7.3",
|
"loguru>=0.7.3",
|
||||||
"nonecorn>=0.17.3",
|
"pip>=25.1",
|
||||||
"pydantic>=2.11.3",
|
"pydantic>=2.11.3",
|
||||||
"pyyaml>=6.0.2",
|
"pyyaml>=6.0.2",
|
||||||
"uvicorn>=0.34.2",
|
"uvicorn>=0.34.2",
|
||||||
"yukilog>=0.1.1",
|
"yukilog>=0.1.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# 开发依赖
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"mypy>=1.15.0",
|
"mypy>=1.15.0",
|
||||||
"pip>=25.1",
|
|
||||||
"pytest>=8.3.5",
|
"pytest>=8.3.5",
|
||||||
"ruff>=0.11.7",
|
"ruff>=0.11.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
# build
|
# 辅助工具配置
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 140
|
||||||
|
src = ["liteyukibot", "main.py"]
|
||||||
|
select = ["I", "F", "E"]
|
||||||
|
|
||||||
|
|
||||||
|
# 构建相关
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["pdm-backend >= 2.4.0"]
|
requires = ["pdm-backend >= 2.4.0"]
|
||||||
build-backend = "pdm.backend"
|
build-backend = "pdm.backend"
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.pdm.build]
|
||||||
line-length = 80
|
includes = ["liteyukibot"]
|
||||||
src = ["liteyukibot", "main.py"]
|
|
||||||
|
|
||||||
# index and sources
|
|
||||||
|
# 索引和源
|
||||||
[[tool.uv.index]]
|
[[tool.uv.index]]
|
||||||
name = "liteyuki-pypi"
|
name = "liteyuki-pypi"
|
||||||
url = "https://git.liteyuki.icu/api/packages/LiteyukiStudio/pypi/simple"
|
url = "https://git.liteyuki.icu/api/packages/LiteyukiStudio/pypi/simple"
|
||||||
publish-url = "https://git.liteyuki.icu/api/packages/LiteyukiStudio/pypi"
|
publish-url = "https://git.liteyuki.icu/api/packages/LiteyukiStudio/pypi"
|
||||||
explicit = true
|
explicit = true
|
||||||
|
|
||||||
|
|
||||||
|
# 从轻雪源安装的包请在此标记
|
||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
yukilog = [
|
yukilog = [
|
||||||
{ index = "liteyuki-pypi" },
|
{ index = "liteyuki-pypi" },
|
||||||
|
@ -1,9 +1,19 @@
|
|||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from liteyukibot import config
|
from liteyukibot import config
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigModel(BaseModel):
|
||||||
|
name: str
|
||||||
|
version: int
|
||||||
|
server_host: str = Field(alias="server.host")
|
||||||
|
server_port: int = Field(alias="server.port")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_load_from_yaml():
|
def test_load_from_yaml():
|
||||||
# 创建一个临时 YAML 文件内容
|
"""测试从yaml文件路径加载配置项"""
|
||||||
yaml_content = """
|
yaml_content = """
|
||||||
name: LiteyukiBot
|
name: LiteyukiBot
|
||||||
version: 7.0.0
|
version: 7.0.0
|
||||||
@ -18,6 +28,7 @@ def test_load_from_yaml():
|
|||||||
|
|
||||||
|
|
||||||
def test_load_from_json():
|
def test_load_from_json():
|
||||||
|
"""测试从json文件路径加载配置"""
|
||||||
json_content = '{"name": "LiteyukiBot", "version": "7.0.0"}'
|
json_content = '{"name": "LiteyukiBot", "version": "7.0.0"}'
|
||||||
with tempfile.NamedTemporaryFile("w", delete=False, suffix=".json") as temp_file:
|
with tempfile.NamedTemporaryFile("w", delete=False, suffix=".json") as temp_file:
|
||||||
temp_file.write(json_content)
|
temp_file.write(json_content)
|
||||||
@ -29,6 +40,7 @@ def test_load_from_json():
|
|||||||
|
|
||||||
|
|
||||||
def test_load_from_toml():
|
def test_load_from_toml():
|
||||||
|
"""测试从toml文件路径加载配置"""
|
||||||
toml_content = """
|
toml_content = """
|
||||||
[info]
|
[info]
|
||||||
name = "LiteyukiBot"
|
name = "LiteyukiBot"
|
||||||
@ -44,6 +56,7 @@ def test_load_from_toml():
|
|||||||
|
|
||||||
|
|
||||||
def test_flatten_dict():
|
def test_flatten_dict():
|
||||||
|
"""测试扁平化字典"""
|
||||||
nested_dict = {
|
nested_dict = {
|
||||||
"name": "LiteyukiBot",
|
"name": "LiteyukiBot",
|
||||||
"version": {
|
"version": {
|
||||||
@ -67,3 +80,74 @@ def test_flatten_dict():
|
|||||||
assert flat_dict["server.db.host"] == "localhost"
|
assert flat_dict["server.db.host"] == "localhost"
|
||||||
assert flat_dict["server.db.port"] == 8080
|
assert flat_dict["server.db.port"] == 8080
|
||||||
assert flat_dict["server.tags"] == ["tag1", "tag2"]
|
assert flat_dict["server.tags"] == ["tag1", "tag2"]
|
||||||
|
|
||||||
|
def test_merge_to_config():
|
||||||
|
"""测试合并配置"""
|
||||||
|
old_config = {
|
||||||
|
"name": "LiteyukiBot",
|
||||||
|
"version": 7,
|
||||||
|
"server": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 8080
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.merge_to_config(old_config)
|
||||||
|
assert config.config["name"] == "LiteyukiBot"
|
||||||
|
new_config = {
|
||||||
|
"version": 8,
|
||||||
|
"server": {
|
||||||
|
"port": 9090
|
||||||
|
},
|
||||||
|
"new_key": "new_value"
|
||||||
|
}
|
||||||
|
|
||||||
|
config.merge_to_config(new_config)
|
||||||
|
|
||||||
|
# config
|
||||||
|
assert config.config["name"] == "LiteyukiBot"
|
||||||
|
assert config.config["version"] == 8
|
||||||
|
assert config.config["server"]["host"] == "localhost"
|
||||||
|
assert config.config["server"]["port"] == 9090
|
||||||
|
assert config.config["new_key"] == "new_value"
|
||||||
|
# test flatten_config
|
||||||
|
assert config.flat_config["name"] == "LiteyukiBot"
|
||||||
|
assert config.flat_config["version"] == 8
|
||||||
|
assert config.flat_config["server.host"] == "localhost"
|
||||||
|
assert config.flat_config["server.port"] == 9090
|
||||||
|
assert config.flat_config["new_key"] == "new_value"
|
||||||
|
|
||||||
|
def test_get_config():
|
||||||
|
"""测试获取配置项"""
|
||||||
|
config_data = {
|
||||||
|
"name": "LiteyukiBot",
|
||||||
|
"version": 7,
|
||||||
|
"server": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 8080
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.merge_to_config(config_data)
|
||||||
|
assert config.get("name") == "LiteyukiBot"
|
||||||
|
assert config.get("version") == 7
|
||||||
|
assert config.get("server.host") == "localhost"
|
||||||
|
assert config.get("server.port") == 8080
|
||||||
|
assert config.get("non_existent_key", default="default_value") == "default_value"
|
||||||
|
assert config.get("non_existent_key", default=42) == 42
|
||||||
|
|
||||||
|
|
||||||
|
def test_bind():
|
||||||
|
"""测试配置项绑定到模型"""
|
||||||
|
config_data = {
|
||||||
|
"name": "LiteyukiBot",
|
||||||
|
"version": 7,
|
||||||
|
"server": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 8080
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.merge_to_config(config_data)
|
||||||
|
bound_model = config.bind(ConfigModel)
|
||||||
|
assert bound_model.name == "LiteyukiBot"
|
||||||
|
assert bound_model.version == 7
|
||||||
|
assert bound_model.server_host == "localhost"
|
||||||
|
assert bound_model.server_port == 8080
|
12
tests/test_log.py
Normal file
12
tests/test_log.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from liteyukibot import log, logger
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_level():
|
||||||
|
logger.info("Testing logger level")
|
||||||
|
logger.debug("Debug message")
|
||||||
|
log.set_level("DEBUG")
|
||||||
|
logger.debug("Debug message after level change")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_set_level()
|
||||||
|
print("测试完成,你应该只会看到一次debug信息")
|
91
uv.lock
generated
91
uv.lock
generated
@ -46,15 +46,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "exceptiongroup"
|
|
||||||
version = "1.2.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastapi"
|
name = "fastapi"
|
||||||
version = "0.115.12"
|
version = "0.115.12"
|
||||||
@ -78,37 +69,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 },
|
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "h2"
|
|
||||||
version = "4.2.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "hpack" },
|
|
||||||
{ name = "hyperframe" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/1b/38/d7f80fd13e6582fb8e0df8c9a653dcc02b03ca34f4d72f34869298c5baf8/h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f", size = 2150682 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hpack"
|
|
||||||
version = "4.1.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hyperframe"
|
|
||||||
version = "6.1.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.10"
|
version = "3.10"
|
||||||
@ -130,11 +90,11 @@ wheels = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "liteyukibot"
|
name = "liteyukibot"
|
||||||
version = "7.0.0"
|
version = "7.0.0"
|
||||||
source = { virtual = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "fastapi" },
|
{ name = "fastapi" },
|
||||||
{ name = "loguru" },
|
{ name = "loguru" },
|
||||||
{ name = "nonecorn" },
|
{ name = "pip" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
{ name = "pyyaml" },
|
{ name = "pyyaml" },
|
||||||
{ name = "uvicorn" },
|
{ name = "uvicorn" },
|
||||||
@ -144,7 +104,6 @@ dependencies = [
|
|||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "mypy" },
|
{ name = "mypy" },
|
||||||
{ name = "pip" },
|
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
{ name = "ruff" },
|
{ name = "ruff" },
|
||||||
]
|
]
|
||||||
@ -153,7 +112,7 @@ dev = [
|
|||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "fastapi", specifier = ">=0.115.12" },
|
{ name = "fastapi", specifier = ">=0.115.12" },
|
||||||
{ name = "loguru", specifier = ">=0.7.3" },
|
{ name = "loguru", specifier = ">=0.7.3" },
|
||||||
{ name = "nonecorn", specifier = ">=0.17.3" },
|
{ name = "pip", specifier = ">=25.1" },
|
||||||
{ name = "pydantic", specifier = ">=2.11.3" },
|
{ name = "pydantic", specifier = ">=2.11.3" },
|
||||||
{ name = "pyyaml", specifier = ">=6.0.2" },
|
{ name = "pyyaml", specifier = ">=6.0.2" },
|
||||||
{ name = "uvicorn", specifier = ">=0.34.2" },
|
{ name = "uvicorn", specifier = ">=0.34.2" },
|
||||||
@ -163,7 +122,6 @@ requires-dist = [
|
|||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "mypy", specifier = ">=1.15.0" },
|
{ name = "mypy", specifier = ">=1.15.0" },
|
||||||
{ name = "pip", specifier = ">=25.1" },
|
|
||||||
{ name = "pytest", specifier = ">=8.3.5" },
|
{ name = "pytest", specifier = ">=8.3.5" },
|
||||||
{ name = "ruff", specifier = ">=0.11.7" },
|
{ name = "ruff", specifier = ">=0.11.7" },
|
||||||
]
|
]
|
||||||
@ -215,22 +173,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 },
|
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nonecorn"
|
|
||||||
version = "0.17.3"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "exceptiongroup" },
|
|
||||||
{ name = "h11" },
|
|
||||||
{ name = "h2" },
|
|
||||||
{ name = "priority" },
|
|
||||||
{ name = "wsproto" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/81/51/3bff01e452f1d12848d1c88be8a59c361d2a7e1b16e1e99c968af62ae8d4/nonecorn-0.17.3.tar.gz", hash = "sha256:8c75c366979f5b11c2bea646133ca1c52f6e20ee03948c192518c496f387ec55", size = 180485 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/cb/a3a50d0e82554845fb93d8a20c119b648f27fa818e4352ca8e6d0f6dda72/nonecorn-0.17.3-py3-none-any.whl", hash = "sha256:8c391be6ae61655805ab2b5c4637953f040db1268ddc75be720f450a7ebe2ad0", size = 72065 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "25.0"
|
version = "25.0"
|
||||||
@ -258,15 +200,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
|
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "priority"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f5/3c/eb7c35f4dcede96fca1842dac5f4f5d15511aa4b52f3a961219e68ae9204/priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0", size = 24792 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5e/5f/82c8074f7e84978129347c2c6ec8b6c59f3584ff1a20bc3c940a3e061790/priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa", size = 8946 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "2.11.3"
|
version = "2.11.3"
|
||||||
@ -454,26 +387,14 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083 },
|
{ url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wsproto"
|
|
||||||
version = "1.2.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "h11" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yukilog"
|
name = "yukilog"
|
||||||
version = "0.1.1"
|
version = "1.0.11"
|
||||||
source = { registry = "https://git.liteyuki.icu/api/packages/LiteyukiStudio/pypi/simple" }
|
source = { registry = "https://git.liteyuki.icu/api/packages/LiteyukiStudio/pypi/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "loguru" },
|
{ name = "loguru" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://git.liteyuki.icu/api/packages/LiteyukiStudio/pypi/files/yukilog/0.1.1/yukilog-0.1.1.tar.gz", hash = "sha256:aeaf3eed9828325196684fbf9fa284c8a2c01353f1ccdf9a2a649268b05b2da3" }
|
sdist = { url = "https://git.liteyuki.icu/api/packages/LiteyukiStudio/pypi/files/yukilog/1.0.11/yukilog-1.0.11.tar.gz", hash = "sha256:84d1fbb2825b2e9b2bffa00617ec4d6b7a4723f7a9b100a9500b1f419d04faad" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://git.liteyuki.icu/api/packages/LiteyukiStudio/pypi/files/yukilog/0.1.1/yukilog-0.1.1-py3-none-any.whl", hash = "sha256:27f778d22e6ca26e88197a413b268af709a64ef56c457dc533018da2e264248e" },
|
{ url = "https://git.liteyuki.icu/api/packages/LiteyukiStudio/pypi/files/yukilog/1.0.11/yukilog-1.0.11-py3-none-any.whl", hash = "sha256:87aba342ee0b9fd51d2768eab7689bdc6e0a633d89055dcecca16168135013d8" },
|
||||||
]
|
]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user