Skip to content

code_executors: defer docker and magic imports to avoid hang on Windows#231

Open
Fromsko wants to merge 15 commits into
trpc-group:mainfrom
Fromsko:fix/lazy-import-docker
Open

code_executors: defer docker and magic imports to avoid hang on Windows#231
Fromsko wants to merge 15 commits into
trpc-group:mainfrom
Fromsko:fix/lazy-import-docker

Conversation

@Fromsko

@Fromsko Fromsko commented Jul 24, 2026

Copy link
Copy Markdown

Fixes #230. Defer top-level import docker and import magic to call-time to prevent hang/segfault on Windows without Docker/libmagic. See issue for full details.

…ws without Docker/libmagic

On Windows (and other platforms without Docker Desktop or libmagic installed),
top-level `import docker` and `import magic` cause the interpreter to hang or
segfault while probing for native daemons/libraries. This makes the entire
trpc_agent_sdk package unusable — even a simple `from trpc_agent_sdk.agents
import LlmAgent` never returns.

Both imports are now deferred to call-time:
- _container_cli.py: `import docker` moved into `_import_docker()`, called
  lazily from `_init_docker_client()` on first real use.
- _files.py: `import magic` moved inside `detect_content_type()`, wrapped in
  try/except to gracefully degrade when python-magic is unavailable.

Verified locally: `from trpc_agent_sdk.agents import LlmAgent` now completes
instantly on Windows 11 without Docker Desktop, and the minimal example
(weather agent with tool calling) runs end-to-end.

Fixes trpc-group#230
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我现在有足够的信息来进行审查了。让我来分析一下关键问题。

_import_docker() 函数在 globals() 中注入符号,但并没有将它们设置为模块级属性,除非在首次使用时进行调用。关键点在于:_init_docker_client 调用了它,而该函数在 __init__ 中被调用,因此 Docker 路径是已覆盖的。对于 _files.py 中的 magic 模块,使用裸 except Exception 会掩盖错误,但考虑到历史行为,这可能是可接受的。让我检查一下类型注解的问题 —— client 属性注解为 docker.DockerClientcontainer 属性使用了 Container,但它们仅在 _import_docker() 运行后才定义。这是没问题的,因为在运行时注解不会被求值。

让我评估一下 detect_content_type 中将 return magic.from_buffer(data, mime=True) 放在 try 内部与外部的差异。如果 from_buffer 引发异常(例如 magic 错误),现在会被吞掉并进入基于内容的回退处理。之前 HAS_MAGIC 只检查了 ImportError。因此行为发生了变化:之前 from_buffer 的运行时错误会向上传播;现在却被吞掉了。这是一个轻微的行为改变。

发现的问题

⚠️ Warning

  • trpc_agent_sdk/code_executors/utils/_files.py:233-237magic.from_buffer 的调用被放入 try/except Exception 中,会吞掉所有运行时异常
    • 原实现仅用 HAS_MAGIC 判断导入是否成功,from_buffer 抛出的异常会向上传播;新实现把 from_buffer 也包进 except Exception: pass,导致 libmagic 运行时错误(如数据损坏、magic 数据库问题)被静默忽略并回退到简单的 content-based 检测,可能掩盖真实问题。建议将 import magicmagic.from_buffer 分开处理:导入失败时 pass 回退,但 from_buffer 的异常仍应上抛或至少 logger.exception 记录。

💡 Suggestion

  • trpc_agent_sdk/code_executors/container/_container_cli.py:38-45_import_docker() 通过 globals().update(...) 注入多个符号,且函数每次调用都重复执行导入与字典更新
    • 该函数在 _init_docker_client 中被调用,而后者在 ContainerClient.__init__ 中执行,因此每个实例都会重复触发导入和全局字典写入。建议在导入成功后用一个模块级标志(如 _docker_imported)做一次短路,避免重复开销;同时 globals().update 这种隐式注入方式可考虑改为在方法内直接 import docker 局部引用,便于阅读和维护。

总结

整体改动方向合理(延迟导入 Docker SDK 和 libmagic 以避免无环境下导入卡死/崩溃),无 Critical 级阻塞问题。主要风险在 _files.py 中将 magic.from_buffer 的运行时异常一并吞掉,建议拆分导入失败与运行时错误的处理逻辑。

测试建议

  • 暂无额外测试建议。如需加固,可补充一个用例:在 detect_content_type 中模拟 magic.from_buffer 抛出非 ImportError 异常时,验证行为是否符合预期(上抛或记录日志而非静默回退)。

- detect_content_type now skips python-magic entirely on win32 to avoid
  native libmagic access violations (not catchable by try/except)
- HAS_MAGIC retained as module-level False constant for backward compat
  with tests that mock it
- test_detect_content_type_with_magic marked skipif(win32)
- No new test failures: 19 pass, 1 skip, 11 pre-existing Windows
  path/glob failures unchanged from main branch
@Fromsko

Fromsko commented Jul 24, 2026

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

@Fromsko

Fromsko commented Jul 24, 2026

Copy link
Copy Markdown
Author

recheck

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已经审查了 pr.diff 及相关上下文。下面是结论。

发现的问题

🚨 Critical

  • tests/code_executors/test_utils_files.py:332-344test_detect_content_type_with_magic 因 lazy import 改动而失效,必然失败

    • 测试通过 @patch('..._files.magic', create=True) 替换的是 _files 模块上的 magic 属性;但新实现把 import magic 改成了 detect_content_type 内部的局部 import,局部 import magic 只查 sys.modules,根本不会读取被 patch 的模块属性,mock 永远不会被调用。无论环境是否安装 python-magic:装了→走真实 magic.from_buffer 返回非 "application/custom";没装→ImportError 被吞→回落到文本检测返回 text/plain; charset=utf-8。两种情况下 assert mime_type == "application/custom"assert_called_once_with 都会失败。需要改为 patch sys.modules['magic'] 或在函数内通过模块属性引用 magic,使 patch 生效。
  • trpc_agent_sdk/code_executors/utils/_files.py:239HAS_MAGIC 被硬编码为 False,导致 python-magic 检测路径在生产环境成为死代码

    • 注释声称"real check is lazy",但 if HAS_MAGIC and sys.platform != 'win32' 中的 HAS_MAGIC 永远为 False,函数内的 import magic 永远不会执行。即使用户在 Linux/Mac 上正确安装了 libmagic,magic 内容类型检测也不再生效,所有无法由文件名推断的文件都会回落到简单的字节签名检测或 application/octet-stream,属于功能性回归。修复方向:去掉 HAS_MAGIC 门控,直接在非 win32 平台尝试 import magic 并缓存结果,或在模块加载时用安全方式探测后写入 HAS_MAGIC

⚠️ Warning

  • trpc_agent_sdk/code_executors/utils/_files.py:239-244:magic 调用异常被静默吞掉

    • except Exception: pass 会掩盖 libmagic 已安装但运行时出错(如 DLL 版本不匹配、损坏数据)等真实问题,调用方无法区分"未安装 magic"与"magic 执行失败"。建议至少 logger.debug 记录异常,便于排查内容类型检测异常降级的原因。
  • trpc_agent_sdk/code_executors/container/_container_cli.py:43-50_import_docker()globals().update 注入多个名字,可读性与可维护性较差

    • 该方式使 dockerContainerframes_iter 等在静态分析下显示为未定义,且每次调用都重复 import + 字典更新。当前功能正确(from __future__ import annotations 使类型注解为字符串,不会在类定义期触发 NameError),但建议改为在函数内 import docker 后直接以局部变量使用,或仅缓存到模块级单一变量,减少全局命名空间污染。

总结

存在两个必须修复的问题:一是 test_detect_content_type_with_magic 因 magic 改为局部 import 后 patch 失效,测试必然失败并阻塞 CI;二是 HAS_MAGIC 被硬编码为 False,使生产环境的 python-magic 检测路径完全失效,与 PR"延迟导入以保留功能"的意图相悖。两者都需在合并前修复。

