Skip to content

refactor: 容器注解补 Any 参数 + 移除 reportMissingTypeArgument 全局抑制 - #3

Merged
Windsland52 merged 1 commit into
Windsland52:mainfrom
a16797:codex/strict-maaframework-types
Jul 1, 2026
Merged

refactor: 容器注解补 Any 参数 + 移除 reportMissingTypeArgument 全局抑制#3
Windsland52 merged 1 commit into
Windsland52:mainfrom
a16797:codex/strict-maaframework-types

Conversation

@a16797

@a16797 a16797 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • keep typeCheckingMode = strict enabled
  • keep reportUnknown* = none because MaaFramework callbacks, Qt signals, and JSON settings are dynamic boundaries in this app
  • remove the broad reportMissingTypeArgument = none suppression
  • add only minimal dict[str, Any] / list[...] / set[...] annotations at dynamic payload boundaries so bare containers are explicit without introducing project-wide wrapper types
  • correct the MaaFramework spelling in the pyright comment (MaaFw, imported as maa)

Validation

  • uv run pyright
  • uv run ruff check .
  • uv run pytest

@sourcery-ai

sourcery-ai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Reviewer's Guide

本次 PR 在保持 pyright 严格模式(strict mode)的前提下,通过引入共享的 JSON/MeasureState 类型别名、添加运行时类型守卫/类型转换,以及清理回调和 MaaFramework 相关类型标注,收紧整个应用中的 JSON 和动态状态类型,从而让严格模式下的 pyright 在不依赖大范围 suppress 的情况下顺利通过。

File-Level Changes

Change Details Files
引入用于 JSON 对象和测量状态的共享类型别名,并在处理时间线、地图、设置、更新器响应、农场流水线以及测量 UI 的代码中统一使用。
  • 新增 aao.types 模块,定义用于共享使用的 JsonObject 类型别名和 MeasureState TypedDict。
  • 更新地图/时间线加载器、农场流水线连接、设置加载/保存、更新器 API 响应,以及测量 worker/overlay/api server/app 等处,统一使用 JsonObject 和 MeasureState,而非裸 dict。
  • 调整多处函数的参数和返回值,使其接受或返回 JsonObject/MeasureState,包括 ExecuteTimeline、按键动作、农场 worker 的 exec_param,以及几何/视图变换器等。
aao/types.py
custom/action/executor.py
custom/action/key_press.py
aao/resources/updater.py
aao/ui/settings_page.py
aao/core/geometry/map_loader.py
aao/core/geometry/view.py
aao/measure/worker.py
aao/measure/api_server.py
aao/measure/overlay.py
aao/ui/farm_worker.py
aao/app.py
通过显式的运行时类型检查、类型转换以及更安全的默认值强化 JSON 和设置处理,使严格模式下 pyright 的 unknown-type 检查通过,并能优雅地处理无效数据。
  • 在对 json.loads 结果以及设置结构进行处理前,增加 Any/cast 的使用和 isinstance 检查,确认其为 dict 或 list。
  • 校验时间线和地图 JSON 文件是否为带有预期字段的对象;如不符合,记录日志并及早失败。
  • 通过 _to_int 等辅助函数以及专门的 default_factory 帮助器,规范候选项/动作列表、几何数组、窗口跟随偏移以及其他杂项数值字段。
  • 确保浮动窗口状态、农场/时间线的可折叠区块状态以及 JSONC 辅助函数返回类型良好的 dict/list,而非任意 Any。
custom/action/executor.py
aao/timeline/model.py
aao/ui/floating_state.py
aao/core/avatar.py
aao/resources/updater.py
aao/ui/settings_page.py
aao/core/geometry/map_loader.py
aao/ui/window_snap.py
custom/reco/click_stage.py
aao/ui/map_picker.py
aao/utils/jsonc.py
aao/core/timing/tick.py
aao/ui/farm_page.py
aao/ui/farm_worker.py
aao/timeline/editor_window.py
优化回调和 Qt 信号连接的类型,使其严格类型化,并避免使用 pyright 无法推断参数类型的匿名 lambda。
  • 用带显式类型标注的命名内部函数替换连接到 Qt 信号的内联 lambda。
  • 明确 _selected_window 的返回类型为 DesktopWindow,并为 _windows 列表添加相应类型。
  • 在处理进度和可折叠状态时,对设置 dict 进行类型转换后再索引,从而在 UI 逻辑中保持严格类型。
