Skip to content

Commit 709cd63

Browse files
committed
fix: type release fetch errors and streamline updater resolution
1 parent 13e6128 commit 709cd63

4 files changed

Lines changed: 87 additions & 79 deletions

File tree

astrbot/core/updator.py

Lines changed: 48 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import asyncio
21
import os
32
import sys
43
import time
@@ -12,7 +11,7 @@
1211
from astrbot.core.utils.astrbot_path import get_astrbot_path
1312
from astrbot.core.utils.io import download_file
1413

15-
from .zip_updator import ReleaseInfo, RepoZipUpdator
14+
from .zip_updator import FetchReleaseError, ReleaseInfo, RepoZipUpdator
1615

1716

1817
class AstrBotUpdator(RepoZipUpdator):
@@ -154,25 +153,23 @@ async def get_releases(self, latest: bool = True) -> list:
154153
@staticmethod
155154
def _is_expected_nightly_fetch_error(exc: BaseException) -> bool:
156155
expected_types = (
157-
asyncio.TimeoutError,
156+
TimeoutError,
158157
aiohttp.ClientError,
159158
JSONDecodeError,
159+
FetchReleaseError,
160160
)
161-
162-
def _matches(error: BaseException | None) -> bool:
163-
if error is None:
164-
return False
165-
if isinstance(error, expected_types):
166-
return True
167-
if str(error).startswith("请求失败,状态码:"):
161+
current: BaseException | None = exc
162+
seen: set[int] = set()
163+
while current is not None and id(current) not in seen:
164+
seen.add(id(current))
165+
if isinstance(current, expected_types):
168166
return True
169-
return False
170-
171-
return (
172-
_matches(exc)
173-
or _matches(getattr(exc, "__cause__", None))
174-
or _matches(getattr(exc, "__context__", None))
175-
)
167+
current = getattr(current, "__cause__", None) or getattr(
168+
current,
169+
"__context__",
170+
None,
171+
)
172+
return False
176173

177174
async def get_nightly_release(self) -> dict | None:
178175
nightly_release_url = f"{self.GITHUB_RELEASE_API}/tags/{self.NIGHTLY_TAG}"
@@ -204,35 +201,43 @@ async def get_releases_with_nightly(self, latest: bool = True) -> list:
204201
releases.insert(0, nightly_release)
205202
return releases
206203

207-
def _resolve_latest(self, releases: list) -> tuple[str, str]:
208-
latest_release = next(
209-
(
210-
item
211-
for item in releases
212-
if item.get("tag_name", "").lower() != self.NIGHTLY_TAG
213-
),
214-
None,
215-
)
216-
if latest_release is None:
217-
raise Exception("未找到可用的发布版本。")
204+
async def _resolve_update_target(
205+
self,
206+
latest: bool,
207+
version: str | None,
208+
) -> tuple[str, str]:
209+
version_str = str(version) if version is not None else ""
210+
211+
if latest:
212+
releases = await self.get_releases(latest=True)
213+
latest_release = next(
214+
(
215+
item
216+
for item in releases
217+
if item.get("tag_name", "").lower() != self.NIGHTLY_TAG
218+
),
219+
None,
220+
)
221+
if latest_release is None:
222+
raise Exception("未找到可用的发布版本。")
218223

219-
latest_version = latest_release["tag_name"]
220-
if self.compare_version(VERSION, latest_version) >= 0:
221-
raise Exception("当前已经是最新版本。")
222-
return latest_version, latest_release["zipball_url"]
224+
latest_version = latest_release["tag_name"]
225+
if self.compare_version(VERSION, latest_version) >= 0:
226+
raise Exception("当前已经是最新版本。")
227+
return latest_version, latest_release["zipball_url"]
223228

224-
def _resolve_nightly(self) -> tuple[str, str]:
225-
return self.NIGHTLY_TAG, (
226-
f"https://github.com/AstrBotDevs/AstrBot/archive/refs/tags/{self.NIGHTLY_TAG}.zip"
227-
)
229+
if version_str.lower() == self.NIGHTLY_TAG:
230+
return self.NIGHTLY_TAG, (
231+
f"https://github.com/AstrBotDevs/AstrBot/archive/refs/tags/{self.NIGHTLY_TAG}.zip"
232+
)
228233

229-
def _resolve_tag(self, releases: list, version_str: str) -> tuple[str, str]:
230-
for data in releases:
231-
if data["tag_name"] == version_str:
232-
return version_str, data["zipball_url"]
233-
raise Exception(f"未找到版本号为 {version_str} 的更新文件。")
234+
if version_str.startswith("v"):
235+
releases = await self.get_releases(latest=False)
236+
for data in releases:
237+
if data["tag_name"] == version_str:
238+
return version_str, data["zipball_url"]
239+
raise Exception(f"未找到版本号为 {version_str} 的更新文件。")
234240

235-
def _resolve_commit(self, version_str: str) -> tuple[str, str]:
236241
if len(version_str) != 40:
237242
raise Exception("commit hash 长度不正确,应为 40")
238243
return (
@@ -246,17 +251,7 @@ async def update(self, reboot=False, latest=True, version=None, proxy="") -> Non
246251
"Error: You are running AstrBot via CLI, please use `pip` or `uv tool upgrade` to update AstrBot."
247252
) # 避免版本管理混乱
248253