测试建议

  • 修复 test_detect_content_type_with_magic 的 patch 方式(patch sys.modules['magic']),并补充一个"magic 抛异常时回落到字节检测"的分支用例,覆盖 except Exception: pass 路径。
  • 建议补充一个非 win32 平台、真实/模拟 magic 可用时 detect_content_type 返回 magic 结果的集成断言,避免再次出现 HAS_MAGIC 恒为 False 导致功能静默失效却无测试覆盖的情况。

@@ -328,6 +331,7 @@ def test_detect_content_type_text_utf8(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_detect_content_type_with_magic 因局部 import 导致 patch 失效

@patch('..._files.magic', create=True) 替换的是模块属性,但新实现把 import magic 移到函数内部,只查 sys.modules,mock 永不会被调用,断言必然失败。应改为 patch sys.modules['magic'] 或在函数内通过模块属性引用 magic。

# On Windows, python-magic requires a native libmagic DLL that, when
# missing, causes an access violation at import time (not catchable by
# try/except). We skip it entirely on win32 and fall through to the
# simple content-based detection below.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HAS_MAGIC 被硬编码为 False,magic 检测路径成为死代码

if HAS_MAGIC and sys.platform != 'win32' 中 HAS_MAGIC 恒为 False,函数内 import magic 永不执行,即便 Linux/Mac 正确安装 libmagic 也不生效,所有无法由文件名推断的文件回落到字节检测。建议去掉门控,直接在非 win32 平台尝试 import 并缓存结果。

Address AI code review feedback on PR trpc-group#231:

1. Replace hardcoded HAS_MAGIC=False with cached _magic_module pattern:
   - On non-win32, detect_content_type lazily imports magic on first call
     and caches the module in _magic_module (checked once via _magic_checked)
   - This preserves the magic detection path on Linux/Mac where libmagic
     is properly installed, fixing the dead-code regression

2. Separate import failure from runtime failure:
   - ImportError → log debug, fall through to byte-signature detection
   - magic.from_buffer exception → log debug with exc_info, fall through
   - Previously both were silently swallowed

3. Fix test_detect_content_type_with_magic to inject mock directly into
   _magic_module instead of patching non-existent module attributes
Fromsko pushed a commit to Fromsko/trpc-agent-python that referenced this pull request Jul 24, 2026
Address AI code review feedback on PR trpc-group#231:

1. Replace hardcoded HAS_MAGIC=False with cached _magic_module pattern:
   - On non-win32, detect_content_type lazily imports magic on first call
     and caches the module in _magic_module (checked once via _magic_checked)
   - This preserves the magic detection path on Linux/Mac where libmagic
     is properly installed, fixing the dead-code regression

2. Separate import failure from runtime failure:
   - ImportError → log debug, fall through to byte-signature detection
   - magic.from_buffer exception → log debug with exc_info, fall through
   - Previously both were silently swallowed

3. Fix test_detect_content_type_with_magic to inject mock directly into
   _magic_module instead of patching non-existent module attributes
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已经全面了解情况,现在让我来撰写审查意见。

发现的问题

🚨 Critical

  • trpc_agent_sdk/code_executors/container/_container_cli.py:96trpc_agent_sdk/code_executors/container/_container_cli.py:101:属性类型注解引用未定义的名字 docker.DockerClient / Container
    • _import_docker() 只在 _init_docker_client()(即 __init__)调用时才把 dockerContainer 注入到 globals()。模块顶层没有 dockerContainer 名字。虽然本文件有 from __future__ import annotations,注解不会在定义时求值,但 client/container@property:访问 client.__annotations__ 或任何触发 typing.get_type_hints() 的工具(文档生成、序列化框架、IDE、pydantic 校验等)会抛 NameError: name 'docker' is not defined。更关键的是,如果有人仅 import 该模块而不实例化 ContainerClient(例如仅做类型导入、from ._container_cli import ContainerConfig),docker 永远不会被注入,类型注解处于永久未定义状态。建议在模块顶层用 TYPE_CHECKING 守卫下导入 docker/Container 供注解使用(运行时仍是延迟导入),或把注解改为字符串字面量并配合 TYPE_CHECKING
      from typing import TYPE_CHECKING
      if TYPE_CHECKING:
          import docker
          from docker.models.containers import Container

⚠️ Warning

  • trpc_agent_sdk/code_executors/container/_container_cli.py:28-50_import_docker() 每次调用都重复执行 import 并 globals().update

    • _init_docker_client() 每次实例化都会再次触发完整 import 与 globals().update_init_docker_client__init__ 中调用,而 __init__ 可被多次调用)。Python import 本身有缓存所以不致命,但 _import_docker() 缺少幂等短路,重复 globals().update 是无谓开销且容易掩盖“是否已导入”的状态判断。建议用模块级 _docker_imported 标志或 if 'docker' in globals(): return 短路。另外 docker.errors.DockerException_container_cli.py:134)在首次未导入时不存在——但因为有 _import_docker() 在前,运行时可解析;不过这与上面 Critical 同根,靠“先调用 _import_docker()”隐式保证,建议显式化。
  • trpc_agent_sdk/code_executors/utils/_files.py:240-252detect_content_type 的 magic 缓存对非 win32 一次性导入失败后会永久跳过

    • _magic_checked = True 在 import 失败(ImportError)后也被置位,导致后续即便用户安装了 python-magic 也不会再尝试。这在长生命周期进程(如服务首次启动时 magic 未装、运行中补装)下会永久退化为字节签名检测。此外 _magic_module.from_buffer 抛异常时只记 debug 日志并静默回退,但不会清除缓存,单次坏数据不应影响后续判断——当前行为尚可,但导入失败的可恢复性建议改为不置 _magic_checked 或保留重试。属于兼容性/稳定性边缘场景。
  • tests/code_executors/test_utils_files.py:332-353:测试依赖被测模块的私有实现细节且未保证隔离

    • 直接改写 _f._magic_module / _f._magic_checked 模块全局变量,依赖 detect_content_type 内部对这两个私有变量的读取顺序。若环境中实际安装了 python-magic,由于测试在 finally 里恢复 orig,恢复的是测试前的真实状态(可能为 None/False),逻辑正确;但测试名声称“with magic”,实际是用 MagicMock 注入,并没有验证真实 magic 路径,覆盖度有限。建议补充一条在真实 magic 可用时(importlib.util.find_spec('magic'))走真实库的 smoke 测试,或至少在注释说明这是注入路径测试。

💡 Suggestion

总结

变更方向正确(延迟导入避免无 Docker/libmagic 环境下导入即崩),但 _container_cli.py 的延迟导入与 @property 类型注解之间存在 Critical 级问题docker/Container 名字在模块顶层未定义,仅靠 from __future__ import annotations 掩盖,任何触发注解求值的场景都会 NameError,必须修复。其余为缓存可恢复性和测试有效性问题。

测试建议

  • 建议补充:在不实例化 ContainerClient 的情况下 import trpc_agent_sdk.code_executors.container._container_cli 并对 ContainerClient.client/ContainerClient.container 调用 typing.get_type_hints,验证不会抛 NameError
  • 建议补充:detect_content_type 在 magic import 失败后仍能正确回退到字节签名检测的用例(模拟 _magic_module=None 路径)。

Fromsko added 2 commits July 25, 2026 00:32
1. _container_cli.py: add TYPE_CHECKING guard for docker/Container type
   annotations so get_type_hints() won't NameError without Docker installed
2. _container_cli.py: add _docker_imported flag to short-circuit repeated
   _import_docker() calls
3. _files.py: only set _magic_checked=True after successful import or
   confirmed ImportError (clearer intent)
4. Run yapf -i on all changed files to satisfy CI format check
- _container_cli.py: add # noqa: F821 for frames_iter/demux_adaptor/
  consume_socket_output (injected at runtime by _import_docker)
- _files.py: add missing `from trpc_agent_sdk.log import logger`
- Both yapf and flake8 now pass clean on changed files
@Fromsko
Fromsko force-pushed the fix/lazy-import-docker branch from d7b0705 to 7f2e29a Compare July 24, 2026 16:41
Rook1ex added a commit to trpc-group/cla-database that referenced this pull request Jul 24, 2026
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