aao/ui/settings_page.py
aao/ui/calibration_page.py
aao/timeline/editor_window.py
调整 MaaFramework 集成以及项目配置,在保持 pyright 严格模式的同时,只抑制 MaaFw 缺失类型 stub,并修复一个导入。
  • 更新 pyproject.toml 中的 pyright 配置,保留 typeCheckingMode=strict,仅对 MaaFw 设置 reportMissingTypeStubs="none",移除宽泛的 reportUnknown*reportMissingTypeArgument 抑制项。
  • 修复 NotificationType 的导入,将其从 maa.context 改为来自 maa.event_sink,与上游 MaaFramework API 保持一致。
pyproject.toml
custom/outcome.py
收紧日志和 numpy 相关类型,以满足严格模式下的 pyright 要求,同时保持运行时行为不变。
  • sys.stdout 视为 TextIOWrapper,安全地重新配置,并在传入 loguru sink 时进行类型转换,以避免 Any 泄漏。
  • 重构负成本检测,改为通过索引访问和类型转换遍历 numpy 数组,使数组元素类型对 pyright 明确可见。
aao/utils/logger.py
aao/core/timing/tick.py

Tips and commands

Interacting with Sourcery

  • 触发新评审: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的评审评论。
  • 从评审评论生成 GitHub issue: 回复 Sourcery 的评审评论,要求其从该评论创建 issue。你也可以在评审评论中回复 @sourcery-ai issue 来从该评论创建 issue。
  • 生成 pull request 标题: 在 pull request 标题中任何位置写上 @sourcery-ai,即可随时生成标题。也可以在 pull request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 总结: 在 pull request 正文的任意位置写上 @sourcery-ai summary,即可在该位置生成 PR 总结。也可以在 pull request 中评论 @sourcery-ai summary 来(重新)生成总结。
  • 生成 reviewer's guide: 在 pull request 中评论 @sourcery-ai guide,即可在任何时候(重新)生成 reviewer's guide。
  • 解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,将所有 Sourcery 评论标记为已解决。如果你已经处理完所有评论且不希望再看到它们,这非常有用。
  • 关闭所有 Sourcery 评审: 在 pull request 中评论 @sourcery-ai dismiss,关闭所有现有的 Sourcery 评审。特别适用于你希望从一次全新的评审开始——别忘了再评论 @sourcery-ai review 以触发新评审!

Customizing Your Experience

访问你的 dashboard 来:

  • 启用或禁用评审功能,例如 Sourcery 自动生成的 pull request 总结、reviewer's guide 等。
  • 更改评审语言。
  • 添加、移除或编辑自定义评审指令。
  • 调整其他评审设置。

Getting Help

Original review guide in English

Reviewer's Guide

This PR keeps pyright in strict mode while tightening JSON and dynamic-state typing across the app by introducing shared JSON/MeasureState aliases, adding runtime type guards/casts, and cleaning up callback and MaaFramework-related typing so strict pyright passes without broad suppressions.

File-Level Changes

Change Details Files
Introduce shared typing aliases for JSON objects and measurement state and propagate them through code handling timelines, maps, settings, updater responses, farm pipelines, and measurement UI.
  • Add aao.types module defining JsonObject alias and MeasureState TypedDict for shared use.
  • Update map/timeline loaders, farm pipeline wiring, settings load/save, updater API responses, and measurement worker/overlay/api server/app to use JsonObject and MeasureState instead of bare dicts.
  • Adjust various functions to return or accept JsonObject/MeasureState, including ExecuteTimeline, key press actions, farm worker exec_param, and geometry/view transformers.
aao/types.py
custom/action/executor.py
custom/action/key_press.py
aao/resources/updater.py
aao/ui/settings_page.py
aao/core/geometry/map_loader.py
aao/core/geometry/view.py
aao/measure/worker.py
aao/measure/api_server.py
aao/measure/overlay.py
aao/ui/farm_worker.py
aao/app.py
Harden JSON and settings handling with explicit runtime type checks, casting, and safer defaults so strict pyright’s unknown-type checks pass and invalid data is handled gracefully.
  • Add Any/cast usage and isinstance checks around json.loads results and settings structures before treating them as dicts or lists.
  • Validate that timeline and map JSON files are objects with expected fields; log and fail early if not.
  • Normalize candidate/action lists, geometry arrays, window follow offsets, and miscellaneous numeric fields via helper functions like _to_int and dedicated default_factory helpers.
  • Ensure floating window state, farm/timeline collapsible section state, and JSONC helpers return well-typed dicts/lists instead of arbitrary Any.
custom/action/executor.py
aao/timeline/model.py
aao/ui/floating_state.py
aao/core/avatar.py
aao/resources/updater.py
aao/ui/settings_page.py
aao/core/geometry/map_loader.py
aao/ui/window_snap.py
custom/reco/click_stage.py
aao/ui/map_picker.py
aao/utils/jsonc.py
aao/core/timing/tick.py
aao/ui/farm_page.py
aao/ui/farm_worker.py
aao/timeline/editor_window.py
Refine callback and Qt signal wiring to be strictly typed and avoid anonymous lambdas where pyright can’t infer parameter types.
  • Replace inline lambdas connected to Qt signals with named inner functions that have explicit type annotations.
  • Clarify _selected_window return type to DesktopWindow and type the _windows list accordingly.
  • Adjust progress and collapsible state handlers to cast settings dicts before indexing, keeping strict typing through UI logic.
aao/ui/settings_page.py
aao/ui/calibration_page.py
aao/timeline/editor_window.py
Adjust MaaFramework integration and project configuration to keep pyright strict while only suppressing missing MaaFw type stubs and fixing an import.
  • Update pyproject.toml pyright config to retain typeCheckingMode=strict and only set reportMissingTypeStubs="none" for MaaFw, removing broad reportUnknown* and reportMissingTypeArgument suppressions.
  • Fix NotificationType import to come from maa.event_sink instead of maa.context, matching upstream MaaFramework APIs.
pyproject.toml
custom/outcome.py
Tighten logging and numpy-related typing to satisfy strict pyright while keeping runtime behavior unchanged.
  • Treat sys.stdout as a TextIOWrapper, reconfigure it safely, and cast it when passing into loguru sinks to avoid Any leakage.
  • Refactor negative-cost detection to iterate over numpy arrays via indexed access and casts, making array element types explicit for pyright.
aao/utils/logger.py
aao/core/timing/tick.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - 我发现了两个问题,并留下了一些整体性的反馈:

  • floating_state.pywindow_snap.py 中引入的临时 _to_int 辅助函数是相同的;可以考虑把这类逻辑(以及类似的 JSON 数值规范化逻辑)集中到一个共享工具中,以避免重复并保持行为一致。
  • 既然已经引入了 JsonObjectMeasureState,你可能想再扫描一下剩余的动态 JSON dict[str, Any] 用法,并将它们统一替换成这些别名,使类型边界更加清晰和统一。
  • detect_negative_cost 中,额外的 white_rows 类型转换和基于索引的循环增加了复杂度;你可以直接遍历 white(配合显式的 np.ndarray 注解),在满足严格类型检查的同时让代码更简单一些。
面向 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
- The ad-hoc `_to_int` helpers introduced in `floating_state.py` and `window_snap.py` are identical; consider centralizing this logic (and similar JSON numeric normalization) in a shared utility to avoid duplication and keep behavior consistent.
- Now that `JsonObject` and `MeasureState` are introduced, you might want to scan for remaining dynamic-JSON `dict[str, Any]` usages and align them with these aliases to make the typing boundary even clearer and more uniform.
- In `detect_negative_cost` the extra `white_rows` cast and index-based loop add complexity; you could iterate directly over `white` (with an explicit `np.ndarray` annotation) to keep the code simpler while still satisfying strict type checking.

## Individual Comments

### Comment 1
<location path="aao/ui/floating_state.py" line_range="61-62" />
<code_context>
     if not isinstance(g, list | tuple):
         return
-    x, y, w, h = [int(v) for v in g]
+    values = [_to_int(v) for v in cast(list[object] | tuple[object, ...], g)]
+    x, y, w, h = values
     widget.setGeometry(x, y, w, h)

</code_context>
<issue_to_address>
**issue (bug_risk):** Restore geometry should guard against sequences with length != 4 to avoid runtime errors.