249-
version_str = str(version) if version is not None else ""
250-
if latest:
251-
releases = await self.get_releases(latest=True)
252-
target_version, file_url = self._resolve_latest(releases)
253-
elif version_str.lower() == self.NIGHTLY_TAG:
254-
target_version, file_url = self._resolve_nightly()
255-
elif version_str.startswith("v"):
256-
releases = await self.get_releases(latest=False)
257-
target_version, file_url = self._resolve_tag(releases, version_str)
258-
else:
259-
target_version, file_url = self._resolve_commit(version_str)
254+
target_version, file_url = await self._resolve_update_target(latest, version)
260255

261256
logger.info(f"准备更新至 AstrBot Core: {target_version}")
262257

astrbot/core/zip_updator.py

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import shutil
44
import ssl
55
import zipfile
6+
from json import JSONDecodeError
67
from typing import NoReturn
78

89
import aiohttp
@@ -38,6 +39,10 @@ def __str__(self) -> str:
3839
return f"\n{self.body}\n\n版本: {self.version} | 发布于: {self.published_at}"
3940

4041

42+
class FetchReleaseError(Exception):
43+
"""Expected errors while fetching release metadata from remote services."""
44+
45+
4146
class RepoZipUpdator:
4247
def __init__(self, repo_mirror: str = "") -> None:
4348
self.repo_mirror = repo_mirror
@@ -67,30 +72,33 @@ async def fetch_release_info(self, url: str, latest: bool = True) -> list:
6772
logger.error(
6873
f"请求 {url} 失败,状态码: {response.status}, 内容: {text}",
6974
)
70-
raise Exception(f"请求失败,状态码: {response.status}")
75+
raise FetchReleaseError(f"请求失败,状态码: {response.status}")
7176
result = await response.json()
72-
if isinstance(result, dict):
73-
result = [result]
74-
if not result:
75-
return []
76-
# if latest:
77-
# ret = self.github_api_release_parser([result[0]])
78-
# else:
79-
# ret = self.github_api_release_parser(result)
80-
ret = []
81-
for release in result:
82-
ret.append(
83-
{
84-
"version": release["name"],
85-
"published_at": release["published_at"],
86-
"body": release["body"],
87-
"tag_name": release["tag_name"],
88-
"zipball_url": release["zipball_url"],
89-
},
90-
)
91-
except Exception as e:
77+
except FetchReleaseError:
78+
raise
79+
except (TimeoutError, aiohttp.ClientError, JSONDecodeError) as e:
9280
logger.error(f"解析版本信息时发生异常: {e}")
93-
raise Exception("解析版本信息失败") from e
81+
raise FetchReleaseError("解析版本信息失败") from e
82+
83+
if isinstance(result, dict):
84+
result = [result]
85+
if not result:
86+
return []
87+
# if latest:
88+
# ret = self.github_api_release_parser([result[0]])
89+
# else:
90+
# ret = self.github_api_release_parser(result)
91+
ret = []
92+
for release in result:
93+
ret.append(
94+
{
95+
"version": release["name"],
96+
"published_at": release["published_at"],
97+
"body": release["body"],
98+
"tag_name": release["tag_name"],
99+
"zipball_url": release["zipball_url"],
100+
},
101+
)
94102
return ret
95103

96104
def github_api_release_parser(self, releases: list) -> list:

tests/unit/test_prerelease_rule_sync.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ def test_prerelease_rule_is_synced_with_dashboard():
99
vue_path = repo_root / "dashboard/src/layouts/full/vertical-header/VerticalHeader.vue"
1010
content = vue_path.read_text(encoding="utf-8")
1111

12-
match = re.search(r"const PRE_RELEASE_TAG_REGEX = /(.+?)/i;", content)
12+
match = re.search(
13+
r"const\s+PRE_RELEASE_TAG_REGEX\s*=\s*/(.+?)/([a-z]*)\s*;?",
14+
content,
15+
)
1316
assert match is not None
14-
assert match.group(1) == PRERELEASE_TAG_REGEX.pattern
17+
vue_pattern, vue_flags = match.groups()
18+
assert vue_pattern == PRERELEASE_TAG_REGEX.pattern
19+
assert ("i" in vue_flags) == bool(PRERELEASE_TAG_REGEX.flags & re.IGNORECASE)

tests/unit/test_updator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22

33
from astrbot.core.updator import AstrBotUpdator
4-
from astrbot.core.zip_updator import RepoZipUpdator
4+
from astrbot.core.zip_updator import FetchReleaseError, RepoZipUpdator
55

66

77
@pytest.mark.asyncio
@@ -136,7 +136,7 @@ async def test_get_nightly_release_returns_none_for_expected_fetch_error(monkeyp
136136

137137
async def mock_fetch_release_info(url: str, latest: bool = True):
138138
_ = url, latest
139-
raise Exception("请求失败,状态码: 404")
139+
raise FetchReleaseError("请求失败,状态码: 404")
140140

141141
monkeypatch.setattr(updator, "fetch_release_info", mock_fetch_release_info)
142142

0 commit comments

Comments
 (0)