确认:之前的提交 (33aedbf) 特意保留了 HAS_MAGIC 以保证向后兼容,但此 PR 将其彻底删除了。HAS_MAGIC 是一个公共模块属性,可能被外部代码导入或引用(它过去是 trpc_agent_sdk.code_executors.utils._files.HAS_MAGIC)。这是一个潜在的向后兼容性破坏。这是一个合理的 ⚠️ Warning。

我现在已经完成了审查,让我来撰写审查结论。

发现的问题

🚨 Critical

(无)

⚠️ Warning

  • trpc_agent_sdk/code_executors/utils/_files.py:29:移除公共属性 HAS_MAGIC 造成向后兼容性破坏

    • 上一提交(33aedbf)明确“keep HAS_MAGIC for backward compat”,而本 PR 将模块级 HAS_MAGIC 直接删除、替换为内部 _magic_module/_magic_checkedHAS_MAGIC 是可被外部代码 from ..._files import HAS_MAGIC 引用的公开名称,删除后会在导入端抛 ImportError。建议保留 HAS_MAGIC 作为只读兼容别名(例如基于 _magic_checked/_magic_module 推导),或明确写入迁移说明。
  • trpc_agent_sdk/code_executors/container/_container_cli.py:33-58_import_docker() 的懒加载缓存无并发保护

    • _docker_imported 标志的 check-then-set 没有加锁,ContainerClient 可被多线程并发构造(见 _container_ws_runtime.py:944-946 的会话级构造)。虽然 import docker 受解释器 import-lock 保护、globals().update 原子,最坏只是重复导入,但仍是竞态。建议用 threading.Lock 包住 check-then-set,或在模块加载时用一次性 _import_docker() 简化。

💡 Suggestion

总结

整体风险偏低:核心目标是把 docker 与 python-magic 的导入推迟到运行时以避免 Windows 上的挂起/崩溃,方向正确且 from __future__ import annotations + TYPE_CHECKING 守卫配合得当。主要需修复的是移除公共 HAS_MAGIC 带来的兼容性破坏;其余为并发与可维护性层面的次要问题,无 Critical 阻塞项。

测试建议

  • 补充一条针对 HAS_MAGIC(若保留为兼容别名)的断言测试,确保其取值与 _magic_module 状态一致,防止兼容契约静默回归。
  • 暂无其他额外测试建议;现有 test_detect_content_type_with_magic 已通过注入 mock 覆盖 magic 命中路径,magic 缺失/异常回退路径可酌情补一条用例(构造 _magic_module=Nonefrom_buffer 抛异常两种场景)。

CI test_container_cli.py uses @patch("..._container_cli.docker") which
requires the docker name to exist at module level. Call _import_docker()
at test import time to ensure the module attribute is present for patching.

Fixes 12 CI failures (9405 passed, 0 regressions).
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.70175% with 11 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@6a2f7f9). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...ent_sdk/code_executors/container/_container_cli.py 80.00000% 7 Missing ⚠️
trpc_agent_sdk/code_executors/utils/_files.py 81.81818% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main        #231   +/-   ##
==========================================
  Coverage        ?   87.84600%           
==========================================
  Files           ?         482           
  Lines           ?       45195           
  Branches        ?           0           
==========================================
  Hits            ?       39702           
  Misses          ?        5493           
  Partials        ?           0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

1. Restore HAS_MAGIC as backward-compatible public alias (updated lazily
   when magic is successfully imported on non-win32)
2. Add threading.Lock to _import_docker() for concurrent safety
3. Remove dead code: unused `import sys as _sys` in test method
4. All yapf + flake8 clean, 47 passed, 0 regressions
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

静态分析已足够。让我完成代码审查。

发现的问题

🚨 Critical

  • trpc_agent_sdk/code_executors/container/_container_cli.py:299-301:惰性 import 的符号未做 NameError 兜底,运行期 NameError 会中断执行
    • _import_docker() 仅在 _init_docker_client()(即 __init__)里调用一次,而 frames_iter/demux_adaptor/consume_socket_output# noqa: F821)在 _exec_run_with_stdin 中被直接引用,这些符号只在 _import_docker() 成功执行后才会注入全局命名空间。在正常 __init__ 路径下没问题,但 _exec_run_with_stdin 是被 exec_run 通过 run_in_executor 调用的公开执行路径;一旦符号因任何原因未注入(例如测试用 ContainerClient.__new__ 绕过初始化、或 _import_docker 抛异常被吞),这里会抛 NameError 而非可识别的 Docker 错误,且被外层 except Exception 吞成 "Execution error"。建议在 _import_docker() 之外再保证这些符号的存在,或在 _exec_run_with_stdin 内显式校验。
    frames = frames_iter(sock, tty=False)  # noqa: F821
    demux_frames = (demux_adaptor(*frame) for frame in frames)  # noqa: F821
    output = consume_socket_output(demux_frames, demux=True)  # noqa: F821

⚠️ Warning

  • tests/code_executors/container/test_container_cli.py:33-35:测试用模块级副作用为 patch 兜底,掩盖了真实缺陷

    • 注释承认 _import_docker() 必须显式调用以让 @patch("..._container_cli.docker") 生效,说明生产代码的惰性注入依赖全局副作用、缺乏显式契约。这使 _exec_run_with_stdin 这条执行路径(_container_cli.py:299-301)在本测试套件里从未被覆盖,符号缺失风险无法被 CI 发现。建议补一个真实覆盖 _exec_run_with_stdin 的集成测试(mock socket),或至少断言这些符号在 import 后存在。
  • trpc_agent_sdk/code_executors/utils/_files.py:45,257,262HAS_MAGIC 模块级布尔与 _has_magic() 并存,存在语义漂移

    • 保留了向后兼容的 HAS_MAGIC,但它在首次 detect_content_type 调用前恒为 False,且只在 win32 之外的成功路径才更新;导入失败时 _magic_checked=TrueHAS_MAGIC 保持 False。任何在调用 detect_content_type 之前读取 HAS_MAGIC 的外部代码会拿到与实际能力不符的值。当前仓库内未见外部读取,定为 Warning;若承诺向后兼容,建议 HAS_MAGIC 改为模块级 _has_magic() 的代理或在 import 时即初始化。

💡 Suggestion

总结

整体方向合理(惰性导入规避无 Docker/libmagic 环境的崩溃),但 _exec_run_with_stdin 直接引用仅由副作用注入的符号、且无兜底与测试覆盖,是必须修复的稳定性/正确性隐患;HAS_MAGIC 双语义属于兼容性风险。

测试建议

  • 建议补一个覆盖 _exec_run_with_stdin 真实路径的测试(mock client.api.exec_create/exec_start 与 socket),验证 stdout/stderr/exit_code 解析,并确保 frames_iter 等符号确实可用。
  • 建议在非 win32 的测试矩阵里验证 HAS_MAGIC_has_magic() 的一致性(import magic 成功后两者均为 True)。

close_write()

frames = frames_iter(sock, tty=False)
demux_frames = (demux_adaptor(*frame) for frame in frames)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

惰性注入符号未做 NameError 兜底

frames_iter/demux_adaptor/consume_socket_output 仅在 _import_docker() 成功后通过副作用注入全局命名空间,_exec_run_with_stdin 直接引用却无显式校验。若符号未注入(如测试用 __new__ 绕过初始化或 import 异常被吞),会抛 NameError 并被外层 except Exception 吞成 "Execution error"。建议在 _exec_run_with_stdin 内显式校验这些符号存在,或在 _import_docker() 之外保证其可用。

….update

Address AI review Critical: eliminate # noqa: F821 and NameError risk
by declaring docker/Container/consume_socket_output/demux_adaptor/
frames_iter as module-level None placeholders, populated by
_import_docker() via direct assignment instead of globals().update.

- Remove TYPE_CHECKING guard (placeholders serve the same purpose)
- Remove all # noqa: F821 comments
- _exec_run_with_stdin now references real module variables, not
  dynamically injected names
- Tests @patch works naturally without _import_docker() side effects
- yapf + flake8 clean, 47 passed, 0 regressions
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已经掌握了足够的信息,现在我来完成这次审查。