`restore_geometry` only checks that `g` is a list/tuple, not that it has length 4. If stored geometry has a different length (e.g., corrupted settings), `x, y, w, h = values` will raise `ValueError`. Please reuse `_valid_geometry` (or at least its length check) before unpacking so invalid data is skipped instead of crashing.
</issue_to_address>

### Comment 2
<location path="aao/resources/updater.py" line_range="98-99" />
<code_context>
                 req.add_header("Authorization", f"Bearer {token}")
             with urllib.request.urlopen(req, timeout=10) as resp:
-                data = json.loads(resp.read())
+                raw = json.loads(resp.read())
+                data = cast(JsonObject, raw) if isinstance(raw, dict) else {}
         except Exception as e:  # noqa: BLE001
             logger.warning("检查更新失败: %s", e)
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Unexpected response shapes from GitHub are silently treated as empty dict, which can hide issues.

When GitHub returns a non-dict payload (e.g., error JSON or HTML), `raw` is discarded and `data` becomes `{}` with no extra logging, so callers just see “no update” instead of a failed check. Consider logging a warning when `raw` is not a dict (or returning `None`) so the failure is visible to callers.

Suggested implementation:

```python
            if token:
                req.add_header("Authorization", f"Bearer {token}")
            with urllib.request.urlopen(req, timeout=10) as resp:
                raw = json.loads(resp.read())
                if isinstance(raw, dict):
                    data = cast(JsonObject, raw)
                else:
                    logger.warning("检查更新失败,返回 payload 非 JSON 对象: %r", raw)
                    return None
        except Exception as e:  # noqa: BLE001
            logger.warning("检查更新失败: %s", e)
            return None

```

```python
        try:
            req = _make_request(_RELEASES_LIST_API, _settings_github_token())
            with urllib.request.urlopen(req, timeout=10) as resp:
                raw = json.loads(resp.read())
                if isinstance(raw, list):
                    releases = cast(list[JsonObject], raw)
                else:
                    logger.warning("拉取 release 列表失败,返回 payload 非 JSON 数组: %r", raw)
                    return ""
        except Exception as e:  # noqa: BLE001
            logger.warning("拉取 release 列表失败,降级为单版 changelog: %s", e)
            return ""

```

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,请考虑分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进评审质量。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • The ad-hoc _to_int helpers introduced in floating_state.py and window_snap.py are identical; consider centralizing this logic (and similar JSON numeric normalization) in a shared utility to avoid duplication and keep behavior consistent.
  • Now that JsonObject and MeasureState are introduced, you might want to scan for remaining dynamic-JSON dict[str, Any] usages and align them with these aliases to make the typing boundary even clearer and more uniform.
  • In detect_negative_cost the extra white_rows cast and index-based loop add complexity; you could iterate directly over white (with an explicit np.ndarray annotation) to keep the code simpler while still satisfying strict type checking.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The ad-hoc `_to_int` helpers introduced in `floating_state.py` and `window_snap.py` are identical; consider centralizing this logic (and similar JSON numeric normalization) in a shared utility to avoid duplication and keep behavior consistent.
- Now that `JsonObject` and `MeasureState` are introduced, you might want to scan for remaining dynamic-JSON `dict[str, Any]` usages and align them with these aliases to make the typing boundary even clearer and more uniform.
- In `detect_negative_cost` the extra `white_rows` cast and index-based loop add complexity; you could iterate directly over `white` (with an explicit `np.ndarray` annotation) to keep the code simpler while still satisfying strict type checking.

## Individual Comments

### Comment 1
<location path="aao/ui/floating_state.py" line_range="61-62" />
<code_context>
     if not isinstance(g, list | tuple):
         return
-    x, y, w, h = [int(v) for v in g]
+    values = [_to_int(v) for v in cast(list[object] | tuple[object, ...], g)]
+    x, y, w, h = values
     widget.setGeometry(x, y, w, h)

</code_context>
<issue_to_address>
**issue (bug_risk):** Restore geometry should guard against sequences with length != 4 to avoid runtime errors.

`restore_geometry` only checks that `g` is a list/tuple, not that it has length 4. If stored geometry has a different length (e.g., corrupted settings), `x, y, w, h = values` will raise `ValueError`. Please reuse `_valid_geometry` (or at least its length check) before unpacking so invalid data is skipped instead of crashing.
</issue_to_address>

