Skip to content

fix: 工程化收敛并移除 ASYNC230/ASYNC240 忽略#5729

Merged
zouyonghe merged 13 commits intoAstrBotDevs:masterfrom
zouyonghe:master
Mar 4, 2026
Merged

fix: 工程化收敛并移除 ASYNC230/ASYNC240 忽略#5729
zouyonghe merged 13 commits intoAstrBotDevs:masterfrom
zouyonghe:master

Conversation

@zouyonghe
Copy link
Member

整体描述

本 PR 以“工程化质量收敛”为目标,完成了从基线约束、CI 门禁、到代码级治理的全流程修复。核心结果是:

  • 在不新增忽略规则的前提下,清理并落地 Ruff 规则治理。
  • 将异步链路中的阻塞文件/路径调用系统性替换为非阻塞方案。
  • 补齐并统一 PR 前质量门禁,确保变更可持续维护。

步骤说明

  1. 提升工程基线与发布前门禁
  • 对齐 Python 3.12 运行基线。
  • 调整 pre-commit/CodeQL 与 CI 配置,补充 dashboard typecheck 与 release 前质量校验。
  • 强化依赖安装策略与安全约束(锁定安装、移除高风险安装方式)。
  1. 制定 Ruff 规则迁移策略
  • 将暂不一次性落地的规则改为显式“渐进迁移 TODO”,避免“静默忽略”导致的技术债失控。
  • 保持规则收敛路径可见,便于后续分阶段推进。
  1. 修复第一轮 Ruff 问题(不新增忽略)
  • 完成 UP/ASYNC 相关问题修复,包括类型语法升级、导入与风格清理、参数命名一致性调整。
  • 对外部调用兼容点补充向后兼容处理,避免关键调用链断裂。
  1. 彻底移除 ASYNC230 / ASYNC240 忽略并完成代码治理
  • 删除 pyproject.tomlASYNC230ASYNC240 忽略项。
  • 全仓修复异步函数中的阻塞 open() / os.path.* / Path.* 调用:
    • 文件读写:改为 asyncio.to_thread + Path.read_* / write_* 或线程封装函数。
    • 第三方 SDK 需要文件句柄的场景:使用线程函数封装上传逻辑,保留原有行为。
    • 分片合并、备份导入导出、平台媒体上传、TTS/STT/多模态等链路统一清理。
  1. 全量验证
  • uv run ruff format .
  • uv run ruff check .
  • make pr-test-neo(pytest + smoke test)

影响与收益

  • 移除了高风险“规则忽略”依赖,质量标准回归到代码本身。
  • 异步链路阻塞点显著减少,降低事件循环被文件 I/O 卡住的风险。
  • CI 与本地质量门禁一致,后续变更更容易稳定演进。

@auto-assign auto-assign bot requested review from Soulter and anka-afk March 4, 2026 02:18
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 4, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Sorry @zouyonghe, your pull request is larger than the review limit of 150000 diff characters

@github-advanced-security
Copy link
Contributor

This pull request sets up GitHub code scanning for this repository. Once the scans have completed and the checks have passed, the analysis results for this pull request branch will appear on this overview. Once you merge this pull request, the 'Security' tab will show more code scanning analysis results (for example, for the default branch). Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results. For more information about GitHub code scanning, check out the documentation.

@dosubot dosubot bot added the area:core The bug / feature is about astrbot's core, backend label Mar 4, 2026
@dosubot
Copy link

dosubot bot commented Mar 4, 2026

Related Documentation

Checked 1 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on improving the engineering quality of the project by converging on coding standards, enhancing asynchronous operations, and enforcing consistent quality checks. It addresses technical debt by removing rule exceptions and upgrading dependencies, leading to a more stable and maintainable codebase.

Highlights

  • Ruff Rule Governance: Cleaned up and implemented Ruff rules without adding new ignore rules, enhancing code quality.
  • Asynchronous Link Improvements: Systematically replaced blocking file/path calls in asynchronous links with non-blocking solutions, reducing I/O bottlenecks.
  • Quality Gate Enforcement: Unified and completed pre-PR quality gates to ensure consistent and maintainable code changes.
  • Python 3.12 Baseline: Aligned the project to use Python 3.12 as the running baseline.