_import_docker() 的 double-checked locking 中有一个值得注意的问题:外层的 fast-path 检查 if _docker_imported: return 发生在获取锁之前,这是没问题的。但请注意,它并没有将检查赋值给局部变量。这没问题。

让我检查一个真正的正确性问题:_has_magic()HAS_MAGIC 可能会不同步。如果 detect_content_type 在 Windows 上运行,_magic_checked 会保持为 False(因为 sys.platform != 'win32' 条件),且 _magic_module 保持为 None,所以 _has_magic() 返回 False —— 是一致的。OK。

但存在一个更微妙的问题:在非 Windows 系统上,如果首次调用时 import magic 成功,HAS_MAGIC=True_has_magic()HAS_MAGIC 保持同步。如果导入引发 ImportError,则两者都保持为 False。保持同步。没问题。

现在让我来编写审查报告。

发现的问题

🚨 Critical

  • trpc_agent_sdk/code_executors/utils/_files.py:258-266:magic 懒加载失败被永久缓存,但只捕获了 ImportError,无法覆盖其它常见失败情形
    • _magic_checked = Trueexcept ImportError 分支里将失败永久缓存。若 import magic 因 libmagic 缺失抛出 OSError/ImportError 之外的异常(例如旧版 magic 在无 DLL 时抛 find_library 相关 ImportError 子类以外的错误),异常会向上冒泡导致 detect_content_type 直接崩溃,而非回退到字节签名检测。注释声称“access violation 不可 catch”,那更应在 except Exception 级别兜底并缓存失败,确保任何导入失败都安全回退。建议把 except ImportError 改为 except Exception,与下方 from_buffer 的兜底策略一致。

⚠️ Warning

  • trpc_agent_sdk/code_executors/utils/_files.py:43-45:保留的 HAS_MAGIC 兼容别名在导入前恒为 False,破坏了原先“导入即确定”的语义

    • 旧实现中 HAS_MAGIC 在模块加载时就反映 magic 是否可用,外部代码若 from ..._files import HAS_MAGIC 做一次性判断,现在在首次调用 detect_content_type 之前永远是 False。仓库内当前虽无外部引用,但注释明确声称保留它是为兼容外部代码——而该别名实际上已不再可靠。建议要么在模块加载时同步探测一次(仅非 win32)以维持语义,要么删除别名并明确弃用,避免给外部调用方错误信号。
  • tests/code_executors/test_utils_files.py:344-352:测试通过直接改写模块全局变量注入 mock,但未还原 HAS_MAGIC

    • orig 只保存了 _magic_module_magic_checked,而 detect_content_type 在注入路径上不会改写 HAS_MAGIC(因为 _magic_checked 已为 True),所以当前能通过;但该测试与实现强耦合于内部缓存字段名,一旦实现调整(例如改回写 HAS_MAGIC)就会静默失效或污染后续测试。建议改为通过 monkeypatch fixture 统一管理,或至少把 HAS_MAGIC 一并纳入 orig/还原,提升健壮性。
  • trpc_agent_sdk/code_executors/container/_container_cli.py:188-195:捕获 Exception 后无条件包装为 RuntimeError,会掩盖 KeyboardInterrupt/SystemExit 之外的正常异常类型

    • 这是既有逻辑(diff 仅在上方新增 _import_docker() 调用),但懒加载使 _import_docker() 自身的 ImportError(docker 未安装)也会落入此 except Exception 分支,被转成“Unexpected error initializing Docker client”,丢失了“docker SDK 未安装”这一可操作信息。建议在调用 _import_docker() 外层单独处理 ImportError,给出明确的安装提示。

💡 Suggestion

  • trpc_agent_sdk/code_executors/container/_container_cli.py:61-77_import_docker() 的 double-checked lock 可读性可改善
    • 外层无锁快检查 if _docker_imported: return 与锁内二次检查是标准 DCL 模式,逻辑正确;但 global 声明把 5 个符号一次性列出,后续若新增符号易遗漏。可考虑用 globals().update(...) 或抽一个 _docker_symbols dict 统一管理,降低维护成本(不阻塞)。

总结

整体风险中等:核心懒加载改造方向正确,但 _files.py 的 magic 导入异常兜底范围不足(Critical),且保留的 HAS_MAGIC 兼容别名已不再可靠,存在误导外部调用方的风险。存在 1 个必须修复的问题。

测试建议

  • 补充一个“import magic 抛出非 ImportError 异常(如 OSError)时 detect_content_type 仍回退到字节签名检测”的测试,覆盖 _files.py:258-266 的失败缓存路径。
  • 补充 docker SDK 未安装(_import_dockerImportError)时 ContainerClient.__init__ 给出明确错误信息的测试。

# missing, causes an access violation at import time (not catchable by
# try/except). We skip it entirely on win32 and fall through to the
# simple content-based detection below.
global _magic_module, _magic_checked, HAS_MAGIC

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magic 懒加载失败缓存仅捕获 ImportError,无法覆盖其它导入失败

_magic_checked = Trueexcept ImportError 分支将失败永久缓存。若 import magic 抛出 OSError 等非 ImportError 异常(如 libmagic 缺失),异常会向上冒泡导致 detect_content_type 直接崩溃,而非回退到字节签名检测。建议改为 except Exception,与下方 from_buffer 的兜底策略一致。

…ests

Address latest AI review:
1. Change except ImportError to except Exception for magic import
   failure, catching OSError and other non-Import failures safely
2. Include HAS_MAGIC in test mock save/restore to prevent state leakage
3. Add exc_info=True to the debug log for better diagnostics
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已掌握足够上下文,开始撰写 review 结论。

发现的问题

🚨 Critical

  • trpc_agent_sdk/code_executors/container/_container_cli.py:151,163docker 占位变量为 None 时直接 docker.DockerClient / docker.errors.DockerException 会触发运行时错误
    • _import_docker() 仅在 _init_docker_client() 内被调用。若该 import 失败抛出异常(例如环境无 docker SDK 或导入期出错),docker 仍为 None,随后 except docker.errors.DockerException 会再次抛出 AttributeError: 'NoneType' object has no attribute 'errors',掩盖原始异常。更严重的是,@property client_container_cli.py:125)以 docker.DockerClient 作为返回注解,在未触发 import 的任何路径下若被静态工具或注解求值引用也会暴露 None。建议在 except 前用 getattr(docker, "errors", None) 兜底,或在 _import_docker 失败时立即 raise 而非保持 docker=None

⚠️ Warning

  • trpc_agent_sdk/code_executors/utils/_files.py:258-266:magic 导入结果缓存存在线程安全与状态一致性隐患

    • _magic_checked/_magic_module/HAS_MAGIC 三个全局量无锁保护,多线程并发首次调用 detect_content_type 时可能重复 import 或出现中间状态(_magic_module 已设但 _magic_checked 未置)。此外 HAS_MAGIC_has_magic() 在导入失败后不一致:失败分支未把 HAS_MAGIC 显式置 False(默认即 False,可接受),但成功后置 True 却无回退,与测试中的手动注入/恢复逻辑耦合较脆。建议用单锁包裹,或仅以 _magic_module is not None 作为唯一真值来源,移除冗余的 HAS_MAGIC 全局可变状态。
    • if sys.platform != 'win32' and not _magic_checked:
          try:
              import magic as _m
              _magic_module = _m
              HAS_MAGIC = True
              _magic_checked = True
          except Exception:
              ...
              _magic_checked = True
  • trpc_agent_sdk/code_executors/container/_container_cli.py:124-130from __future__ import annotations 下属性返回类型注解不再触发求值,但 client -> docker.DockerClient 仍误导

    • 由于 docker 在 import 前为 None,类型注解虽然因 annotations 延迟求值而安全,但 client/container_init_docker_client 抛错后 _client/_container 保持初始值,调用方拿到的会是 None 而非抛错。这属于既有行为,但本次改动新增了"占位+延迟 import"后,未在属性 getter 中加 if self._client is None: raise,破坏了 fail-fast。建议补防御。
  • tests/code_executors/test_utils_files.py:332-353:测试通过手动改写模块全局量注入 mock,存在状态泄漏与跨测试污染风险

    • finally 中恢复 _f._magic_module_f._magic_checked_f.HAS_MAGIC,但若该测试运行前其他测试已触发真实 magic 导入使 _magic_checked=True,恢复后 _magic_checked 仍为 True,后续依赖真实 magic 的路径不受影响;反之若中断在赋值之间(异常被 try 外吞掉)会留下脏状态。更稳妥的做法是用 unittest.mock.patch.object 上下文管理器一次性 patch 三个属性,保证原子回滚。

