diff --git a/docker/docker_plugin_test.py b/docker/docker_plugin_test.py
index fad12684..3cd6747d 100644
--- a/docker/docker_plugin_test.py
+++ b/docker/docker_plugin_test.py
@@ -251,8 +251,10 @@ async def run(self):
# 创建插件测试项目
await self.create_poetry_project()
if self._create:
- await self.show_package_info()
- await self.show_plugin_dependencies()
+ await asyncio.gather(
+ self.show_package_info(),
+ self.show_plugin_dependencies(),
+ )
await self.run_poetry_project()
metadata = {}
@@ -278,8 +280,14 @@ async def run(self):
return self._run, self._lines_output
async def command(self, cmd: str, timeout: int = 300) -> tuple[bool, str, str]:
- """
- 执行命令
+ """执行命令
+
+ Args:
+ cmd (str): 命令
+ timeout (int, optional): 超时限制. Defaults to 300.
+
+ Returns:
+ tuple[bool, str, str]: 命令执行返回值,标准输出,标准错误
"""
try:
proc = await create_subprocess_shell(
@@ -299,6 +307,7 @@ async def command(self, cmd: str, timeout: int = 300) -> tuple[bool, str, str]:
return not code, stdout.decode(), stderr.decode()
async def create_poetry_project(self):
+ """创建 poetry 项目用来测试插件"""
if not self.path.exists():
self.path.mkdir()
@@ -321,6 +330,7 @@ async def create_poetry_project(self):
self._create = True
async def show_package_info(self) -> None:
+ """获取插件的版本与插件信息"""
if self.path.exists():
code, stdout, stderr = await self.command(
f"poetry show {self.project_link}"
@@ -337,6 +347,7 @@ async def show_package_info(self) -> None:
self._log_output(f"插件 {self.project_link} 信息获取失败。")
async def run_poetry_project(self) -> None:
+ """运行插件"""
if self.path.exists():
# 默认使用 fake 驱动
with open(self.path / ".env", "w", encoding="utf8") as f:
@@ -377,6 +388,7 @@ async def run_poetry_project(self) -> None:
self._log_output(f" {i}")
async def show_plugin_dependencies(self) -> None:
+ """获取插件的依赖"""
if self.path.exists():
code, stdout, stderr = await self.command("poetry export --without-hashes")
@@ -414,9 +426,15 @@ def _get_plugin_module_name(self, require: str) -> str | None:
def main():
- plugin = PluginTest(
- os.environ.get("PLUGIN_INFO", ""), os.environ.get("PLUGIN_CONFIG", None)
- )
+ """
+ 根据传入的环境变量 PLUGIN_INFO 和 PLUGIN_CONFIG 进行测试
+
+ PLUGIN_INFO 即为该插件的键
+ """
+
+ plugin_info = os.environ.get("PLUGIN_INFO", "")
+ plugin_config = os.environ.get("PLUGIN_CONFIG", None)
+ plugin = PluginTest(plugin_info, plugin_config)
asyncio.run(plugin.run())
diff --git a/src/plugins/publish/__init__.py b/src/plugins/publish/__init__.py
index 3c549bc8..f7fd4154 100644
--- a/src/plugins/publish/__init__.py
+++ b/src/plugins/publish/__init__.py
@@ -27,8 +27,6 @@
from .utils import (
process_pr_and_issue_title,
comment_issue,
- commit_and_push,
- create_pull_request,
ensure_issue_content,
ensure_issue_test_button,
resolve_conflict_pull_requests,
@@ -36,7 +34,6 @@
should_skip_plugin_publish,
should_skip_plugin_test,
trigger_registry_update,
- update_file,
)
from .validation import (
validate_adapter_info_from_issue,
@@ -206,6 +203,7 @@ async def handle_publish_plugin_check(
# 分支命名示例 publish/issue123
branch_name = f"{BRANCH_NAME_PREFIX}{issue_number}"
+ # 验证之后创建拉取请求和修改议题的标题
await process_pr_and_issue_title(
bot, repo_info, result, branch_name, issue_number, title, issue
)
@@ -251,6 +249,7 @@ async def handle_adapter_publish_check(
# 分支命名示例 publish/issue123
branch_name = f"{BRANCH_NAME_PREFIX}{issue_number}"
+ # 验证之后创建拉取请求和修改议题的标题
await process_pr_and_issue_title(
bot, repo_info, result, branch_name, issue_number, title, issue
)
@@ -292,6 +291,7 @@ async def handle_bot_publish_check(
# 分支命名示例 publish/issue123
branch_name = f"{BRANCH_NAME_PREFIX}{issue_number}"
+ # 验证之后创建拉取请求和修改议题的标题
await process_pr_and_issue_title(
bot, repo_info, result, branch_name, issue_number, title, issue
)
diff --git a/src/plugins/publish/render.py b/src/plugins/publish/render.py
index 8857fbc7..0ca22bb7 100644
--- a/src/plugins/publish/render.py
+++ b/src/plugins/publish/render.py
@@ -1,5 +1,4 @@
from pathlib import Path
-from typing import TYPE_CHECKING
import jinja2
@@ -7,9 +6,7 @@
from .config import plugin_config
from .constants import LOC_NAME_MAP
-
-if TYPE_CHECKING:
- from src.utils.validation import ValidationDict
+from src.utils.validation import ValidationDict
def tags_to_str(tags: list[dict[str, str]]) -> str:
@@ -50,7 +47,7 @@ def loc_to_name(loc: list[str | int]) -> str:
env.filters["loc_to_name"] = loc_to_name
-async def render_comment(result: "ValidationDict", reuse: bool = False) -> str:
+async def render_comment(result: ValidationDict, reuse: bool = False) -> str:
"""将验证结果转换为评论内容"""
title = f"{result.type}: {result.name}"
diff --git a/src/plugins/publish/templates/render_data.md.jinja b/src/plugins/publish/templates/render_data.md.jinja
index 3682f4ba..b035e12d 100644
--- a/src/plugins/publish/templates/render_data.md.jinja
+++ b/src/plugins/publish/templates/render_data.md.jinja
@@ -1,23 +1,23 @@
{% macro render_data(key, value, skip_plugin_test) %}
{% if key == "homepage" %}
-项目 主页 返回状态码 200。
+ 项目 主页 返回状态码 200。
{%- elif key == "tags" and value %}
-标签: {{ value|tags_to_str }}。
+ 标签: {{ value|tags_to_str }}。
{%- elif key == "project_link" %}
-项目 {{ value }} 已发布至 PyPI。
+ 项目 {{ value }} 已发布至 PyPI。
{%- elif key == "type" %}
-插件类型: {{ value }}。
+ 插件类型: {{ value }}。
{%- elif key == "load" %}
-插件加载测试: {{ value }}。
+ 插件加载测试: {{ value }}。
{%- elif key == "supported_adapters" %}
-插件支持的适配器: {{ value|supported_adapters_to_str }}。
+ 插件支持的适配器: {{ value|supported_adapters_to_str }}。
{%- elif key == "action_url" %}
-{% if skip_plugin_test %}
-插件 加载测试 已跳过。
-{%- else %}
-插件 加载测试 通过。
-{%- endif %}
+ {% if skip_plugin_test %}
+ 插件 加载测试 已跳过。
+ {%- else %}
+ 插件 加载测试 通过。
+ {%- endif %}
{% else %}
-{{ key }}: {{ value }}。
+ {{ key }}: {{ value }}。
{%- endif %}
{% endmacro %}
diff --git a/src/plugins/publish/templates/render_error.md.jinja b/src/plugins/publish/templates/render_error.md.jinja
index f6f96cd6..581572d4 100644
--- a/src/plugins/publish/templates/render_error.md.jinja
+++ b/src/plugins/publish/templates/render_error.md.jinja
@@ -1,47 +1,64 @@
{% macro render_error(error) %}
+
{% set loc = error.loc %}
{% set type = error.type %}
-{%- if loc|length == 3 and loc[0] == "tags" and type == "missing" %}
-第 {{ loc[1] + 1 }} 个标签缺少 {{ loc[2] }} 字段。
请确保标签字段完整。
-{% elif loc|length == 3 and loc[0] == "tags" and type == "string_too_long" %}
-第 {{ loc[1] + 1 }} 个标签名称过长请确保标签名称不超过 10 个字符。
-{%- elif loc|length == 3 and loc[0] == "tags" and type == "color_error" %}
-第 {{ loc[1] + 1 }} 个标签颜色错误请确保标签颜色符合十六进制颜色码规则。
-{%- elif loc|length == 2 and loc[0] == "tags" and type == "model_type" %}
-第 {{ loc[1] + 1 }} 个标签格式错误。请确保标签为字典。
-{%- elif type == "homepage" and error.ctx.status_code != -1 %}
-项目 主页 返回状态码 {{ error.ctx.status_code }}。请确保你的项目主页可访问。
-{%- elif type == "homepage" and error.ctx.status_code == -1 %}
-项目 主页 访问出错。错误信息
{{ error.ctx.msg }}
-{%- elif type == "project_link.not_found" %}
-项目 {{ error.input }} 未发布至 PyPI。请将你的项目发布至 PyPI。
-{%- elif type == "project_link.name" %}
-PyPI 项目名 {{ error.input }} 不符合规范。请确保项目名正确。
-{%- elif type == "module_name" %}
-包名 {{ error.input }} 不符合规范。请确保包名正确。
-{%- elif type == "duplication" %}
-{{ error.msg }}请确保没有重复发布。
-{%- elif type == "plugin_test" %}
-插件加载测试未通过。测试输出
{{ error.ctx.output }}
-{%- elif type == "metadata" %}
-无法获取到插件元数据。{{ "请填写插件元数据" if error.ctx.plugin_test_result else "请确保插件正常加载" }}。
-{%- elif type == "plugin.type" %}
-插件类型 {{ error.input }} 不符合规范。请确保插件类型正确,当前仅支持 application 与 library。
-{%- elif type == "supported_adapters.missing" %}
-适配器 {{ ', '.join(error.ctx.missing_adapters) }} 不存在。请确保适配器模块名称正确。
-{%- elif type == "missing" %}
-{{ loc|loc_to_name }}: 无法匹配到数据。请确保填写该项目。
-{%- elif type == "model_type" %}
-{{ loc|loc_to_name }}: 格式错误。请确保其为字典。
-{%- elif type == "list_type" %}
-{{ loc|loc_to_name }}: 格式错误。请确保其为列表。
-{%- elif type == "set_type" %}
-{{ loc|loc_to_name }}: 格式错误。请确保其为集合。
-{%- elif type == "json_type" %}
-{{ loc|loc_to_name }}: 解码失败。请确保其为 JSON 格式。
-{%- elif type == "string_too_long" %}
-{{ loc|loc_to_name }}: 字符过多。请确保其不超过 {{ error.ctx.max_length }} 个字符。
-{%- else %}
-{{ error.loc|loc_to_name }}: {{ error.msg }}
-{%- endif %}
+{% set ctx = error.ctx %}
+{% set name = loc|loc_to_name %}
+{% set input = error.input %}
+
+{% macro render_tag_error(index, message, dt_message) %}
+第 {{ index + 1 }} 个标签{{ message }}。请确保标签{{ dt_message }}。
{% endmacro %}
+
+{% macro detail_message(summary, detail) %}
+{{ summary }}
{{ detail }}
+{% endmacro %}
+
+{% if loc|length == 3 and loc[0] == "tags" %}
+ {% if type == "missing" %}
+ {{ render_tag_error(loc[1], "缺少第" ~ loc[2] ~ " 字段", "请确保标签字段完整。") }}
+ {% elif type == "string_too_long" %}
+ {{ render_tag_error(loc[1], "名称过长", "请确保标签名称不超过 10 个字符。") }}
+ {% elif type == "color_error" %}
+ {{ render_tag_error(loc[1], "颜色错误", "请确保标签颜色符合十六进制颜色码规则。") }}
+ {% endif %}
+{% elif loc|length == 2 and loc[0] == "tags" and type == "model_type" %}
+ {{ render_tag_error(loc[1], "格式错误", "请确保标签为字典。") }}
+{% elif type == "homepage" %}
+ {% if ctx.status_code != -1 %}
+ 项目 主页 返回状态码 {{ ctx.status_code }}。请确保你的项目主页可访问。
+ {% else %}
+ 项目 主页 访问出错。{{ detail_message("错误信息", ctx.error) }}
+ {% endif %}
+{% elif type == "project_link.not_found" %}
+ 项目 {{ input }} 未发布至 PyPI。请将你的项目发布至 PyPI。
+{% elif type == "project_link.name" %}
+ PyPI 项目名 {{ input }} 不符合规范。请确保项目名正确。
+{% elif type == "module_name" %}
+ 包名 {{ input }} 不符合规范。请确保包名正确。
+{% elif type == "duplication" %}
+ {{ error.msg }}请确保没有重复发布。
+{% elif type == "plugin.test" %}
+ 插件加载测试未通过。{{ detail_message("测试输出", ctx.output) }}
+{% elif type == "metadata" %}
+ 无法获取到插件元数据。{{ "请填写插件元数据" if ctx.plugin_test_result else "请确保插件正常加载" }}。
+{% elif type == "plugin.type" %}
+ 插件类型 {{ input }} 不符合规范。请确保插件类型正确,当前仅支持 application 与 library。
+{% elif type == "supported_adapters.missing" %}
+ 适配器 {{ ', '.join(ctx.missing_adapters) }} 不存在。请确保适配器模块名称正确。
+{% elif type == "missing" %}
+ {{ name }}: 无法匹配到数据。请确保填写该项目。
+{% elif type == "model_type" %}
+ {{ name }}: 格式错误。请确保其为字典。
+{% elif type == "list_type" %}
+ {{ name }}: 格式错误。请确保其为列表。
+{% elif type == "set_type" %}
+ {{ name }}: 格式错误。请确保其为集合。
+{% elif type == "json_type" %}
+ {{ name }}: 解码失败。请确保其为 JSON 格式。
+{% elif type == "string_too_long" %}
+ {{ name }}: 字符过多。请确保其不超过 {{ ctx.max_length }} 个字符。
+{% else %}
+ {{ error.name }}: {{ error.msg }}
+{% endif %}
+{% endmacro %}
\ No newline at end of file
diff --git a/src/plugins/publish/utils.py b/src/plugins/publish/utils.py
index 7c580072..f7bfaa55 100644
--- a/src/plugins/publish/utils.py
+++ b/src/plugins/publish/utils.py
@@ -510,7 +510,7 @@ async def trigger_registry_update(
client_payload = {
"type": publish_type.value,
- "key": f"{result.data['project_link']}:{result.data['module_name']}",
+ "key": f"{result.data["project_link"]}:{result.data["module_name"]}",
"config": config.group(1) if config else "",
"data": json.dumps(result.data),
}
diff --git a/src/plugins/publish/validation.py b/src/plugins/publish/validation.py
index 32ab1aee..381f30f7 100644
--- a/src/plugins/publish/validation.py
+++ b/src/plugins/publish/validation.py
@@ -46,11 +46,12 @@ def strip_ansi(text: str | None) -> str:
async def validate_plugin_info_from_issue(issue: "Issue") -> ValidationDict:
- """从议题中提取插件信息"""
+ """从议题中获取插件信息,并且运行插件测试加载且获取插件元信息后进行验证"""
body = issue.body if issue.body else ""
author = issue.user.id if issue.user else ""
author_id = issue.user.id if issue.user else None
+ # 从议题里提取插件所需信息
raw_data: dict[str, Any] = extract_publish_info_from_issue(
{
"module_name": PLUGIN_MODULE_NAME_PATTERN,
@@ -67,6 +68,10 @@ async def validate_plugin_info_from_issue(issue: "Issue") -> ValidationDict:
with plugin_config.input_config.plugin_path.open("r", encoding="utf-8") as f:
previous_data: list[dict[str, str]] = json.load(f)
+ raw_data["author"] = author
+ raw_data["author_id"] = author_id
+
+ # 运行插件测试,获取插件测试输出与元信息
plugin_test_result: DockerTestResult = await DockerPluginTest(
DOCKER_IMAGES, project_link, module_name, test_config
).run("3.10")
@@ -82,11 +87,10 @@ async def validate_plugin_info_from_issue(issue: "Issue") -> ValidationDict:
"output": plugin_test_output,
"metadata": plugin_test_metadata,
"previous_data": previous_data,
- "author": author,
- "author_id": author_id,
}
)
- # 如果插件测试被跳过,则从议题中获取信息
+
+ # 如果插件被跳过,则从议题获取插件信息
if plugin_config.skip_plugin_test:
plugin_info = extract_publish_info_from_issue(
{
@@ -100,23 +104,23 @@ async def validate_plugin_info_from_issue(issue: "Issue") -> ValidationDict:
)
raw_data.update(plugin_info)
elif plugin_test_metadata:
- raw_data.update(plugin_test_metadata)
- raw_data["desc"] = raw_data.get("description")
+ # 发布信息更新插件元数据
+ raw_data.update(plugin_test_metadata.model_dump())
else:
# 插件缺少元数据
# 可能为插件测试未通过,或者插件未按规范编写
raw_data["name"] = project_link
- # 如果升级至 pydantic 2 后,可以使用 validation-context
+ # 传入的验证插件信息的上下文
validation_context = {
"previous_data": raw_data.get("previous_data"),
"skip_plugin_test": raw_data.get("skip_plugin_test"),
"plugin_test_output": raw_data.get("plugin_test_output"),
}
+ # 验证插件相关信息
validate_data = validate_info(PublishType.PLUGIN, raw_data, validation_context)
- # 如果是插件,还需要额外验证插件加载测试结果
if (
validate_data.data.get("metadata") is None
and not plugin_config.skip_plugin_test
diff --git a/src/utils/store_test/store.py b/src/utils/store_test/store.py
index 9e016011..a7a5138a 100644
--- a/src/utils/store_test/store.py
+++ b/src/utils/store_test/store.py
@@ -194,6 +194,7 @@ async def merge_data(
dump_json(DRIVERS_PATH, self._store_drivers)
dump_json(PLUGINS_PATH, list(plugins.values()))
dump_json(RESULTS_PATH, results)
+ dump_json(PLUGIN_CONFIG_PATH, self._plugin_configs)
async def run(self, limit: int, force: bool = False):
"""运行商店测试
diff --git a/src/utils/validation/constants.py b/src/utils/validation/constants.py
index bf1de29f..c3fefeee 100644
--- a/src/utils/validation/constants.py
+++ b/src/utils/validation/constants.py
@@ -1,5 +1,5 @@
import re
-from typing_extensions import LiteralString
+from typing import LiteralString
# PyPI 格式
PYPI_PACKAGE_NAME_PATTERN = re.compile(
diff --git a/src/utils/validation/models.py b/src/utils/validation/models.py
index 45099cdc..3d10f142 100644
--- a/src/utils/validation/models.py
+++ b/src/utils/validation/models.py
@@ -241,7 +241,8 @@ def plugin_test_metadata_validator(
context = info.context
if context is None:
raise PydanticCustomError("validation_context", "未获取到验证上下文")
-
+ if v is None:
+ raise PydanticCustomError("plugin.metadata", "插件缺少元数据")
if context.get("skip_plugin_test"):
return None
return v