Changelog
  • .pre-commit-config.yaml
    • Updated Ruff pre-commit hook to v0.15.1 and upgraded pyupgrade args to py312-plus.
  • Dockerfile
    • Removed gnupg and the nodesource.com installation method, directly installing nodejs and npm via apt.
  • README.md
    • Updated the Python version badge to reflect Python 3.12+ compatibility.
  • README_fr.md
    • Updated the Python version badge to reflect Python 3.12+ compatibility.
  • README_ja.md
    • Updated the Python version badge to reflect Python 3.12+ compatibility.
  • README_ru.md
    • Updated the Python version badge to reflect Python 3.12+ compatibility.
  • README_zh-TW.md
    • Updated the Python version badge to reflect Python 3.12+ compatibility.
  • README_zh.md
    • Updated the Python version badge to reflect Python 3.12+ compatibility.
  • astrbot/cli/utils/plugin.py
    • Changed PluginStatus enum to inherit from StrEnum instead of Enum.
  • astrbot/core/agent/agent.py
    • Removed Generic type from Agent class definition.
  • astrbot/core/agent/handoff.py
    • Removed Generic type from HandoffTool class definition.
  • astrbot/core/agent/hooks.py
    • Removed Generic type from BaseAgentRunHooks class definition.
  • astrbot/core/agent/mcp_client.py
    • Removed Generic type from MCPTool class definition, replaced asyncio.TimeoutError with TimeoutError, and used asyncio.to_thread for os.path.exists.
  • astrbot/core/agent/runners/base.py
    • Removed Generic type from BaseAgentRunner class definition.
  • astrbot/core/agent/runners/coze/coze_agent_runner.py
    • Removed conditional import of override based on Python version and changed timeout to timeout_seconds in _execute_coze_request.
  • astrbot/core/agent/runners/coze/coze_api_client.py
    • Replaced asyncio.TimeoutError with TimeoutError, added Path import, used asyncio.to_thread for Path.read_bytes, and renamed timeout to timeout_seconds.
  • astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py
    • Removed conditional import of override based on Python version.
  • astrbot/core/agent/runners/deerflow/deerflow_agent_runner.py
    • Removed conditional import of override based on Python version, replaced asyncio.TimeoutError with TimeoutError, and renamed timeout to timeout_seconds.
  • astrbot/core/agent/runners/deerflow/deerflow_api_client.py
    • Renamed timeout to timeout_seconds and updated timeout parameters in create_thread and stream_run methods.
  • astrbot/core/agent/runners/dify/dify_agent_runner.py
    • Removed conditional import of override based on Python version and renamed timeout to timeout_seconds.
  • astrbot/core/agent/runners/dify/dify_api_client.py
    • Renamed timeout to timeout_seconds in chat_messages and workflow_run methods, and used asyncio.to_thread for Path(file_path).read_bytes.
  • astrbot/core/agent/runners/tool_loop_agent_runner.py
    • Removed conditional import of override based on Python version.
  • astrbot/core/agent/tool.py
    • Removed Generic type from FunctionTool class definition.
  • astrbot/core/agent/tool_executor.py
    • Removed Generic type from BaseFunctionToolExecutor class definition.
  • astrbot/core/astr_agent_run_util.py
    • Used asyncio.to_thread for Path(audio_path).read_bytes in _simulated_stream_tts.
  • astrbot/core/astr_agent_tool_exec.py
    • Replaced asyncio.TimeoutError with TimeoutError in _execute_local.
  • astrbot/core/astr_main_agent_resources.py
    • Used asyncio.to_thread for os.path.exists in _resolve_path_from_sandbox.
  • astrbot/core/backup/exporter.py
    • Added asyncio import, used asyncio.to_thread for Path operations, and updated datetime usage to use UTC.
  • astrbot/core/backup/importer.py
    • Added asyncio import, used asyncio.to_thread for Path operations, and updated datetime usage to use UTC.
  • astrbot/core/computer/booters/bay_manager.py
    • Renamed timeout to timeout_seconds in wait_healthy.
  • astrbot/core/computer/booters/boxlite.py
    • Replaced asyncio.TimeoutError with TimeoutError and used asyncio.to_thread for Path(path).read_bytes.
  • astrbot/core/computer/booters/local.py
    • Renamed timeout to timeout_seconds in exec methods.
  • astrbot/core/computer/booters/shipyard_neo.py
    • Added asyncio and Path imports, renamed timeout to timeout_seconds, and used asyncio.to_thread for Path(path).read_bytes.
  • astrbot/core/computer/computer_client.py
    • Added asyncio import and used asyncio.to_thread for Path operations.
  • astrbot/core/computer/olayer/browser.py
    • Renamed timeout to timeout_seconds in exec and exec_batch methods.
  • astrbot/core/computer/olayer/python.py
    • Renamed timeout to timeout_seconds in exec method.
  • astrbot/core/computer/olayer/shell.py
    • Renamed timeout to timeout_seconds in exec method.
  • astrbot/core/computer/tools/browser.py
    • Renamed timeout to timeout_seconds in call methods and handled legacy timeout.
  • astrbot/core/computer/tools/fs.py
    • Added asyncio import and used asyncio.to_thread for os.path.exists and os.path.isfile.
  • astrbot/core/cron/manager.py
    • Updated datetime usage to use UTC.
  • astrbot/core/db/migration/helper.py
    • Added asyncio import and used asyncio.to_thread for os.path.exists.
  • astrbot/core/db/migration/migra_3_to_4.py
    • Updated datetime usage to use UTC.
  • astrbot/core/db/po.py
    • Updated datetime usage to use UTC.
  • astrbot/core/db/sqlite.py
    • Updated datetime usage to use UTC.
  • astrbot/core/file_token_service.py
    • Handled legacy timeout and used asyncio.to_thread for os.path.exists.
  • astrbot/core/knowledge_base/models.py
    • Updated datetime usage to use UTC.
  • astrbot/core/message/components.py
    • Added StrEnum import, used asyncio.to_thread for os.path.exists and Path operations.
  • astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/platform/sources/discord/client.py
    • Removed conditional import of override based on Python version.
  • astrbot/core/platform/sources/discord/discord_platform_adapter.py
    • Removed conditional import of override based on Python version.
  • astrbot/core/platform/sources/kook/kook_adapter.py
    • Replaced asyncio.TimeoutError with TimeoutError.
  • astrbot/core/platform/sources/kook/kook_client.py
    • Replaced asyncio.TimeoutError with TimeoutError and used asyncio.to_thread for os.path.exists and Path operations.
  • astrbot/core/platform/sources/lark/lark_adapter.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/platform/sources/lark/lark_event.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/platform/sources/line/line_event.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/platform/sources/misskey/misskey_adapter.py
    • Used asyncio.to_thread for os.path.exists.
  • astrbot/core/platform/sources/misskey/misskey_api.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
    • Used asyncio.to_thread for os.path.exists.
  • astrbot/core/platform/sources/telegram/tg_adapter.py
    • Removed conditional import of override based on Python version.
  • astrbot/core/platform/sources/webchat/message_parts_helper.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/platform/sources/wecom/wecom_adapter.py
    • Removed conditional import of override based on Python version and used asyncio.to_thread for Path operations.
  • astrbot/core/platform/sources/wecom/wecom_event.py
    • Used asyncio.to_thread for os.path.exists.
  • astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py
    • Replaced asyncio.TimeoutError with TimeoutError and used asyncio.to_thread for Path operations.
  • astrbot/core/platform/sources/wecom_ai_bot/wecomai_webhook.py
    • Added asyncio import and used asyncio.to_thread for Path operations.
  • astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py
    • Removed conditional import of override based on Python version and used asyncio.to_thread for Path operations.
  • astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/provider/entities.py
    • Added asyncio import and used asyncio.to_thread for Path operations.
  • astrbot/core/provider/func_tool_manager.py
    • Replaced asyncio.TimeoutError with TimeoutError and added asyncio import and used asyncio.to_thread for Path operations.
  • astrbot/core/provider/provider.py
    • Handled legacy timeout and used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/anthropic_source.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/dashscope_tts.py
    • Replaced asyncio.TimeoutError with TimeoutError and used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/edge_tts_source.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/fishaudio_tts_api_source.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/gemini_source.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/genie_tts.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/gsv_selfhosted_source.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/gsvi_tts_source.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/minimax_tts_api_source.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/openai_source.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/openai_tts_api_source.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/sensevoice_selfhosted_source.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/volcengine_tts.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/whisper_api_source.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/whisper_selfhosted_source.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/provider/sources/xinference_stt_provider.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/skills/neo_skill_sync.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/skills/skill_manager.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/star/star_handler.py
    • Refactored StarHandlerRegistry to use a TypeVar with an upper bound.
  • astrbot/core/star/star_manager.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/utils/datetime_utils.py
    • Replaced timezone.utc with UTC.
  • astrbot/core/utils/io.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/utils/media_utils.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/core/utils/session_waiter.py
    • Renamed timeout to timeout_seconds.
  • astrbot/core/utils/temp_dir_cleaner.py
    • Replaced asyncio.TimeoutError with TimeoutError.
  • astrbot/core/utils/tencent_record_helper.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/dashboard/routes/api_key.py
    • Updated datetime usage to use UTC.
  • astrbot/dashboard/routes/backup.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/dashboard/routes/chat.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/dashboard/routes/config.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/dashboard/routes/knowledge_base.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/dashboard/routes/live_chat.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/dashboard/routes/open_api.py
    • Replaced asyncio.TimeoutError with TimeoutError.
  • astrbot/dashboard/routes/plugin.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/dashboard/routes/skills.py
    • Used asyncio.to_thread for Path operations.
  • astrbot/dashboard/routes/stat.py
    • Used asyncio.to_thread for Path operations.
  • main.py
    • Used asyncio.to_thread for Path operations.
  • pyproject.toml
    • Set target-version to py312 and removed ASYNC230/ASYNC240 ignore rules.
  • tests/test_skill_manager_sandbox_cache.py
    • Add pytest import and fix test.
