mirror of
https://github.com/suyiiyii/nonebot-bison.git
synced 2026-05-09 18:27:56 +08:00
✨ 通过 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>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"version": 1,
|
||||
"groups": [
|
||||
{
|
||||
"user": {
|
||||
"type": "group",
|
||||
"uid": 123
|
||||
},
|
||||
"subs": [
|
||||
{
|
||||
"categories": [],
|
||||
"tags": [],
|
||||
"target": {
|
||||
"target_name": "weibo_name",
|
||||
"target": "weibo_id",
|
||||
"platform_name": "weibo",
|
||||
"default_schedule_weight": 10
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"user": {
|
||||
"type": "group",
|
||||
"uid": 234
|
||||
},
|
||||
"subs": [
|
||||
{
|
||||
"categories": [],
|
||||
"tags": [
|
||||
"kaltsit",
|
||||
"amiya"
|
||||
],
|
||||
"target": {
|
||||
"target_name": "weibo_name",
|
||||
"target": "weibo_id",
|
||||
"platform_name": "weibo",
|
||||
"default_schedule_weight": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"categories": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"tags": [],
|
||||
"target": {
|
||||
"target_name": "bilibili_name",
|
||||
"target": "bilibili_id",
|
||||
"platform_name": "bilibili",
|
||||
"default_schedule_weight": 10
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
version: 1
|
||||
groups:
|
||||
- subs:
|
||||
- categories: []
|
||||
tags: []
|
||||
target:
|
||||
default_schedule_weight: 10
|
||||
platform_name: weibo
|
||||
target: weibo_id
|
||||
target_name: weibo_name
|
||||
user:
|
||||
type: group
|
||||
uid: 12355
|
||||
- subs:
|
||||
- categories: []
|
||||
tags:
|
||||
- kaltsit
|
||||
- amiya
|
||||
target:
|
||||
default_schedule_weight: 10
|
||||
platform_name: weibo
|
||||
target: weibo_id
|
||||
target_name: weibo_name
|
||||
- categories:
|
||||
- 1
|
||||
- 2
|
||||
tags: []
|
||||
target:
|
||||
default_schedule_weight: 10
|
||||
platform_name: bilibili
|
||||
target: bilibili_id
|
||||
target_name: bilibili_name
|
||||
user:
|
||||
type: group
|
||||
uid: 23466
|
||||
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"version": 1,
|
||||
"groups": [
|
||||
{
|
||||
"user": {
|
||||
"type": "group",
|
||||
"uid": 123
|
||||
},
|
||||
"subs": [
|
||||
{
|
||||
"categories": [],
|
||||
"tags": [],
|
||||
"target": {
|
||||
"target_name": "weibo_name",
|
||||
"target": "weibo_id",
|
||||
"platform_name": "weibo",
|
||||
"default_schedule_weight": 10
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"user": {
|
||||
"type": "group",
|
||||
"uid": 234
|
||||
},
|
||||
"subs": [
|
||||
{
|
||||
"tags": [
|
||||
"kaltsit",
|
||||
"amiya"
|
||||
],
|
||||
"target": {
|
||||
"target_name": "weibo_name",
|
||||
"target": "weibo_id",
|
||||
"platform_name": "weibo",
|
||||
"default_schedule_weight": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"categories": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"tags": [],
|
||||
"target": [
|
||||
{
|
||||
"target_name": "bilibili_name",
|
||||
"target": "bilibili_id",
|
||||
"platform_name": "bilibili",
|
||||
"default_schedule_weight": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"user": {
|
||||
"type": "group",
|
||||
"uid": 123
|
||||
},
|
||||
"subs": {
|
||||
"categories": [],
|
||||
"tags": [],
|
||||
"target": {
|
||||
"target_name": "weibo_name2",
|
||||
"target": "weibo_id2",
|
||||
"platform_name": "weibo",
|
||||
"default_schedule_weight": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"user": {
|
||||
"type": "group",
|
||||
"uid": 123
|
||||
},
|
||||
"subs": [
|
||||
{
|
||||
"categories": [],
|
||||
"tags": [],
|
||||
"target": {
|
||||
"target_name": "weibo_name2",
|
||||
"platform_name": "weibo",
|
||||
"default_schedule_weight": 10
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"version": 1,
|
||||
"groups": [
|
||||
{
|
||||
"user": {
|
||||
"type": "group",
|
||||
"uid": 123
|
||||
},
|
||||
"subs": [
|
||||
{
|
||||
"categories": [],
|
||||
"tags": [],
|
||||
"target": {
|
||||
"target_name": "weibo_name",
|
||||
"target": "weibo_id",
|
||||
"platform_name": "weibo",
|
||||
"default_schedule_weight": 10
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"user": {
|
||||
"type": "group",
|
||||
"uid": 234
|
||||
},
|
||||
"subs": [
|
||||
{
|
||||
"categories": [],
|
||||
"tags": [
|
||||
"kaltsit",
|
||||
"amiya"
|
||||
],
|
||||
"target": {
|
||||
"target_name": "weibo_name",
|
||||
"target": "weibo_id",
|
||||
"platform_name": "weibo",
|
||||
"default_schedule_weight": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"categories": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"tags": [],
|
||||
"target": {
|
||||
"target_name": "bilibili_name",
|
||||
"target": "bilibili_id",
|
||||
"platform_name": "bilibili",
|
||||
"default_schedule_weight": 10
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"user": {
|
||||
"type": "group",
|
||||
"uid": 123
|
||||
},
|
||||
"subs": [
|
||||
{
|
||||
"categories": [],
|
||||
"tags": [],
|
||||
"target": {
|
||||
"target_name": "weibo_name",
|
||||
"target": "weibo_id",
|
||||
"platform_name": "weibo",
|
||||
"default_schedule_weight": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"categories": [
|
||||
2,
|
||||
6
|
||||
],
|
||||
"tags": [
|
||||
"poca"
|
||||
],
|
||||
"target": {
|
||||
"target_name": "weibo_name2",
|
||||
"target": "weibo_id2",
|
||||
"platform_name": "weibo",
|
||||
"default_schedule_weight": 10
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from click.testing import CliRunner
|
||||
from nonebug.app import App
|
||||
|
||||
from .utils import get_file
|
||||
|
||||
|
||||
def test_cli_help(app: App):
|
||||
from nonebot_bison.script.cli import cli
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
result = runner.invoke(cli, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
|
||||
for arg in ["subs-export", "subs-import"]:
|
||||
assert arg not in result.output
|
||||
|
||||
for arg in ["export", "import"]:
|
||||
assert arg in result.output
|
||||
|
||||
result = runner.invoke(cli, ["export", "--help"])
|
||||
assert result.exit_code == 0
|
||||
|
||||
for opt in ["--path", "-p", "导出路径", "--format", "指定导出格式[json, yaml],默认为 json"]:
|
||||
assert opt in result.output
|
||||
|
||||
result = runner.invoke(cli, ["import", "--help"])
|
||||
assert result.exit_code == 0
|
||||
|
||||
for opt in ["--path", "-p", "导入文件名", "--format", "指定导入格式[json, yaml],默认为 json"]:
|
||||
assert opt in result.output
|
||||
|
||||
|
||||
async def test_subs_export(app: App, tmp_path: Path):
|
||||
from nonebot_bison.config.db_config import config
|
||||
from nonebot_bison.script.cli import cli, run_sync
|
||||
from nonebot_bison.types import Target as TTarget
|
||||
|
||||
await config.add_subscribe(
|
||||
user=123,
|
||||
user_type="group",
|
||||
target=TTarget("weibo_id"),
|
||||
target_name="weibo_name",
|
||||
platform_name="weibo",
|
||||
cats=[],
|
||||
tags=[],
|
||||
)
|
||||
await config.add_subscribe(
|
||||
user=234,
|
||||
user_type="group",
|
||||
target=TTarget("weibo_id"),
|
||||
target_name="weibo_name",
|
||||
platform_name="weibo",
|
||||
cats=[],
|
||||
tags=["kaltsit", "amiya"],
|
||||
)
|
||||
await config.add_subscribe(
|
||||
user=234,
|
||||
user_type="group",
|
||||
target=TTarget("bilibili_id"),
|
||||
target_name="bilibili_name",
|
||||
platform_name="bilibili",
|
||||
cats=[1, 2],
|
||||
tags=[],
|
||||
)
|
||||
|
||||
assert len(await config.list_subs_with_all_info()) == 3
|
||||
|
||||
with patch("time.time") as mock_time:
|
||||
mock_now = mock_time.now.return_value
|
||||
mock_now.time.return_value = 1
|
||||
|
||||
runner = CliRunner()
|
||||
# 是否默认导出到工作目录
|
||||
with runner.isolated_filesystem(temp_dir=tmp_path) as td:
|
||||
result = await run_sync(runner.invoke)(cli, ["export"])
|
||||
assert result.exit_code == 0
|
||||
file_path = Path.cwd() / "bison_subscribes_export_1.json"
|
||||
assert file_path.exists()
|
||||
assert file_path.stat().st_size
|
||||
|
||||
# 是否导出到指定已存在文件夹
|
||||
data_dir = tmp_path / "data"
|
||||
data_dir.mkdir()
|
||||
result = await run_sync(runner.invoke)(cli, ["export", "-p", str(data_dir)])
|
||||
assert result.exit_code == 0
|
||||
file_path2 = data_dir / "bison_subscribes_export_1.json"
|
||||
assert file_path2.exists()
|
||||
assert file_path2.stat().st_size
|
||||
|
||||
# 是否拒绝导出到不存在的文件夹
|
||||
result = await run_sync(runner.invoke)(
|
||||
cli, ["export", "-p", str(tmp_path / "data2")]
|
||||
)
|
||||
assert result.exit_code == 1
|
||||
|
||||
# 是否可以以yaml格式导出
|
||||
result = await run_sync(runner.invoke)(
|
||||
cli, ["export", "-p", str(tmp_path), "--format", "yaml"]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
file_path3 = tmp_path / "bison_subscribes_export_1.yaml"
|
||||
assert file_path3.exists()
|
||||
assert file_path3.stat().st_size
|
||||
|
||||
# 是否允许以未支持的格式导出
|
||||
result = await run_sync(runner.invoke)(
|
||||
cli, ["export", "-p", str(tmp_path), "--format", "toml"]
|
||||
)
|
||||
assert result.exit_code == 2
|
||||
|
||||
|
||||
async def test_subs_import(app: App, tmp_path):
|
||||
from nonebot_bison.config.db_config import config
|
||||
from nonebot_bison.script.cli import cli, run_sync
|
||||
|
||||
assert len(await config.list_subs_with_all_info()) == 0
|
||||
|
||||
mock_file: Path = tmp_path / "1.json"
|
||||
mock_file.write_text(get_file("subs_export.json"))
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
result = await run_sync(runner.invoke)(cli, ["import"])
|
||||
assert result.exit_code == 2
|
||||
|
||||
result = await run_sync(runner.invoke)(cli, ["import", "-p"])
|
||||
assert result.exit_code == 2
|
||||
|
||||
result = await run_sync(runner.invoke)(cli, ["import", "-p", str(mock_file)])
|
||||
assert result.exit_code == 0
|
||||
assert len(await config.list_subs_with_all_info()) == 3
|
||||
|
||||
mock_file: Path = tmp_path / "2.yaml"
|
||||
mock_file.write_text(get_file("subs_export.yaml"))
|
||||
|
||||
result = await run_sync(runner.invoke)(
|
||||
cli, ["import", "-p", str(mock_file), "--format=yml"]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert len(await config.list_subs_with_all_info()) == 6
|
||||
@@ -0,0 +1,91 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from nonebug.app import App
|
||||
|
||||
from .utils import get_file, get_json
|
||||
|
||||
|
||||
async def test_subs_export(app: App, init_scheduler):
|
||||
import time
|
||||
|
||||
from nonebot_bison.config.db_config import config
|
||||
from nonebot_bison.config.db_model import User
|
||||
from nonebot_bison.config.subs_io import subscribes_export
|
||||
from nonebot_bison.types import Target as TTarget
|
||||
|
||||
await config.add_subscribe(
|
||||
user=123,
|
||||
user_type="group",
|
||||
target=TTarget("weibo_id"),
|
||||
target_name="weibo_name",
|
||||
platform_name="weibo",
|
||||
cats=[],
|
||||
tags=[],
|
||||
)
|
||||
await config.add_subscribe(
|
||||
user=234,
|
||||
user_type="group",
|
||||
target=TTarget("weibo_id"),
|
||||
target_name="weibo_name",
|
||||
platform_name="weibo",
|
||||
cats=[],
|
||||
tags=["kaltsit", "amiya"],
|
||||
)
|
||||
await config.add_subscribe(
|
||||
user=234,
|
||||
user_type="group",
|
||||
target=TTarget("bilibili_id"),
|
||||
target_name="bilibili_name",
|
||||
platform_name="bilibili",
|
||||
cats=[1, 2],
|
||||
tags=[],
|
||||
)
|
||||
|
||||
data = await config.list_subs_with_all_info()
|
||||
assert len(data) == 3
|
||||
|
||||
nbesf_data = await subscribes_export(lambda x: x)
|
||||
assert nbesf_data.dict() == get_json("subs_export.json")
|
||||
|
||||
nbesf_data_user_234 = await subscribes_export(
|
||||
lambda stmt: stmt.where(User.uid == 234, User.type == "group")
|
||||
)
|
||||
assert len(nbesf_data_user_234.groups) == 1
|
||||
assert len(nbesf_data_user_234.groups[0].subs) == 2
|
||||
assert nbesf_data_user_234.groups[0].user.dict() == {"uid": 234, "type": "group"}
|
||||
|
||||
|
||||
async def test_subs_import(app: App, init_scheduler):
|
||||
from nonebot_bison.config.db_config import config
|
||||
from nonebot_bison.config.subs_io import nbesf_parser, subscribes_import
|
||||
|
||||
nbesf_data = nbesf_parser(get_json("subs_export.json"))
|
||||
|
||||
await subscribes_import(nbesf_data)
|
||||
|
||||
data = await config.list_subs_with_all_info()
|
||||
assert len(data) == 3
|
||||
|
||||
|
||||
async def test_subs_import_dup_err(app: App, init_scheduler):
|
||||
|
||||
from nonebot_bison.config.db_config import config
|
||||
from nonebot_bison.config.subs_io import nbesf_parser, subscribes_import
|
||||
|
||||
nbesf_data = nbesf_parser(get_json("subs_export_has_subdup_err.json"))
|
||||
|
||||
await subscribes_import(nbesf_data)
|
||||
|
||||
data = await config.list_subs_with_all_info()
|
||||
|
||||
assert len(data) == 4
|
||||
|
||||
|
||||
async def test_subs_import_all_fail(app: App, init_scheduler):
|
||||
"""只要文件格式有任何一个错误, 都不会进行订阅"""
|
||||
from nonebot_bison.config.subs_io import nbesf_parser
|
||||
from nonebot_bison.config.subs_io.nbesf_model import NBESFParseErr
|
||||
|
||||
with pytest.raises(NBESFParseErr):
|
||||
nbesf_data = nbesf_parser(get_json("subs_export_all_illegal.json"))
|
||||
@@ -0,0 +1,18 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(__file__).parent / "static"
|
||||
|
||||
|
||||
def get_json(file_name: str):
|
||||
"""返回该目录下static目录下的<file_name>解析得到的json"""
|
||||
with open(path / file_name, "r", encoding="utf8") as f:
|
||||
json_ = json.load(f)
|
||||
return json_
|
||||
|
||||
|
||||
def get_file(file_name: str):
|
||||
"""返回该目录下static目录下的<file_name>的文件内容"""
|
||||
with open(path / file_name, "r", encoding="utf8") as f:
|
||||
file_text = f.read()
|
||||
return file_text
|
||||
Reference in New Issue
Block a user