💡 Suggestion

  • trpc_agent_sdk/code_executors/utils/_files.py:45,257HAS_MAGIC 既是公开别名又作为可变全局参与内部状态机,语义双重。可考虑仅保留 _has_magic() 作为 live 探测入口,将 HAS_MAGIC 标注为 deprecated 或改为属性,降低后续维护者误用风险。

总结

整体风险中等:延迟 import 思路正确,但 _container_cli.pydocker 占位为 Noneexcept docker.errors.DockerException 会二次崩溃掩盖原始错误,属必须修复的 Critical;magic 模块状态机缺乏并发保护与测试注入的原子性,建议一并加固。

测试建议

  • 补充一个测试:在 _import_docker()ImportError 的场景下调用 ContainerClient(...),断言抛出的是清晰错误而非 AttributeError: NoneType.errors
  • 补充并发场景测试:多线程同时首次调用 detect_content_type,验证 _magic_module 不会被重复 import 或出现不一致状态。

Address AI review Critical: if _import_docker() fails silently and docker
remains None, the except docker.errors.DockerException line would throw
AttributeError: NoneType has no attribute errors, masking the real error.
Now explicitly checks docker is not None after import and raises a clear
RuntimeError with install instructions.
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

发现的问题

⚠️ Warning

  • trpc_agent_sdk/code_executors/utils/_files.py:262HAS_MAGIC 兼容别名语义已破坏

    • 旧代码在模块导入时即根据 import magic 成败设置 HAS_MAGIC,新代码将其初始化为 False,仅在首次成功调用 detect_content_type() 后才置 True。任何按旧约定在导入期读取 from ..._files import HAS_MAGIC 的外部代码会始终得到 False(即使 magic 可用),与注释声称的"向后兼容"不符。建议要么在模块导入期同步探测一次以维持原语义,要么删除该别名并改为只暴露 _has_magic()
  • trpc_agent_sdk/code_executors/container/_container_cli.py:148docker is None 守卫不可达,错误信息误导

    • _import_docker() 内部 import docker 未做 try/except,SDK 未安装时会直接抛 ImportError,根本到不了 if docker is None 守卫,因此注释承诺的"Docker SDK is not available. Install it with: pip install docker"永远不会出现。建议在 _import_docker() 内捕获 ImportError 并保持 docker=None,或删掉该死守卫、让 ImportError 自然向上传播并修正注释。

💡 Suggestion

  • tests/code_executors/container/test_container_cli.py:35:测试模块在导入期直接调用 _import_docker()
    • 这会在收集阶段强制导入真实 docker SDK,若 CI 环境未安装 docker 将导致整个测试文件收集失败。若希望测试可在无 docker 环境下收集,可将其移入 fixture 或加 skip 守卫;当前容器测试本就依赖 docker,影响有限,仅作维护性提示。

总结

整体为延迟导入重构,方向合理,无阻塞级别的安全或核心逻辑缺陷。两处 Warning 均为错误信息/兼容语义与实际行为不一致的维护性问题,建议修复以避免误导调用方。

测试建议

  • 补充一个 _import_docker()import dockerImportError 时的测试,断言 _init_docker_client() 抛出预期的清晰错误(覆盖上述守卫不可达路径)。
  • HAS_MAGIC / _has_magic() 在 magic 可用但未调用 detect_content_type 前的初始状态补充断言,锁定兼容语义。

…me semantics

1. _import_docker(): catch ImportError so docker stays None when SDK
   is not installed, making the `if docker is None` guard in
   _init_docker_client() actually reachable
2. _files.py: probe magic at module import time on non-win32 to set
   HAS_MAGIC immediately, preserving the original semantics where
   external code can check HAS_MAGIC right after import
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

现在让我更仔细地检查 _import_dockerexcept 子句问题,以及 Windows 下的 magic 模块崩溃是否影响 _files.py 的模块级代码路径(第 47-53 行的保护措施在 Windows 上会跳过该导入)。我已经掌握了足够的信息。

让我验证一个关于 _docker_imported 缓存导致后续在实际 docker 模块不可用时 docker 保持为 None 的真正担忧,但 _init_docker_client 已经对此进行了防护。很好。

让我检查一下 _import_docker 中仅使用 except ImportError 是否存在问题——docker 包可能引发其他异常,从而留下不一致的状态。实际上在锁内,如果发生非 ImportError_docker_imported 将不会被设置为 True(异常会传播)。这实际上更安全。可以接受。

现在开始撰写审查结论。

发现的问题

🚨 Critical

  • tests/code_executors/container/test_container_cli.py:30-37:模块级副作用调用 _import_docker() 会污染全局模块状态,且对真实 docker 的可用性敏感
    • 该调用在导入期执行 _import_docker():若测试机已安装 docker SDK,会把真实的 docker/Container 等模块符号写入 _container_cli 全局并置 _docker_imported=True,之后任何测试即便 @patch("...docker") 失败也无法重新触发导入;若未安装,docker 永久为 None。这使得测试行为依赖运行环境而非自洽的 mock,且 _docker_imported 一旦置位即不可恢复(无 reset 途径)。建议改为在 fixture 中用 monkeypatch 直接设置 _container_cli.docker 等符号,或显式管理 _docker_imported 状态,避免 import 期执行有状态的全局副作用。

⚠️ Warning

  • trpc_agent_sdk/code_executors/utils/_files.py:266-275:懒加载分支中先赋值 _magic_module 再置 _magic_checked=True,且未加锁,存在并发下重复导入/状态不一致风险

    • detect_content_typenot _magic_checked 时执行 import magic 并依次写 _magic_moduleHAS_MAGIC_magic_checked;多线程首次调用可能同时进入分支、重复执行 import 并互相覆盖。虽然 import 本身有 GIL 保护不会崩,但写入非原子,可能出现 _magic_checked=True_magic_module 仍为 None 的中间态被另一线程读到。建议参考 _container_cli._import_docker 加锁(双检锁),或至少先置 _magic_checked 再赋值,确保状态一致。
  • trpc_agent_sdk/code_executors/utils/_files.py:54:271HAS_MAGIC 作为模块级布尔在懒加载成功后才更新,无法反映给 from ..._files import HAS_MAGIC 的既有调用方

    • 仓库内尚无此类外部引用(grep 仅见本文件),但注释声称保留它为"向后兼容的公开别名"。若外部代码在模块导入时(Windows 或未装 magic)拿到 HAS_MAGIC=False 的绑定,后续懒加载置 True 不会更新其已绑定的局部变量,语义与旧版"导入即确定可用性"不一致。若确实保留为公开 API,应在文档中说明其仅在非 win32 且首次 detect_content_type 调用前可靠;否则建议弃用并指向 _has_magic()
  • tests/code_executors/container/test_container_cli.py:10-17:import 期调用函数并将 import 拆分到副作用调用两侧,违反导入顺序约定

    • _import_docker() 调用插入在两条 from ... import 之间,使后续 from trpc_agent_sdk.utils import CommandExecResult 出现在模块级可执行语句之后。虽然本仓 flake8 忽略 E402 不会阻断 CI,但降低了可读性与工具兼容性。建议把所有 import 集中到顶部,再在 conftest/fixture 中执行初始化副作用。

💡 Suggestion

  • trpc_agent_sdk/code_executors/container/_container_cli.py:81_import_docker 仅捕获 ImportError,但 docker SDK 在缺依赖时可能抛 ModuleNotFoundErrorImportError 子类,已覆盖)以外的初始化异常
    • 当前实现下非 ImportError 异常会向上传播且不置 _docker_imported,行为尚可接受;如希望更稳健,可改为 except Exceptionlogger.debug 记录,与 _files.py 的处理方式保持一致。

总结

整体改动方向(懒加载 docker/magic 以避免无依赖环境崩溃)合理,但测试侧通过 import 期执行 _import_docker() 引入了对真实环境敏感的全局副作用,存在 Critical 级别的稳定性与可复现性问题,建议优先修复;其余为并发状态一致性与公开别名语义的 Warning。

测试建议

  • 补充一条"docker SDK 未安装"路径的测试:mock _import_docker 使 docker 保持 None,断言 ContainerClient(...) 抛出 RuntimeError("Docker SDK is not available..."),覆盖 _container_cli.py:154-155 新增的 guard。
  • detect_content_type 增加并发首次调用场景(或在 win32 上断言走字节签名回退分支),验证 _magic_module/_magic_checked 状态不会出现不一致。

ContainerConfig,
)
from trpc_agent_sdk.utils import CommandExecResult
from trpc_agent_sdk.code_executors.container._container_cli import \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

模块级副作用调用 _import_docker 污染全局状态

import 期执行 _import_docker() 会把真实 docker 符号写入 _container_cli 全局并置 _docker_imported=True,使测试行为依赖运行环境而非自洽的 mock,且 _docker_imported 置位后不可恢复。建议改在 fixture 中用 monkeypatch 设置相关符号或显式管理 _docker_imported 状态,避免 import 期执行有状态副作用。

1. test_container_cli.py: replace module-level _import_docker() call
   with autouse module-scoped fixture, removing import-time global side
   effects and fixing import ordering (all imports at top)
2. _files.py: add threading.Lock for magic lazy import to prevent
   concurrent race condition on _magic_module/_magic_checked state
3. Consolidate _magic_checked=True inside lock for atomic state update
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已经有足够的信息进行评估了。让我来整理一下审查意见。

发现的问题

🚨 Critical

  • trpc_agent_sdk/code_executors/container/_container_cli.py:42:模块级符号 Container 占位符遮蔽了 docker.models.containers.Container,被 __init__.py 间接导入后破坏外部 from ..._container_cli import Container 的语义
    • 现在模块顶层 Container = None,导入本模块会得到 None 而非真正的 Docker Container 类型;若有外部代码依赖原始 Container 类型(例如类型注解、isinstance 校验),将静默失效。docker/consume_socket_output/demux_adaptor/frames_iter 同理从顶层真实对象变为占位 None,属于对外可见的破坏性变更。建议仅在 TYPE_CHECKING 下保留类型别名(如 Container: "docker.models.containers.Container" 字符串注解),运行期不在模块顶层暴露同名占位变量,或在文档/__all__ 中明确这些符号已非顶层可用。

⚠️ Warning

  • trpc_agent_sdk/code_executors/container/_container_cli.py:131-136client/container 属性的返回类型注解 docker.DockerClientContainer_import_docker() 未执行前为 None

    • 虽然 from __future__ import annotations 让注解不求值,但属性实际返回的是 self._client/self._container,类型注解依赖延迟导入的符号,IDE/类型检查在未触发懒加载时无法解析 docker。建议改用字符串字面量或 TYPE_CHECKING 块下的类型注解,确保静态类型解析正确。
  • tests/code_executors/container/test_container_cli.py:34-40_ensure_docker_imported fixture 调用真实 _import_docker() 触发对宿主真实 docker 包的导入

    • 该 fixture 为 autouse=True, scope="module",在 CI 缺少 docker 包的环境下 _import_docker() 静默失败(docker 仍为 None),随后多数 @patch("...docker") 测试虽能跑通(mock 覆盖),但 _make_mock_docker_clientimport dockertest_container_cli.py:101)会真实抛 ImportError,导致 ping_ok=False 分支测试在无 docker 包环境失败。建议 fixture 内对导入失败做显式断言或 skip,避免环境相关的不确定行为。
  • tests/code_executors/test_utils_files.py:332-353test_detect_content_type_with_magic 通过直接改写模块全局 _magic_module/_magic_checked/HAS_MAGIC 注入 mock,且在 win32 上 skip

    • 该测试在非 win32 且本机已装 python-magic 时,注入 mock 覆盖了已缓存的真实 _magic_modulefinally 恢复后 _magic_checked 仍为 True,逻辑可工作;但若并发或后续测试依赖真实 magic 路径,全局可变状态存在时序耦合风险。建议用 monkeypatch 替代手工 save/restore,或拆分“mock 路径”与“真实 magic 路径”测试以降低对全局状态的依赖。
  • trpc_agent_sdk/code_executors/utils/_files.py:50-57:非 win32 平台在模块导入时同步 import magic,与 PR 声称的“惰性导入以避免无 libmagic 时崩溃”目标存在冲突

    • 注释说明 magic 在缺 libmagic 时可能“不可被 try/except 捕获地崩溃”,但 _files.py:50-56 仍在 import 阶段 import magic(仅用 except Exception),若该崩溃确实不可捕获,则本次改动并未真正消除导入期风险,仅 win32 被绕过。建议确认非 win32 是否同样需要惰性导入,或明确注释为何仅 win32 需要 skip。

总结

整体改动方向(懒加载 docker/magic 以避免无 Docker/libmagic 环境下导入崩溃)合理,但存在一个 Critical 的对外 API 破坏性变更(顶层 Container 等符号从真实类型退化为 None 占位)及若干测试稳定性与兼容性隐患,建议在合并前修复 Critical 项。

测试建议

  • 建议补充:在无 docker 包环境(pip uninstall docker)下运行 test_container_cli.py,验证 _ensure_docker_imported fixture 与 ping_ok=False 分支不会因真实 import docker 失败。
  • 建议补充:覆盖 detect_content_type_magic_module.from_buffer 抛异常时正确回退到字节签名检测的路径(当前测试未覆盖 from_buffer 失败分支)。

# Declaring them here eliminates F821 and ensures _exec_run_with_stdin
# gets a clear RuntimeError instead of NameError if called before init.
docker = None
Container = None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

模块级占位符遮蔽真实 Docker 类型,破坏对外 API 语义

模块顶层 Container = None 等占位符遮蔽了 docker.models.containers.Container 等真实类型,导入本模块得到 None 而非真实类型,外部依赖类型注解或 isinstance 校验的代码将静默失效。建议仅在 TYPE_CHECKING 下保留字符串类型别名,运行期不在顶层暴露同名占位变量。

…owing

Fix AI review Critical: module-level docker=None/Container=None shadowed
the TYPE_CHECKING type aliases, breaking external
`from ..._container_cli import Container`.

- Use TYPE_CHECKING guard for docker/Container type annotations (static only)
- Store runtime symbols in private _docker_mod/_docker_container_cls/
  _docker_consume_socket_output/_docker_demux_adaptor/_docker_frames_iter
- Update all runtime references to use private names
- Update test @patch targets to _docker_mod
- No public module-level names are shadowed
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已经了解了全貌。让我来撰写审查意见。

发现的问题

🚨 Critical

  • trpc_agent_sdk/code_executors/utils/_files.py:49-57:非 win32 平台模块级强制 import magic,未达到 PR 声明的“延迟导入”目标

    • 注释声称只在 detect_content_type() 首次调用时才 import,但在 if sys.platform != 'win32' 分支里直接 import magic 会在 _files 模块 import 时立即执行。在装了 python-magic 但 libmagic 原生库损坏/缺失的 Linux/macOS 环境下,import magic 本身可能抛异常(虽然这里用 except Exception 兜底),更关键的是若 magic 模块在导入阶段触发原生库加载副作用,仍会在 import trpc_agent_sdk 时发生,违背 PR 的初衷。建议非 win32 也走延迟导入,让首次 detect_content_type() 调用时才探测;HAS_MAGIC 可保留为模块级布尔但默认 False,或文档化其语义变化(见下条兼容性问题)。
  • trpc_agent_sdk/code_executors/utils/_files.py:57HAS_MAGIC 在 win32 上语义被破坏性变更

    • 改动前 HAS_MAGIC 在所有平台都反映 magic 是否可用;改动后 win32 上 HAS_MAGIC 恒为 False(即使实际安装可用)。仓库内虽无外部引用,但注释明确其为“Backward-compatible public alias”。若存在外部代码 from ..._files import HAS_MAGIC 依赖其值,win32 行为被静默改变。建议至少在变更说明中标注,或保留 win32 下也尝试探测(仅避开会段错误的导入路径)。

⚠️ Warning

  • trpc_agent_sdk/code_executors/container/_container_cli.py:148-153_init_docker_client_import_docker() 后立即判空抛错,改变了未安装 docker SDK 时的异常类型

    • 旧代码(docker 在顶部导入)会在未安装 SDK 时于 import 阶段直接 ImportError;新代码把 ImportError 吞掉(_import_dockerexcept ImportError: pass),改为运行时 RuntimeError。这通常更好,但 _import_docker 只捕获 ImportError,若 docker SDK import 过程中抛其它异常(如原生依赖问题)将直接逃逸,且 _docker_imported 因早退未置 True 导致后续每次重试。建议捕获 Exception 或确保异常路径也置 _docker_imported = True,避免重复重试与状态不一致。
  • trpc_agent_sdk/code_executors/container/_container_cli.py:39,75_docker_container_cls 被缓存但从未使用,属死代码

    • _import_docker 花成本导入并缓存 Container as _C_docker_container_cls,但全模块无任何引用(isinstance/类型检查均未用到)。若仅为对称可保留,否则建议删除以减少维护面与导入开销。
  • tests/code_executors/test_utils_files.py:332-353test_detect_content_type_with_magic 注入后未覆盖“首次延迟导入”路径

    • 测试直接把 _magic_checked 设为 True 并注入 mock,绕过了 detect_content_type 中真正的延迟导入分支(if ... not _magic_checked)。该分支(含加锁、import、except 回退)从未被执行,等于未覆盖。建议新增一条用例:保持 _magic_checked=False 并 patch import magic 成功,验证延迟导入与缓存逻辑。
  • tests/code_executors/container/test_container_cli.py:35-42:模块级 autouse fixture 调用 _import_docker(),在未装 docker SDK 的 CI 上污染模块全局缓存

    • _ensure_docker_imported 在模块作用域对所有用例生效并真实调用 _import_docker(),会将真实 docker SDK(若存在)加载进进程全局缓存 _docker_mod。后续 @patch("_docker_mod") 虽覆盖了引用,但真实 SDK 已被 import 触发副作用,且若 SDK 未安装则 _docker_mod 保持 None,仅靠 patch 兜底。这削弱了“不依赖真实 docker 即可运行测试”的隔离性。建议将 fixture 改为只在需要时 mock,或显式 patch _import_docker 而非真实调用。

💡 Suggestion

  • trpc_agent_sdk/code_executors/utils/_files.py:36-43_has_magic() 函数定义后无任何调用方
    • 作为新增的“向后兼容指示器”却未被仓库引用,属未使用代码。若确为对外 API 可保留并加文档,否则建议删除以降低维护负担。

总结

整体方向(延迟导入 docker/magic 以避免无依赖环境下 import 卡死)正确,但 _files.py 的非 win32 模块级 import magicHAS_MAGIC 语义变化存在兼容性与初衷不符的风险(Critical),docker 侧的异常处理早退与死代码属建议修复项。存在需修复的阻塞问题,建议处理 _files.py 的导入时机与 HAS_MAGIC 兼容性后再合入。

测试建议

  • 补充一条覆盖 detect_content_type 真实延迟导入分支(_magic_checked=False 时加锁 import 成功/失败两条路径)的用例。
  • 补充 docker SDK 未安装场景下 ContainerClient.__init__RuntimeError("Docker SDK is not available...") 的用例,以及 _import_docker 抛非 ImportError 异常时的行为用例。

# External code may reference it as ``from ..._files import HAS_MAGIC``.
# On non-win32, probe once at import time to preserve the original semantics
# (HAS_MAGIC reflects availability immediately, not deferred to first call).
if sys.platform != 'win32':

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

非 win32 模块级强制 import magic,未达成延迟导入目标

注释声称仅在 detect_content_type() 首次调用时才 import magic,但 if sys.platform != 'win32' 分支在模块 import 时即执行 import magic。若 magic 导入触发原生库加载副作用,仍会在 import trpc_agent_sdk 时发生,违背 PR 初衷。建议非 win32 也走延迟导入。

_magic_checked = True
except Exception:
_magic_checked = True
HAS_MAGIC = _magic_module is not None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HAS_MAGIC 在 win32 上语义被破坏性变更

改动后 win32 上 HAS_MAGIC 恒为 False(即使实际安装可用),而注释称其为向后兼容的公开别名。外部代码若依赖其值将被静默改变。建议在变更说明中标注,或保留 win32 下也尝试探测。

1. _files.py: remove module-level import magic on non-win32 entirely
   (was still triggering at import time). Now truly deferred to first
   detect_content_type() call. HAS_MAGIC defaults False, updated lazily.
2. Remove unused _has_magic() function (dead code per review)
3. _container_cli.py: remove unused _docker_container_cls
4. _container_cli.py: widen except ImportError to except Exception
   ensuring _docker_imported=True always set, preventing retry storms
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

现在我已经有了足够的上下文。让我来总结一下审查意见。

发现的问题

⚠️ Warning

  • trpc_agent_sdk/code_executors/container/_container_cli.py:317-319_exec_run_with_stdin 直接使用 _docker_frames_iter / _docker_demux_adaptor / _docker_consume_socket_output,但未调用 _import_docker()。当前能正常工作仅因 _init_docker_client()__init__ 中已触发导入,属于隐式时序依赖;一旦该路径在构造后被单独调用(或 import 部分失败被 except Exception: pass 吞掉,仅 _docker_mod 成功而 socket 符号为 None),会抛出 TypeError: 'NoneType' is not callable 而非清晰的 SDK 不可用错误。建议在方法入口加一个最小防护或断言这些符号非空,使失败语义与 _init_docker_client 的 guard 一致。

  • trpc_agent_sdk/code_executors/container/_container_cli.py:76-77_import_docker()except Exception: pass 静默吞掉所有导入异常(包括 docker 已安装但其子模块 API 变更、docker.utils.socket 导入名被改等真实编程错误),随后只笼统提示"Docker SDK is not available"。这会把真正的环境/版本 bug 伪装成"未安装",排障困难。建议至少 logger.debug(..., exc_info=True) 记录原始异常(与 _files.py 中 magic 导入失败的处理保持一致),再继续 fallback。

  • trpc_agent_sdk/code_executors/utils/_files.py:39,251-258HAS_MAGIC 语义由"导入时即反映 magic 是否可用"改为"模块加载时恒为 False,直到首次 detect_content_type() 调用后才可能变为 True"。这是对模块级公开常量的行为破坏性变更:任何在调用 detect_content_type 之前读取 HAS_MAGIC 判断能力的下游代码会得到 False_files 为下划线前缀的内部模块故风险有限,但 python-magic 仍是 pyproject 中的强依赖;建议确认无下游依赖该常量,或在文档注释中明确语义变化。

  • tests/code_executors/container/test_container_cli.py:36-42:新增的 _ensure_docker_imported autouse fixture 在模块加载时调用真实 _import_docker(),若 CI 环境未安装 docker 包则 _docker_mod 保持 None,后续 @patch("..._docker_mod") 虽可 patch 占位 None,但被 patch 后的 mock 需自行设置 .errors.DockerException 等属性——测试本身已设置,尚可;但该 fixture 使测试隐式依赖"docker 包已安装"。若 CI 存在无 docker 的矩阵,建议在 fixture 中对 _docker_mod is None 的情况补一个 MagicMock 占位,避免 patch None 时的边界差异。

💡 Suggestion

总结

本 PR 将 docker SDK 与 python-magic 改为惰性导入以解决无 Docker/libmagic 环境下导入崩溃的问题,整体方向正确,双检锁实现合理;不存在必须修复的 Critical 问题。主要风险在于 _import_docker 的静默异常吞没与 _exec_run_with_stdin 对惰性符号的隐式依赖,建议补日志与防护以提升可诊断性。