Ignored Files
  • Ignored by pattern: .github/workflows/** (5)
    • .github/workflows/codeql.yml
    • .github/workflows/coverage_test.yml
    • .github/workflows/dashboard_ci.yml
    • .github/workflows/docker-image.yml
    • .github/workflows/release.yml
Activity
  • Updated Ruff pre-commit hook to v0.15.1.
  • Upgraded pyupgrade args to py312-plus.
  • Removed gnupg and the nodesource.com installation method, directly installing nodejs and npm via apt.
  • Updated the Python version badge to reflect Python 3.12+ compatibility.
  • Changed PluginStatus enum to inherit from StrEnum instead of Enum.
  • Removed Generic type from Agent, HandoffTool, and BaseAgentRunHooks class definitions.
  • Replaced asyncio.TimeoutError with TimeoutError and used asyncio.to_thread for os.path.exists and Path operations.
  • Removed conditional import of override based on Python version and changed timeout to timeout_seconds in _execute_coze_request.
  • Renamed timeout to timeout_seconds and updated timeout parameters in create_thread and stream_run methods.
  • Removed conditional import of override based on Python version and renamed timeout to timeout_seconds.
  • Refactored StarHandlerRegistry to use a TypeVar with an upper bound.
  • Set target-version to py312 and removed ASYNC230/ASYNC240 ignore rules.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

这个 PR 做了一次大规模的工程质量改进,目标非常明确且完成度很高。

  • Python 3.12 基线对齐:代码库全面升级到了 Python 3.12,并使用了 PEP 695 等新特性,非常棒。
  • 异步代码治理:系统性地将异步函数中的阻塞式 I/O 调用(如 open, os.path.*)替换为 asyncio.to_thread,彻底解决了 ASYNC230/ASYNC240 的 linting 问题,显著提升了异步代码的健壮性。
  • 代码一致性:在多处 API 中将 timeout 参数重命名为 timeout_seconds,并细心地增加了向后兼容处理,这体现了很好的工程素养。
  • 配置和依赖更新:相关的 CI 和 pre-commit 配置也得到了同步更新,确保了工程化链路的完整性。

整体而言,这是一次非常有价值的重构,代码质量和可维护性都得到了极大的提升。我只发现了一个小的遗漏点,详见具体评论。

Note: Security Review did not run due to the size of the PR.

Comment on lines 204 to 207
def file_to_base64(file_path: str) -> str:
with open(file_path, "rb") as f:
data_bytes = f.read()
base64_str = base64.b64encode(data_bytes).decode()
data_bytes = Path(file_path).read_bytes()
base64_str = base64.b64encode(data_bytes).decode()
return "base64://" + base64_str
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

这个函数中的 Path(file_path).read_bytes() 是一个阻塞式 I/O 操作。由于此函数是从异步上下文中同步调用的(例如 Image.convert_to_base64),它会阻塞事件循环,这违反了 ASYNC230 规则。

建议将此函数修改为异步函数,并使用 asyncio.to_thread 来异步读取文件。请注意,修改后需要更新所有调用方以使用 await

Suggested change
def file_to_base64(file_path: str) -> str:
with open(file_path, "rb") as f:
data_bytes = f.read()
base64_str = base64.b64encode(data_bytes).decode()
data_bytes = Path(file_path).read_bytes()
base64_str = base64.b64encode(data_bytes).decode()
return "base64://" + base64_str
async def file_to_base64(file_path: str) -> str:
data_bytes = await asyncio.to_thread(Path(file_path).read_bytes)
base64_str = base64.b64encode(data_bytes).decode()
return "base64://" + base64_str

@zouyonghe zouyonghe requested a review from advent259141 March 4, 2026 02:27
@zouyonghe zouyonghe merged commit a9c16fe into AstrBotDevs:master Mar 4, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant