✨ feat(http_utils): 添加可选的 accept_status_codes 参数以支持自定义状态码处理 (#2096)

- 在 AsyncHttpx 类中,新增 accept_status_codes 参数,允许用户指定哪些 HTTP 状态码不视为错误。
- 更新了单次请求的逻辑,以便在响应状态码符合 accept_status_codes 时不调用 raise_for_status 方法。

✨ feat(message): 增加随机失败提示消息功能

- 在 MessageUtils 类中,添加可爱风格的失败提示消息列表和获取随机消息的方法。

✨ feat(github_utils): 引入动态查询外部插件分组的功能

- 在 AliyunFileInfo 类中,新增 list_group_repositories 方法以列出分组下的所有仓库,并实现通过仓库名称获取仓库ID的逻辑。

✨ feat(virtual_env_package_manager): 增加清理 requirements 文件的功能

- 新增 _clean_requirements_file 方法,清理 requirements 文件中的非ASCII注释,避免 Windows 上 pip 使用 GBK 编码读取 UTF-8 文件时出错。

✨ feat(repo_utils): 优化阿里云仓库 URL 处理逻辑

- 更新 prepare_aliyun_url 方法以支持指定分组名称,并新增 get_aliyun_group_for_repo 方法以获取仓库所属的阿里云分组名。
This commit is contained in:
HibiKier
2026-02-06 16:45:48 +08:00
committed by GitHub
parent b95acce800
commit 5c067bcf04
7 changed files with 368 additions and 22 deletions
+183 -13
View File
@@ -1,7 +1,7 @@
import base64
import contextlib
import sys
from typing import Protocol
from typing import ClassVar, Protocol
from aiocache import cached
from alibabacloud_devops20210625 import models as devops_20210625_models
@@ -20,6 +20,7 @@ else:
from .const import (
ALIYUN_ENDPOINT,
ALIYUN_EXTERNAL_PLUGIN_GROUPS,
ALIYUN_ORG_ID,
ALIYUN_REGION,
ALIYUN_REPO_MAPPING,
@@ -317,6 +318,9 @@ class AliyunFileInfo:
repository_id: str
"""仓库ID"""
# 动态仓库ID缓存: {repo_name: repository_id}
_dynamic_repo_cache: ClassVar[dict[str, str]] = {}
@classmethod
async def get_client(cls) -> devops20210625Client:
"""获取阿里云客户端"""
@@ -331,6 +335,179 @@ class AliyunFileInfo:
return devops20210625Client(config)
@classmethod
@cached(ttl=CACHED_API_TTL)
async def list_group_repositories(cls, group_path: str) -> list[dict]:
"""列出分组下的所有仓库
参数:
group_path: 分组路径,如 "zhenxun_plugins"
返回:
list[dict]: 仓库信息列表,每个元素包含 id, name, path 等
"""
try:
client = await cls.get_client()
result = []
page = 1
per_page = 100
while True:
request = devops_20210625_models.ListRepositoriesRequest(
organization_id=ALIYUN_ORG_ID,
access_token=base64.b64decode(
RDC_access_token_encrypted.encode()
).decode(),
page=page,
per_page=per_page,
)
runtime = util_models.RuntimeOptions()
headers = {}
response = await client.list_repositories_with_options_async(
request, headers, runtime
)
if response and response.body:
if not response.body.success:
raise ValueError(
f"阿里云请求失败: {response.body.error_code} - "
f"{response.body.error_message}"
)
repos = response.body.result or []
if not repos:
break
for repo in repos:
repo_dict = repo.to_map()
# 尝试多种可能的字段名
path_with_ns = (
repo_dict.get("pathWithNamespace")
or repo_dict.get("path_with_namespace")
or repo_dict.get("path")
or ""
)
# pathWithNamespace 格式: {org_id}/{group_path}/{repo_name}
# 只保留属于该分组的仓库
if f"/{group_path}/" in path_with_ns:
result.append(repo_dict)
# 同时更新缓存
repo_name = repo_dict.get("name", "")
# 注意:阿里云API返回的ID字段是 "Id" (大写I)
repo_id = str(
repo_dict.get("Id") or repo_dict.get("id") or ""
)
if repo_name and repo_id:
cls._dynamic_repo_cache[repo_name] = repo_id
# 如果返回的数量小于请求的数量,说明已经没有更多数据
if len(repos) < per_page:
break
page += 1
else:
break
return result
except Exception as e:
raise ValueError(f"获取仓库列表失败: {e}")
@classmethod
async def get_repository_id(cls, repo_name: str) -> str | None:
"""通过仓库名称获取仓库ID
优先从静态映射中查找,然后从缓存中查找,最后从API动态查询
参数:
repo_name: 仓库名称
返回:
str | None: 仓库ID,未找到返回 None
"""
# 1. 先从静态映射中查找
if repo_id := ALIYUN_REPO_MAPPING.get(repo_name):
return repo_id
# 2. 从动态缓存中查找
if repo_id := cls._dynamic_repo_cache.get(repo_name):
return repo_id
# 3. 从外部插件分组中动态查询
for group_path in ALIYUN_EXTERNAL_PLUGIN_GROUPS:
try:
repos = await cls.list_group_repositories(group_path)
for repo in repos:
if repo.get("name") == repo_name:
# 注意:阿里云API返回的ID字段是 "Id" (大写I)
repo_id = str(repo.get("Id") or repo.get("id") or "")
if repo_id:
cls._dynamic_repo_cache[repo_name] = repo_id
return repo_id
except Exception:
continue
return None
@classmethod
async def debug_list_all_repositories(cls) -> list[dict]:
"""调试方法:列出组织下的所有仓库信息
返回:
list[dict]: 所有仓库信息列表
"""
try:
client = await cls.get_client()
result = []
request = devops_20210625_models.ListRepositoriesRequest(
organization_id=ALIYUN_ORG_ID,
access_token=base64.b64decode(
RDC_access_token_encrypted.encode()
).decode(),
page=1,
per_page=100,
)
runtime = util_models.RuntimeOptions()
headers = {}
response = await client.list_repositories_with_options_async(
request, headers, runtime
)
if response and response.body and response.body.result:
for repo in response.body.result:
repo_dict = repo.to_map()
result.append(repo_dict)
# 同时填充缓存
repo_name = repo_dict.get("name", "")
repo_id = str(repo_dict.get("Id") or repo_dict.get("id") or "")
if repo_name and repo_id:
cls._dynamic_repo_cache[repo_name] = repo_id
return result
except Exception as e:
raise ValueError(f"列出仓库失败: {e}")
@classmethod
async def _get_repository_id_or_raise(cls, repo: str) -> str:
"""获取仓库ID,如果未找到则抛出异常
参数:
repo: 仓库名称
返回:
str: 仓库ID
异常:
ValueError: 未找到仓库
"""
repository_id = await cls.get_repository_id(repo)
if not repository_id:
raise ValueError(f"未找到仓库 {repo} 对应的阿里云仓库ID")
return repository_id
@classmethod
async def get_file_content(
cls, file_path: str, repo: str, ref: str = "main"
@@ -346,9 +523,7 @@ class AliyunFileInfo:
str: 文件内容
"""
try:
repository_id = ALIYUN_REPO_MAPPING.get(repo)
if not repository_id:
raise ValueError(f"未找到仓库 {repo} 对应的阿里云仓库ID")
repository_id = await cls._get_repository_id_or_raise(repo)
client = await cls.get_client()
@@ -406,9 +581,7 @@ class AliyunFileInfo:
list[AliyunTree]: 仓库树信息列表
"""
try:
repository_id = ALIYUN_REPO_MAPPING.get(repo)
if not repository_id:
raise ValueError(f"未找到仓库 {repo} 对应的阿里云仓库ID")
repository_id = await cls._get_repository_id_or_raise(repo)
client = await cls.get_client()
@@ -452,9 +625,7 @@ class AliyunFileInfo:
commit: 最新提交信息
"""
try:
repository_id = ALIYUN_REPO_MAPPING.get(repo)
if not repository_id:
raise ValueError(f"未找到仓库 {repo} 对应的阿里云仓库ID")
repository_id = await cls._get_repository_id_or_raise(repo)
client = await cls.get_client()
@@ -498,9 +669,8 @@ class AliyunFileInfo:
@classmethod
async def parse_repo_info(cls, repo: str) -> list[str]:
"""解析仓库信息获取仓库树"""
repository_id = ALIYUN_REPO_MAPPING.get(repo)
if not repository_id:
raise ValueError(f"未找到仓库 {repo} 对应的阿里云仓库ID")
# 验证仓库存在
await cls._get_repository_id_or_raise(repo)
tree_list = await cls.get_repository_tree(
repo=repo,