74 lines
2.8 KiB
Python
74 lines
2.8 KiB
Python
"""
|
||
DashScope SDK 补丁:修复 __getattr__ 违反 Python 属性访问协议的 bug。
|
||
|
||
背景
|
||
----
|
||
DashScope SDK 的 DictMixin(所有响应类的基类)的 __getattr__ 实现为:
|
||
|
||
def __getattr__(self, attr):
|
||
return self[attr]
|
||
|
||
当属性/键不存在时,它抛出 KeyError。但按照 Python 数据模型规范,
|
||
__getattr__ 应当抛出 AttributeError,否则 hasattr()/getattr(obj, name, default)
|
||
等内置函数会失效。
|
||
|
||
实际影响
|
||
--------
|
||
requests 库在构造 HTTPError 时会调用 hasattr(response, "request")
|
||
(见 requests/exceptions.py:22),当 DashScope 响应对象参与异常链路时,
|
||
hasattr 会因 KeyError 直接崩溃,掩盖了真正的 HTTP 错误(如 429 限流、超时)。
|
||
|
||
此时抛出的异常表现为 KeyError('request'),极具误导性,并导致项目内已有的
|
||
429 自动重试逻辑无法捕获真正的限流错误。
|
||
|
||
参考
|
||
----
|
||
DashScope SDK 官方 Issue #114:
|
||
https://github.com/dashscope/dashscope-sdk-python/issues/114
|
||
|
||
修复
|
||
----
|
||
对 DictMixin.__getattr__ 进行 monkey-patch,将 KeyError 转换为 AttributeError,
|
||
使其符合 Python 语义。补丁应用于基类,因此所有派生响应类型(DashScopeAPIResponse、
|
||
GenerationResponse、MultiModalConversationResponse 等)都能一次性受益。
|
||
|
||
使用方式
|
||
--------
|
||
在应用入口(main.py / celery_worker.py)的最顶部导入本模块,
|
||
在任何 DashScope 调用发生前完成补丁注入:
|
||
|
||
import app.plugins.dashscope_patch # noqa: F401
|
||
"""
|
||
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
try:
|
||
from dashscope.api_entities.dashscope_response import DictMixin
|
||
|
||
# 防止被重复应用(例如 main 和 celery worker 都导入时)
|
||
if not getattr(DictMixin, "_redbear_getattr_patched", False):
|
||
_orig_getattr = DictMixin.__getattr__
|
||
|
||
def _safe_getattr(self, attr):
|
||
"""符合 Python 语义的 __getattr__:键缺失抛 AttributeError 而非 KeyError。"""
|
||
try:
|
||
return _orig_getattr(self, attr)
|
||
except KeyError as e:
|
||
# 使用 `from None` 抑制 KeyError 链,避免异常信息里出现误导性的
|
||
# "During handling of the above exception..." 堆栈
|
||
raise AttributeError(attr) from None
|
||
|
||
DictMixin.__getattr__ = _safe_getattr
|
||
DictMixin._redbear_getattr_patched = True # type: ignore[attr-defined]
|
||
logger.info(
|
||
"DashScope SDK 补丁已生效:DictMixin.__getattr__ 在缺失键时抛 AttributeError"
|
||
)
|
||
except ImportError:
|
||
# DashScope SDK 未安装时跳过,不影响其他 provider
|
||
logger.debug("未安装 dashscope,跳过 DashScope SDK 补丁")
|
||
except Exception as e:
|
||
# 补丁失败不应阻止应用启动
|
||
logger.warning(f"应用 DashScope SDK 补丁失败,将继续启动: {e}")
|