AttributeError: ‘function’ object has no attribute ‘__wrapped__’

该报错是 Dify 测试代码在 Windows 上因路径分隔符不兼容导致的遍历装饰器链失败,优先检查测试文件中的 co_filename.endswith() 比较是否被反斜杠路径干扰。

快速结论:该报错是 Dify 测试代码在 Windows 上因路径分隔符不兼容导致的遍历装饰器链失败,优先检查测试文件中的 co_filename.endswith() 比较是否被反斜杠路径干扰。

适用环境:Dify(源码安装,API 1.16.x,main 分支 @ b6824334cf),Windows 操作系统。

最快修复方案:暂无确认的一步修复方案。Issue 中是建议性修复(归一化路径比较),并未说明已合入主分支。

注意事项:该修复仅建议在测试代码中修改路径比较方式,不影响 Dify 运行时功能;实测修复效果和建议方案尚未经过 Issue 作者或维护者验证。

问题场景

在 Windows 上通过 pytest 运行 Dify API 的单元测试 test_model_config_api.py::test_post_uses_one_session_and_rolls_back_when_signal_fails 时,测试代码尝试遍历装饰器链并断言内部包装函数来自 controllers/console/app/wraps.py,但遍历过程中抛出了 AttributeError,测试失败。该测试在 Linux CI 上可以正常通过。

报错原文

AttributeError: 'function' object has no attribute '__wrapped__'

原因分析

测试代码中使用了硬编码的正斜杠路径来匹配 co_filename,例如:

while not method.__code__.co_filename.endswith("controllers/common/session.py"):
    method = method.__wrapped__
assert method.__wrapped__.__code__.co_filename.endswith("controllers/console/app/wraps.py")

在 Windows 上,Python 报告的 co_filename 使用反斜杠路径(例如 D:\dify\api\controllers\common\session.py),因此 endswith 永远无法匹配,循环会一路解包到装饰器链的末尾,最终访问不存在的 __wrapped__ 属性而抛出 AttributeError。这本质上是测试代码的路径分隔符可移植性 bug,与装饰器链本身是否完整无关(Issue 中已验证 with_session 装饰器存在)。

环境排查

  • 确认运行环境是否为 Windows(反斜杠路径触发此问题)。
  • 确认 Dify 源码版本为 main 分支 @ b6824334cf(API 1.16.x)。
  • 检查测试文件 api/tests/unit_tests/controllers/console/app/test_model_config_api.py 第 142-144 行是否存在硬编码正斜杠的 endswith 比较。

解决步骤

  1. 定位测试文件 api/tests/unit_tests/controllers/console/app/test_model_config_api.py 中的装饰器链遍历逻辑(约第 142-144 行)。
  2. 将原始的 endswith 路径比较替换为归一化路径比较,例如将 co_filename 中的反斜杠替换为正斜杠后再比较:
while not method.__code__.co_filename.replace("\\", "/").endswith("controllers/common/session.py"):
    method = method.__wrapped__
assert method.__wrapped__.__code__.co_filename.replace("\\", "/").endswith("controllers/console/app/wraps.py")
  1. (可选更规范做法)使用 pathlib 归一化路径后再做后缀匹配,例如定义辅助函数:
import pathlib

def _filename_matches(code, suffix):
    return pathlib.PurePosixPath(code.co_filename.replace("\\", "/")).as_posix().endswith(suffix)

method = model_config_module.ModelConfigResource.post
while not _filename_matches(method.__code__, "controllers/common/session.py"):
    method = method.__wrapped__
assert _filename_matches(method.__wrapped__.__code__, "controllers/console/app/wraps.py")
  1. 修改后重新运行原测试命令确认通过。

验证方法

在 Windows 上重新运行同一 pytest 用例,若不再抛出 AttributeError: 'function' object has no attribute '__wrapped__' 且测试全部通过,即可确认修复生效。该修复仅影响测试代码,不影响 Dify 正常运行。

参考来源

langgenius/dify #39890

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 18661

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注