AzideCupric 4e304a43b1
通过 nb-cli 实现数据库一键导入导出 (#210)
* feat: 实现导出存储的订阅信息的功能

* test: 编写导出功能测试

* test: 使用tmp_path

* feat: 实现导入订阅文件功能

* refactor: 将订阅导入导出部分独立出来

* fix: 修复一些拼写错误
test: 完成import的第一个测试

* feat: 将订阅导入导出函数加入nb script

test: 添加cli测试

* test: 完善subs import测试

* 🐛 fix nb cli entrypoint name error

* fix: 修改错误的entry_point, 关闭yaml导出时对键名的排序

* fix: 使用更简短的命令名

* 🚚 将subs_io迁移到config下

* ♻️ 不再使用抛出异常的方式创建目录

* refactor: 将subscribe_export类转为函数

* refactor: 将subscribe_import类转为函数

* refactor: 根据重写的subs_io重新调整cli

* test: 调整重写subs_io后的test

* chore: 清理未使用的import内容

* feat(cli): 将--yaml更改为--format

* test: 调整测试

* fix(cli): 为import添加不支持格式的报错

*  improve export performace

* feat: subscribes_import函数不再需要传入参数函数,而是指定为add_subscribes

fix: nbesf_parser在传入str时将调用parse_raw, 否则调用parse_obj

* feat: subscribes_import现在会根据nbesf_data的版本选择合适的导入方式

* fix(test): 调整测试

* feat: nb bison export命令不再将文件导出到data目录,而是当前工作目录

* docs: 增添相关文档

* fix(test): 修复错误的变量名

---------

Co-authored-by: felinae98 <731499577@qq.com>
2023-03-19 16:29:05 +08:00

144 lines
4.0 KiB
Python

import importlib
import json
import time
from functools import partial, wraps
from pathlib import Path
from types import ModuleType
from typing import Any, Callable, Coroutine, TypeVar
from nonebot.log import logger
from ..config.subs_io import nbesf_parser, subscribes_export, subscribes_import
from ..config.subs_io.nbesf_model import SubGroup
from ..scheduler.manager import init_scheduler
try:
import anyio
import click
from typing_extensions import ParamSpec
except ImportError as e: # pragma: no cover
raise ImportError("请使用 `pip install nonebot-bison[cli]` 安装所需依赖") from e
def import_yaml_module() -> ModuleType:
try:
pyyaml = importlib.import_module("yaml")
except ImportError as e:
raise ImportError("请使用 `pip install nonebot-bison[yaml]` 安装所需依赖") from e
return pyyaml
P = ParamSpec("P")
R = TypeVar("R")
def run_sync(func: Callable[P, R]) -> Callable[P, Coroutine[Any, Any, R]]:
@wraps(func)
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
return await anyio.to_thread.run_sync(partial(func, *args, **kwargs))
return wrapper
def run_async(func: Callable[P, Coroutine[Any, Any, R]]) -> Callable[P, R]:
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
return anyio.from_thread.run(partial(func, *args, **kwargs))
return wrapper
@click.group()
def cli():
"""Nonebot Bison CLI"""
pass
def path_init(ctx, param, value):
if not value:
export_path = Path.cwd()
else:
export_path = Path(value)
return export_path
@cli.command(help="导出Nonebot Bison Exchangable Subcribes File", name="export")
@click.option(
"--path", "-p", default=None, callback=path_init, help="导出路径, 如果不指定,则默认为工作目录"
)
@click.option(
"--format",
default="json",
type=click.Choice(["json", "yaml", "yml"]),
help="指定导出格式[json, yaml],默认为 json",
)
@run_async
async def subs_export(path: Path, format: str):
await init_scheduler()
export_file = path / f"bison_subscribes_export_{int(time.time())}.{format}"
logger.info("正在获取订阅信息...")
export_data: SubGroup = await subscribes_export(lambda x: x)
with export_file.open("w", encoding="utf-8") as f:
match format:
case "yaml" | "yml":
logger.info("正在导出为yaml...")
pyyaml = import_yaml_module()
pyyaml.safe_dump(export_data.dict(), f, sort_keys=False)
case "json":
logger.info("正在导出为json...")
json.dump(export_data.dict(), f, indent=4, ensure_ascii=False)
case _:
raise click.BadParameter(message=f"不支持的导出格式: {format}")
logger.success(f"导出完毕!已导出到 {path} ")
@cli.command(help="从Nonebot Biosn Exchangable Subscribes File导入订阅", name="import")
@click.option("--path", "-p", required=True, help="导入文件名")
@click.option(
"--format",
default="json",
type=click.Choice(["json", "yaml", "yml"]),
help="指定导入格式[json, yaml],默认为 json",
)
@run_async
async def subs_import(path: str, format: str):
await init_scheduler()
import_file_path = Path(path)
assert import_file_path.is_file(), "该路径不是文件!"
with import_file_path.open("r", encoding="utf-8") as f:
match format:
case "yaml" | "yml":
logger.info("正在从yaml导入...")
pyyaml = import_yaml_module()
import_items = pyyaml.safe_load(f)
case "json":
logger.info("正在从json导入...")
import_items = json.load(f)
case _:
raise click.BadParameter(message=f"不支持的导入格式: {format}")
nbesf_data = nbesf_parser(import_items)
await subscribes_import(nbesf_data)
def main():
anyio.run(run_sync(cli)) # pragma: no cover