测试建议

  • 建议补充一条用例:模拟 docker 包已安装但 docker.utils.socket 某符号导入失败(即 _docker_mod 非 None 而 _docker_frames_iter 为 None),断言 _exec_run_with_stdin 给出可预期的错误而非 TypeError
  • 建议补一条 detect_content_type 在 magic 导入失败(_magic_module 为 None)时正确回退到字节签名检测的测试,覆盖 _files.py:259-261 的 fallback 路径。

Address latest AI review warnings (no Critical this round):
1. Add logger.debug(exc_info=True) for _import_docker except path
   to aid debugging when docker import fails for non-obvious reasons
2. Move import threading to standard library group at top of file
3. Remove duplicate import threading below the third-party imports
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

现在我来总结一下我的分析。主要观察结果如下:

  1. docker 依赖现在是必需的,但会被延迟导入 —— pyproject.toml 仍然将 dockerpython-magic 声明为必需依赖项。延迟导入(lazy import)纯粹是为了避免在未安装 Docker 的环境中导入时卡住(hang)。鉴于它们是必需的依赖项,这是可以接受的。

  2. _import_docker 捕获了广泛的 Exception —— 如果没有安装 docker 包,_docker_mod 将保持 None,随后调用 _init_docker_client 会引发 RuntimeError("Docker SDK is not available...")。这是没问题的。

  3. 测试夹具(Test fixture)scope="module" 调用 _import_docker() —— 这会在真实环境中运行,尝试导入真实的 docker。在 CI 环境中,如果安装了 docker 包,_docker_mod 就会变成真实的 docker 模块。然后 @patch("..._docker_mod") 会在每个测试中进行打补丁(patch)。但是模块级的 _ensure_docker_imported 夹具将 _docker_imported 设置为 True 并导入了真实的 docker。由于每个测试都在打补丁 _docker_mod,这工作正常。

    然而 —— 关于 HAS_MAGIC 缓存存在一个测试污染问题:测试 test_detect_content_type_with_magic 设置了 _f._magic_checked = True_f.HAS_MAGIC = True 并保存/恢复了原始值。但原本的 _magic_checked 可能已经是 True 且来自真实环境的 _magic_module 可能已被设置。恢复操作处理了这种情况。没问题。

  4. test_detect_content_type_with_magic 中的真实污染问题:它在 win32 上被跳过。在非 win32 环境下,如果确实安装了 python-magic,模块级变量会被此测试修改,但它会被恢复。没问题。然而,HAS_MAGIC —— 该测试将其设置为 True 然后恢复。没问题。

  5. 关于 magic 的真正担忧:在延迟导入路径中,如果 _magic_module 真实且 from_buffer 引发异常,它会回退到字节签名。没问题。但这意味着 HAS_MAGIC 仍然为 True,即使 from_buffer 失败。这是一个小问题。

  6. 并发_import_docker 中的双重检查锁定(double-checked locking)—— 在 Python 中,模块属性赋值在 GIL 下是原子的。双重检查读取 if _docker_imported: return。这没问题。

  7. _init_docker_client 抛出 RuntimeError 的路径 —— 之前直接使用 docker.errors.DockerException;现在使用 _docker_mod.errors.DockerException。由于此处 _docker_mod 保证非 None(有防护措施),所以没问题。

  8. 潜在的真正问题:测试文件导入了 _import_docker(下划线前缀 = 私有)—— 这是一个测试中的代码异味(code smell),但可以接受。

  9. _ensure_docker_imported 夹具具有 scope="module"autouse=True —— 它调用 _import_docker(),其副作用是在测试环境中将 _docker_imported 设置为 True 并导入真实的 docker。如果在测试环境中未安装 docker,_docker_mod 保持为 None,且 @patch("..._docker_mod") 仍然有效(打补丁 None 值)。所以测试可以通过。很好。

  10. 真正的问题 —— test_container_ws_runtime.py 不在 diff 范围内,但它从 _container_cli 导入。不属于 diff 范围。

  11. _import_docker 中的线程问题 —— 存在 TOCTOU(竞态条件):锁外进行的第一次检查读取 _docker_imported。如果两个线程同时看到 False,两者都会获取锁(串行执行),第二个线程看到 True 并返回。功能上正确。但模块级变量 _docker_mod 在锁外被读取 —— 在 _init_docker_client 中,它在 _import_docker() 返回之后才读取 _docker_mod,所以安全性没问题。因为 _import_docker() 只有在 _docker_imported 为 True(已完全设置)之后才会返回,所以 _docker_mod 保证已被赋值。没问题。

  12. _magic_checked / HAS_MAGIC 缓存 —— 在 detect_content_type 中,HAS_MAGIC 在导入成功后变为 True。但由于缓存,如果第一次调用时 python-magic 不可用,HAS_MAGIC 将永久保持 False,且后续调用(即使安装了 magic)也不会重试。对于单进程使用而言,这是可接受的缓存行为。

让我更仔细地看一下一个具体问题 —— _import_dockerexcept Exception 吞掉了所有异常,包括 KeyboardInterrupt 吗?不,Exception 不包括 KeyboardInterrupt。没问题。

现在,真正值得关注的问题:test_detect_content_type_with_magic 中,测试将 _magic_module 设置为一个 mock,但 detect_content_type 内部的延迟导入受到 not _magic_checked 的控制,而测试将其设置为 True,因此它不会重新导入。它直接使用注入的 _magic_module。这是有效的。但这很脆弱。

让我确认一下没有其他值得标记的真正问题。差异(diff)主要是关于延迟导入重构 + 测试适配。这很干净。让我写下审查结果。

发现的问题

🚨 Critical

(无)

⚠️ Warning

  • tests/code_executors/test_utils_files.py:343-353:测试通过直接改写模块私有全局变量 _magic_module/_magic_checked/HAS_MAGIC 来注入 mock,并在 finally 中恢复。该测试未加 @pytest.mark.usefixtures 隔离,且 detect_content_type 内部用 _magic_checked 缓存决定是否重新导入;一旦本测试与其它测试在并行/同进程中交错,或恢复前抛出非预期异常,全局缓存会被污染影响后续 detect_content_type 行为判断。建议用 monkeypatch.setattr 或独立 fixture 统一注入与还原,避免裸操作模块全局态。

  • trpc_agent_sdk/code_executors/utils/_files.py:252-261_magic_checked 一旦置 True(含导入失败)便永久缓存,后续即使补装 python-magic 也不会重试;且 HAS_MAGICfrom_buffer 运行期失败时仍为 True,外部若仅依赖 HAS_MAGIC 判断可用性会被误导。属边界/语义不精确,建议在 from_buffer 失败时考虑回退语义或在文档/注释中明确 HAS_MAGIC 仅表示"导入成功"而非"调用必成功"。

💡 Suggestion

  • trpc_agent_sdk/code_executors/container/_container_cli.py:30-31:测试侧 from ... import _import_docker 依赖带下划线的私有符号以支撑 fixture;可考虑在 _container_cli 暴露一个稳定的内部入口(或让测试直接调用 _init_docker_client 路径),减少对私有名 的耦合,便于后续重构不破坏测试。

总结

本次 PR 将 dockerpython-magic 改为延迟导入以规避无 Docker/libmagic 环境下的导入卡死/崩溃,整体逻辑正确,错误路径与 @patch 测试适配合理,无阻塞问题。主要需关注测试对模块私有全局态的注入方式与 magic 缓存语义,建议按 Warning 项收敛。

测试建议

  • 补充一条"docker SDK 未安装时 _init_docker_client 抛出 RuntimeError("Docker SDK is not available...")"的用例(可 monkeypatch _docker_modNone 验证),覆盖新增的 None 守卫分支。
  • detect_content_type 增加 from_buffer 运行期失败时回退到字节签名检测的用例,验证 fallback 路径而非仅 mock 成功路径。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: top-level import docker in _container_cli.py causes hang/segfault when Docker is unavailable — LlmAgent import blocked

2 participants