Skip to content

Commit 1b7b8f3

Browse files
authored
fix: 修复支持的适配器字段验证信息错误的问题 (#177)
在没有跳过插件加载测试的情况下,应提示不是集合,而非 JSON 解码失败
1 parent dda05c2 commit 1b7b8f3

File tree

8 files changed

+41
-27
lines changed

8 files changed

+41
-27
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/lang/zh-CN/
1111

1212
- 翻译 Pydantic 验证错误信息
1313

14+
### Fixed
15+
16+
- 修复支持的适配器字段验证信息错误的问题
17+
1418
## [3.0.2] - 2023-08-28
1519

1620
### Fixed

src/plugins/publish/templates/render_error.md.jinja

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ PyPI 项目名 {{ error.input }} 不符合规范。<dt>请确保项目名正确
3535
{{ loc|loc_to_name }}: 格式错误。<dt>请确保其为字典。</dt>
3636
{%- elif type == "type_error.list" %}
3737
{{ loc|loc_to_name }}: 格式错误。<dt>请确保其为列表。</dt>
38+
{%- elif type == "type_error.set" %}
39+
{{ loc|loc_to_name }}: 格式错误。<dt>请确保其为集合。</dt>
3840
{%- elif type == "value_error.json" %}
3941
{{ loc|loc_to_name }}: 解码失败。<dt>请确保其为 JSON 格式。</dt>
4042
{%- elif type == "value_error.any_str.max_length" %}

src/utils/validation/__init__.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from pydantic import validate_model
66

7-
from .constants import CUSTOM_MESSAGES
7+
from .constants import CUSTOM_MESSAGES, VALIDATION_CONTEXT
88
from .models import AdapterPublishInfo, BotPublishInfo, PluginPublishInfo, PublishInfo
99
from .models import PublishType as PublishType
1010
from .models import Tag
@@ -25,6 +25,14 @@ def validate_info(
2525
if publish_type not in validation_model_map:
2626
raise ValueError("⚠️ 未知的发布类型。") # pragma: no cover
2727

28+
# 如果升级至 pydantic 2 后,可以使用 validation-context
29+
# https://docs.pydantic.dev/latest/usage/validators/#validation-context
30+
validation_context = {
31+
"previous_data": raw_data.get("previous_data"),
32+
"skip_plugin_test": raw_data.get("skip_plugin_test"),
33+
}
34+
VALIDATION_CONTEXT.set(validation_context)
35+
2836
data, _, errors = validate_model(validation_model_map[publish_type], raw_data)
2937

3038
# tags 会被转成 list[Tag],需要转成 dict
@@ -38,11 +46,6 @@ def validate_info(
3846
for tag in tags
3947
]
4048

41-
# 这个字段仅用于判断是否重复
42-
# 如果升级至 pydantic 2 后,可以使用 validation-context
43-
# https://docs.pydantic.dev/latest/usage/validators/#validation-context
44-
data.pop("previous_data", None)
45-
4649
errors_with_input = []
4750
if errors:
4851
for error in convert_errors(errors, CUSTOM_MESSAGES):

src/utils/validation/constants.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import contextvars
12
import re
3+
from typing import Any
24

35
# PyPI 格式
46
PYPI_PACKAGE_NAME_PATTERN = re.compile(
@@ -24,10 +26,14 @@
2426
CUSTOM_MESSAGES = {
2527
"type_error.dict": "值不是合法的字典",
2628
"type_error.list": "值不是合法的列表",
29+
"type_error.set": "值不是合法的集合",
2730
"type_error.none.not_allowed": "值不能为 None",
2831
"value_error.json": "JSON 格式不合法",
2932
"value_error.missing": "字段不存在",
3033
"value_error.color": "颜色格式不正确",
3134
"value_error.any_str.max_length": "字符串长度不能超过 {limit_value} 个字符",
3235
"value_error.list.max_items": "列表长度不能超过 {limit_value} 个元素",
3336
}
37+
38+
VALIDATION_CONTEXT = contextvars.ContextVar[dict[str, Any]]("validation_context")
39+
"""验证上下文"""

src/utils/validation/models.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from pydantic import BaseModel, Field, root_validator, validator
77
from pydantic.color import Color
8-
from pydantic.errors import JsonError, ListError
8+
from pydantic.errors import JsonError, SetError
99

1010
if TYPE_CHECKING:
1111
from pydantic.error_wrappers import ErrorDict as PydanticErrorDict
@@ -19,6 +19,7 @@ class ErrorDict(PydanticErrorDict, total=False):
1919
PLUGIN_VALID_TYPE,
2020
PYPI_PACKAGE_NAME_PATTERN,
2121
PYTHON_MODULE_NAME_REGEX,
22+
VALIDATION_CONTEXT,
2223
)
2324
from .errors import (
2425
DuplicationError,
@@ -56,8 +57,6 @@ class PyPIMixin(BaseModel):
5657
module_name: str
5758
project_link: str
5859

59-
previous_data: list[dict[str, Any]]
60-
6160
@validator("module_name", pre=True)
6261
def module_name_validator(cls, v: str) -> str:
6362
if not PYTHON_MODULE_NAME_REGEX.match(v):
@@ -78,7 +77,7 @@ def prevent_duplication(cls, values: dict[str, Any]) -> dict[str, Any]:
7877
module_name = values.get("module_name")
7978
project_link = values.get("project_link")
8079

81-
data = values.get("previous_data")
80+
data = VALIDATION_CONTEXT.get().get("previous_data")
8281
if data is None:
8382
raise ValueError("未获取到数据列表")
8483

@@ -155,8 +154,9 @@ def type_validator(cls, v: str) -> str:
155154
def supported_adapters_validator(
156155
cls, v: str | list[str] | None
157156
) -> list[str] | None:
157+
skip_plugin_test = VALIDATION_CONTEXT.get().get("skip_plugin_test")
158158
# 如果是从 issue 中获取的数据,需要先解码
159-
if isinstance(v, str):
159+
if skip_plugin_test and isinstance(v, str):
160160
try:
161161
v = json.loads(v)
162162
except json.JSONDecodeError:
@@ -166,8 +166,8 @@ def supported_adapters_validator(
166166
if v is None:
167167
return None
168168

169-
if not isinstance(v, list):
170-
raise ListError()
169+
if not isinstance(v, (list, set)):
170+
raise SetError()
171171

172172
supported_adapters = {resolve_adapter_name(x) for x in v}
173173
store_adapters = get_adapters()

tests/publish/render/test_render_error.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,8 @@ async def test_render_type_eroor(app: App, mocker: MockFixture):
349349
},
350350
{
351351
"loc": ("supported_adapters",),
352-
"msg": "value is not a valid iterable",
353-
"type": "type_error.list",
352+
"msg": "value is not a valid set",
353+
"type": "type_error.set",
354354
"input": '"test"',
355355
},
356356
],
@@ -362,7 +362,7 @@ async def test_render_type_eroor(app: App, mocker: MockFixture):
362362
comment = await render_comment(result)
363363
assert (
364364
comment
365-
== """# 📃 商店发布检查结果\n\n> Plugin: 帮助\n\n**⚠️ 在发布检查过程中,我们发现以下问题:**\n\n<pre><code><li>⚠️ 插件类型 invalid 不符合规范。<dt>请确保插件类型正确,当前仅支持 application 与 library。</dt></li><li>⚠️ 适配器 missing 不存在。<dt>请确保适配器模块名称正确。</dt></li><li>⚠️ 插件支持的适配器: 格式错误。<dt>请确保其为列表。</dt></li></code></pre>\n\n<details>\n<summary>详情</summary>\n<pre><code><li>✅ 插件 <a href="https://github.com/owner/repo/actions/runs/123456">加载测试</a> 通过。</li></code></pre>\n</details>\n\n---\n\n💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。\n💡 当插件加载测试失败时,请发布新版本后在当前页面下评论任意内容以触发测试。\n\n\n💪 Powered by [NoneFlow](https://github.com/nonebot/noneflow)\n<!-- NONEFLOW -->\n"""
365+
== """# 📃 商店发布检查结果\n\n> Plugin: 帮助\n\n**⚠️ 在发布检查过程中,我们发现以下问题:**\n\n<pre><code><li>⚠️ 插件类型 invalid 不符合规范。<dt>请确保插件类型正确,当前仅支持 application 与 library。</dt></li><li>⚠️ 适配器 missing 不存在。<dt>请确保适配器模块名称正确。</dt></li><li>⚠️ 插件支持的适配器: 格式错误。<dt>请确保其为集合。</dt></li></code></pre>\n\n<details>\n<summary>详情</summary>\n<pre><code><li>✅ 插件 <a href="https://github.com/owner/repo/actions/runs/123456">加载测试</a> 通过。</li></code></pre>\n</details>\n\n---\n\n💡 如需修改信息,请直接修改 issue,机器人会自动更新检查结果。\n💡 当插件加载测试失败时,请发布新版本后在当前页面下评论任意内容以触发测试。\n\n\n💪 Powered by [NoneFlow](https://github.com/nonebot/noneflow)\n<!-- NONEFLOW -->\n"""
366366
)
367367

368368

tests/utils/validation/test_plugin_supported_adapters.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,19 @@ async def test_plugin_supported_adapters_none(mocked_api: MockRouter) -> None:
1818
assert mocked_api["homepage"].called
1919

2020

21-
async def test_plugin_supported_adapters_list(mocked_api: MockRouter) -> None:
22-
"""不是列表的情况"""
21+
async def test_plugin_supported_adapters_set(mocked_api: MockRouter) -> None:
22+
"""不是集合的情况"""
2323
from src.utils.validation import PublishType, validate_info
2424

25-
data = generate_plugin_data(supported_adapters="test") # type: ignore
25+
data = generate_plugin_data(supported_adapters="test")
2626

2727
result = validate_info(PublishType.PLUGIN, data)
2828

2929
assert not result["valid"]
3030
assert "supported_adapters" not in result["data"]
3131
assert result["errors"]
32-
assert result["errors"][0]["type"] == "type_error.list"
32+
assert result["errors"][0]["type"] == "type_error.set"
33+
assert result["errors"][0]["msg"] == "值不是合法的集合"
3334

3435
assert mocked_api["homepage"].called
3536

@@ -38,14 +39,14 @@ async def test_plugin_supported_adapters_json(mocked_api: MockRouter) -> None:
3839
"""不是 JSON 的情况"""
3940
from src.utils.validation import PublishType, validate_info
4041

41-
data = generate_plugin_data()
42-
data["supported_adapters"] = "test"
42+
data = generate_plugin_data(supported_adapters="test", skip_test=True)
4343

4444
result = validate_info(PublishType.PLUGIN, data)
4545

4646
assert not result["valid"]
4747
assert "supported_adapters" not in result["data"]
4848
assert result["errors"]
4949
assert result["errors"][0]["type"] == "value_error.json"
50+
assert result["errors"][0]["msg"] == "JSON 格式不合法"
5051

5152
assert mocked_api["homepage"].called

tests/utils/validation/utils.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def generate_plugin_data(
5959
desc: str | None = "desc",
6060
homepage: str | None = "https://nonebot.dev",
6161
type: str | None = "application",
62-
supported_adapters: list[str] | None = None,
62+
supported_adapters: Any = None,
6363
skip_test: bool | None = False,
6464
plugin_test_result: bool | None = True,
6565
plugin_test_output: str | None = "plugin_test_output",
@@ -75,10 +75,8 @@ def generate_plugin_data(
7575
"desc": desc,
7676
"homepage": homepage,
7777
"type": type,
78-
"supported_adapters": json.dumps(supported_adapters)
79-
if supported_adapters is not None
80-
else None,
81-
"skip_test": skip_test,
78+
"supported_adapters": supported_adapters,
79+
"skip_plugin_test": skip_test,
8280
"plugin_test_result": plugin_test_result,
8381
"plugin_test_output": plugin_test_output,
8482
"plugin_test_metadata": {},

0 commit comments

Comments
 (0)