### Comment 2
<location path="aao/resources/updater.py" line_range="98-99" />
<code_context>
                 req.add_header("Authorization", f"Bearer {token}")
             with urllib.request.urlopen(req, timeout=10) as resp:
-                data = json.loads(resp.read())
+                raw = json.loads(resp.read())
+                data = cast(JsonObject, raw) if isinstance(raw, dict) else {}
         except Exception as e:  # noqa: BLE001
             logger.warning("检查更新失败: %s", e)
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Unexpected response shapes from GitHub are silently treated as empty dict, which can hide issues.

When GitHub returns a non-dict payload (e.g., error JSON or HTML), `raw` is discarded and `data` becomes `{}` with no extra logging, so callers just see “no update” instead of a failed check. Consider logging a warning when `raw` is not a dict (or returning `None`) so the failure is visible to callers.

Suggested implementation:

```python
            if token:
                req.add_header("Authorization", f"Bearer {token}")
            with urllib.request.urlopen(req, timeout=10) as resp:
                raw = json.loads(resp.read())
                if isinstance(raw, dict):
                    data = cast(JsonObject, raw)
                else:
                    logger.warning("检查更新失败,返回 payload 非 JSON 对象: %r", raw)
                    return None
        except Exception as e:  # noqa: BLE001
            logger.warning("检查更新失败: %s", e)
            return None

```

```python
        try:
            req = _make_request(_RELEASES_LIST_API, _settings_github_token())
            with urllib.request.urlopen(req, timeout=10) as resp:
                raw = json.loads(resp.read())
                if isinstance(raw, list):
                    releases = cast(list[JsonObject], raw)
                else:
                    logger.warning("拉取 release 列表失败,返回 payload 非 JSON 数组: %r", raw)
                    return ""
        except Exception as e:  # noqa: BLE001
            logger.warning("拉取 release 列表失败,降级为单版 changelog: %s", e)
            return ""

```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread aao/ui/floating_state.py Outdated
Comment thread aao/resources/updater.py Outdated
@Windsland52
Windsland52 force-pushed the codex/strict-maaframework-types branch from 32ea680 to 7c2f83d Compare July 1, 2026 14:21
@a16797
a16797 force-pushed the codex/strict-maaframework-types branch from 568d7a4 to 64a91e6 Compare July 1, 2026 14:53
@a16797 a16797 changed the title Keep pyright strict without broad MaaFramework suppressions Keep pyright strict with dynamic-boundary ignores Jul 1, 2026
@Windsland52
Windsland52 force-pushed the codex/strict-maaframework-types branch from 64a91e6 to 141001d Compare July 1, 2026 15:25

@Windsland52 Windsland52 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Approve — clean, well-scoped PR. No blocking issues.

Verification (ran the project toolchain on PR head 141001d over base 436dc5d):

  • uv run pyright → 0 errors, 0 warnings, 0 informations
  • uv run ruff check . → all checks passed

The clean pyright run is the key result: removing the project-wide reportMissingTypeArgument = "none" suppression could surface bare-container errors anywhere in the tree. Pyright passing clean confirms every site is covered. Also confirmed the two files whose diff showed no new Any import (aao/measure/api_server.py:14, aao/ui/settings_page.py:12) already import Any.

Annotation-only change; all touched files use from __future__ import annotations, so zero runtime/behavioral impact.

Minor non-blocking notes (pre-existing looseness inherited by the PR, not regressions it introduced):

  1. aao/ui/settings_page.py:390self._windows: list[Any] could be list[DesktopWindow] (under TYPE_CHECKING) for real precision. Was bare list before, so the PR just made it pass minimally.
  2. aao/measure/api_server.py:37self._clients: set[Any] similarly loose for a WebSocket client set. Acceptable given the PR's minimal-annotation goal.
  3. pyproject.toml — trailing blank line removed (cosmetic, no issue).

Conclusion: the broad suppression is removed, strict mode is retained, and both pyright and ruff pass. Ready to merge.

@Windsland52
Windsland52 merged commit 78711a1 into Windsland52:main Jul 1, 2026
2 checks passed
@Windsland52 Windsland52 changed the title Keep pyright strict with dynamic-boundary ignores refactor: 容器注解补 Any 参数 + 移除 reportMissingTypeArgument 全局抑制 Jul 1, 2026
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.

2 participants