From d7d3cfce69e59d8096490eb0b95f50ef2236af69 Mon Sep 17 00:00:00 2001 From: snowy-yang <102860110+snowy-yang@users.noreply.github.com> Date: Sat, 26 Sep 2026 18:53:53 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20HTTP=20=E5=9E=AB=E7=89=87=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E5=B1=82=E6=AC=A1=E6=94=B6=E6=95=9B=E5=88=B0=20core.h?= =?UTF-8?q?ttp=5Ferrors=20=E5=8D=95=E4=B8=80=E6=9D=A5=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为什么:core/aiohttp_compat.py 与 core/sync_http.py 各自定义了一份 RequestException / HTTPError,是**两个不同的类**。调用方在同步段捕获 core.sync_http.HTTPError、异步段抛的却是 core.aiohttp_compat.HTTPError, 两者无继承关系——异常会穿透所有 except 分支,属于隐藏的跨层 bug。 改法:新建 core/http_errors.py 作为唯一定义处(RequestException 基类 + HTTPError / Timeout / ConnectionError),两个垫片改为从它导入并再导出。 刻意保留 requests 的命名约定(Timeout / ConnectionError 与内置同名, 调用方需带模块前缀引用),已在 docstring 写明,避免后来者误改。 注意:sync_http 补了 __all__ 显式再导出——models._RetryFromScratch 继承它、 tests/test_aria2.py 直接导入它,符号必须保持可导入。 --- core/aiohttp_compat.py | 24 ++---------------------- core/http_errors.py | 38 ++++++++++++++++++++++++++++++++++++++ core/sync_http.py | 18 ++++++------------ 3 files changed, 46 insertions(+), 34 deletions(-) create mode 100644 core/http_errors.py diff --git a/core/aiohttp_compat.py b/core/aiohttp_compat.py index 7cc8c75..f783887 100644 --- a/core/aiohttp_compat.py +++ b/core/aiohttp_compat.py @@ -21,31 +21,11 @@ from typing import Any import aiohttp +from core.http_errors import ConnectionError, HTTPError, RequestException, Timeout + DEFAULT_TIMEOUT = 30 DEFAULT_MAX_BYTES = 8 * 1024 * 1024 -# ---------- 异常层次(对齐 requests 命名,便于原样 except) ---------- - - -class RequestException(Exception): - """请求异常基类(网络/协议层)。""" - - -class HTTPError(RequestException): - """raise_for_status() 抛出的 HTTP 状态异常。""" - - def __init__(self, message: str, response: 'Response | None' = None): - super().__init__(message) - self.response = response - - -class Timeout(RequestException): - """请求超时。""" - - -class ConnectionError(RequestException): - """连接错误。""" - class Response: """requests.Response 的异步子集:body 已读全,属性同步访问。""" diff --git a/core/http_errors.py b/core/http_errors.py new file mode 100644 index 0000000..74ae34d --- /dev/null +++ b/core/http_errors.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +"""requests 同形异常层次(同步 / 异步两个 HTTP 垫片共用)。 + +`core.aiohttp_compat`(异步)与 `core.sync_http`(同步)此前各自定义了一份 +`RequestException` / `HTTPError`——两份定义互不相干,于是同一条调用链上 +「同步段抛的异常捕获不到异步段的同类失败」,调用方被迫记住自己走的是哪条垫片。 + +此处收敛为唯一来源;命名沿用 requests(`Timeout` / `ConnectionError` 与内置 +异常同名,这是 requests 自身的约定——调用方应带模块前缀引用,例如 +`requests.ConnectionError`,裸 `ConnectionError` 会命中内置的那个)。 + +层次: + RequestException 网络 / 协议层失败(可捕获的总入口) + ├── HTTPError raise_for_status() 抛出的状态异常(携带 response) + ├── Timeout 请求超时 + └── ConnectionError 连接错误 +""" +from typing import Any + + +class RequestException(Exception): + """请求异常基类(网络/协议层)。""" + + +class HTTPError(RequestException): + """raise_for_status() 抛出的 HTTP 状态异常。""" + + def __init__(self, message: str, response: Any = None): + super().__init__(message) + self.response = response + + +class Timeout(RequestException): + """请求超时。""" + + +class ConnectionError(RequestException): + """连接错误。""" diff --git a/core/sync_http.py b/core/sync_http.py index b387c96..ae050ed 100644 --- a/core/sync_http.py +++ b/core/sync_http.py @@ -16,22 +16,16 @@ from typing import Any from core import async_bridge from core.http_client import client as _client +from core.http_errors import HTTPError, RequestException + +# 异常层次与异步垫片(core.aiohttp_compat)共用同一份定义——调用方可用同一组 +# 类型捕获两条通道的失败。RequestException 在此再导出:music_downloader.models +# 的 _RetryFromScratch 继承它,既有 except 与测试也直接导入它。 +__all__ = ['HTTPError', 'RequestException', 'Session', 'SyncResponse', 'get', 'post'] _DEFAULT_TIMEOUT = 30 -class RequestException(Exception): - """请求异常基类(对齐 requests 命名,便于迁移)。""" - - -class HTTPError(RequestException): - """raise_for_status() 抛出的 HTTP 状态异常。""" - - def __init__(self, message: str, response: 'SyncResponse | None' = None): - super().__init__(message) - self.response = response - - class SyncResponse: """requests.Response 的同步子集(底层 aiohttp)。"""