From 141001dc7e16a84d8704b7577688568ea656289b Mon Sep 17 00:00:00 2001 From: a16797 <16302217+a16797@users.noreply.github.com> Date: Wed, 1 Jul 2026 22:53:16 +0800 Subject: [PATCH] style: keep strict with dynamic boundary ignores --- aao/app.py | 4 ++-- aao/core/avatar.py | 6 +++--- aao/core/geometry/map_loader.py | 3 ++- aao/core/geometry/view.py | 3 ++- aao/core/timing/battle_state.py | 5 ++++- aao/measure/api_server.py | 4 ++-- aao/measure/overlay.py | 4 +++- aao/measure/worker.py | 6 +++--- aao/resources/updater.py | 7 ++++--- aao/ui/farm_worker.py | 4 ++-- aao/ui/map_picker.py | 4 +++- aao/ui/settings_page.py | 6 +++--- custom/action/executor.py | 9 ++++++--- custom/reco/click_stage.py | 3 ++- pyproject.toml | 9 ++++----- 15 files changed, 45 insertions(+), 32 deletions(-) diff --git a/aao/app.py b/aao/app.py index e402bf9..8e6b90b 100644 --- a/aao/app.py +++ b/aao/app.py @@ -18,7 +18,7 @@ import argparse import os import sys -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any if TYPE_CHECKING: from maa.controller import Win32Controller @@ -493,7 +493,7 @@ def _reset_measure_timer(self, node_name: str) -> None: logger.debug("计时器重置(pipeline 节点: %s)", node_name) self.worker.request_reset_timer() - def _on_measure_state(self, state: dict) -> None: + def _on_measure_state(self, state: dict[str, Any]) -> None: from aao.core.timing.time_source import format_timer total = state.get("totalElapsedFrames", 0) diff --git a/aao/core/avatar.py b/aao/core/avatar.py index 56b0404..dfe446f 100644 --- a/aao/core/avatar.py +++ b/aao/core/avatar.py @@ -11,7 +11,7 @@ import json import time from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any import numpy as np @@ -71,7 +71,7 @@ def _normalize_name(name: str) -> str: def detect_slots( context: Context, image: np.ndarray, -) -> list[dict]: +) -> list[dict[str, Any]]: """用 pipeline 节点 DetectSlots 检测待部署区所有干员槽位。""" reco_detail = context.run_recognition("DetectSlots", image) @@ -217,7 +217,7 @@ def _ocr_oper_name(context: Context, detail_img: np.ndarray) -> str | None: def _save_avatar_from_image( image: np.ndarray, - slot: dict, + slot: dict[str, Any], oper_name: str, ) -> bool: """从截图截取槽位头像并存盘。""" diff --git a/aao/core/geometry/map_loader.py b/aao/core/geometry/map_loader.py index b773d94..03fdb2f 100644 --- a/aao/core/geometry/map_loader.py +++ b/aao/core/geometry/map_loader.py @@ -10,6 +10,7 @@ import json from pathlib import Path +from typing import Any from aao.utils.logger import logger from aao.utils.runtime_paths import project_root @@ -52,7 +53,7 @@ def find_map_file(code: str) -> Path | None: return None -def load_map(code: str) -> dict | None: +def load_map(code: str) -> dict[str, Any] | None: """加载关卡数据。code 如 '1-7'。""" path = find_map_file(code) if path is None: diff --git a/aao/core/geometry/view.py b/aao/core/geometry/view.py index 932271f..a25a5fc 100644 --- a/aao/core/geometry/view.py +++ b/aao/core/geometry/view.py @@ -10,6 +10,7 @@ from __future__ import annotations import math +from typing import Any import numpy as np @@ -69,7 +70,7 @@ def _build_matrix(view_offset: list[float], side: bool) -> np.ndarray: def transform_map_to_view( - level_data: dict, + level_data: dict[str, Any], side: bool = False, ) -> list[list[tuple[float, float]]]: """把关卡数据投影为屏幕坐标(0-1 比例)。 diff --git a/aao/core/timing/battle_state.py b/aao/core/timing/battle_state.py index e37f49e..d4802b6 100644 --- a/aao/core/timing/battle_state.py +++ b/aao/core/timing/battle_state.py @@ -13,6 +13,7 @@ from dataclasses import dataclass from enum import Enum +from typing import Any import numpy as np @@ -445,7 +446,9 @@ def detect_battle_state(frame: np.ndarray) -> BattleState: } -def _diagnose_battle_begin_bands(frame: np.ndarray, width: int, height: int, scale: float) -> dict: +def _diagnose_battle_begin_bands( + frame: np.ndarray, width: int, height: int, scale: float +) -> dict[str, Any]: """诊断 7 band sampling 各 band 的值。""" step = max(4, round(scale * BATTLE_BEGIN_SAMPLE_STEP_SCALE)) top_y_step = max(step, round(height * 0.10)) diff --git a/aao/measure/api_server.py b/aao/measure/api_server.py index 91c6d43..a7d7104 100644 --- a/aao/measure/api_server.py +++ b/aao/measure/api_server.py @@ -25,7 +25,7 @@ class ApiServer: def __init__( self, - get_state: Callable[[], dict], + get_state: Callable[[], dict[str, Any]], host: str = "localhost", port: int = DEFAULT_PORT, rate_hz: float = 60.0, @@ -34,7 +34,7 @@ def __init__( self.host = host self.port = port self.rate_hz = rate_hz - self._clients: set = set() + self._clients: set[Any] = set() self._thread: threading.Thread | None = None def start(self) -> None: diff --git a/aao/measure/overlay.py b/aao/measure/overlay.py index 9ba534b..9da4b43 100644 --- a/aao/measure/overlay.py +++ b/aao/measure/overlay.py @@ -6,6 +6,8 @@ from __future__ import annotations +from typing import Any + from PySide6.QtCore import QRectF, Qt, QTimer from PySide6.QtGui import ( QColor, @@ -131,7 +133,7 @@ def reset_layout(self, x: int, y: int) -> None: self.move(x, y) self._save_window_state() - def on_state(self, state: dict) -> None: + def on_state(self, state: dict[str, Any]) -> None: running = state.get("isRunning", False) cf = state.get("currentFrame") total = state.get("totalFramesInCycle", 0) diff --git a/aao/measure/worker.py b/aao/measure/worker.py index 70140e5..ba9ec6d 100644 --- a/aao/measure/worker.py +++ b/aao/measure/worker.py @@ -9,7 +9,7 @@ import threading import time -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from PySide6.QtCore import QObject, Signal @@ -47,11 +47,11 @@ def __init__( self._running = False self._reset_requested = False self._consecutive_errors = 0 - self._latest: dict = {} + self._latest: dict[str, Any] = {} self._lock = threading.Lock() @property - def latest_state(self) -> dict: + def latest_state(self) -> dict[str, Any]: with self._lock: return dict(self._latest) diff --git a/aao/resources/updater.py b/aao/resources/updater.py index fba019f..16094d4 100644 --- a/aao/resources/updater.py +++ b/aao/resources/updater.py @@ -25,6 +25,7 @@ from collections.abc import Callable from dataclasses import dataclass from pathlib import Path +from typing import Any from aao import __version__ from aao.resources import syncer @@ -209,13 +210,13 @@ def update_resources( progress_cb(r.message) return results - def update_all(self, progress_cb: Callable[[str], None] | None = None) -> dict: + def update_all(self, progress_cb: Callable[[str], None] | None = None) -> dict[str, Any]: """检查软件更新 + 更新资源。 Returns: {"software": ReleaseInfo | None, "resources": [SyncResult]} """ - result: dict = {} + result: dict[str, Any] = {} if progress_cb: progress_cb("检查软件更新...") @@ -417,7 +418,7 @@ def apply_update(self, zip_path: Path) -> None: _DOWNLOAD_BACKOFF_SEC = 1.0 -def _pick_win_asset(assets: list) -> AssetInfo | None: +def _pick_win_asset(assets: list[dict[str, Any]]) -> AssetInfo | None: """从 release assets 里选 win-x64 zip。""" for a in assets: name = str(a.get("name", "")).lower() diff --git a/aao/ui/farm_worker.py b/aao/ui/farm_worker.py index c34c607..5e9844d 100644 --- a/aao/ui/farm_worker.py +++ b/aao/ui/farm_worker.py @@ -13,7 +13,7 @@ import json import threading import time -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from PySide6.QtCore import QObject, Signal @@ -114,7 +114,7 @@ def _on_node(node_name: str) -> None: tl_param = json.dumps({"timeline_path": self._timeline_path}, ensure_ascii=False) pipeline["Farm@ClickStage"]["custom_recognition_param"] = tl_param - exec_param: dict = {"timeline_path": self._timeline_path} + exec_param: dict[str, Any] = {"timeline_path": self._timeline_path} if self._profile: exec_param["calibration"] = self._profile pipeline["Farm@Execute"]["custom_action_param"] = json.dumps(exec_param, ensure_ascii=False) diff --git a/aao/ui/map_picker.py b/aao/ui/map_picker.py index d79cdf1..db9af17 100644 --- a/aao/ui/map_picker.py +++ b/aao/ui/map_picker.py @@ -10,6 +10,8 @@ from __future__ import annotations +from typing import Any + from PySide6.QtCore import Signal from PySide6.QtGui import QBrush, QColor, QFont, QMouseEvent, QPainter, QPen from PySide6.QtWidgets import ( @@ -43,7 +45,7 @@ class _MapGrid(QGraphicsView): picked = Signal(str) # 棋盘记号,如 "D2" - def __init__(self, map_data: dict): + def __init__(self, map_data: dict[str, Any]): super().__init__() self._map_data = map_data self._height = map_data["height"] diff --git a/aao/ui/settings_page.py b/aao/ui/settings_page.py index c1eac14..66f05ca 100644 --- a/aao/ui/settings_page.py +++ b/aao/ui/settings_page.py @@ -47,7 +47,7 @@ def _settings_path(): return project_root() / "config" / "settings.json" -def load_settings() -> dict: +def load_settings() -> dict[str, Any]: p = _settings_path() if not p.exists(): return {} @@ -57,7 +57,7 @@ def load_settings() -> dict: return {} -def save_settings(data: dict) -> None: +def save_settings(data: dict[str, Any]) -> None: p = _settings_path() p.parent.mkdir(parents=True, exist_ok=True) p.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8") @@ -387,7 +387,7 @@ def _build_ui(self) -> None: self.btn_token_eye.clicked.connect(self._toggle_token_visible) self.btn_export_log.clicked.connect(self._on_export_log) - self._windows: list = [] # DesktopWindow 列表(与 list_windows 行对应) + self._windows: list[Any] = [] # DesktopWindow 列表(与 list_windows 行对应) self._preview_thread: QThread | None = None self._preview_worker: _PreviewWorker | None = None diff --git a/custom/action/executor.py b/custom/action/executor.py index d6919e0..370810f 100644 --- a/custom/action/executor.py +++ b/custom/action/executor.py @@ -23,6 +23,7 @@ import time from collections.abc import Callable from dataclasses import dataclass +from typing import Any import numpy as np from maa.context import Context @@ -87,7 +88,7 @@ def run(self, context: Context, argv: CustomAction.RunArg) -> CustomAction.RunRe logger.exception("ExecuteTimeline 异常") return CustomAction.RunResult(success=False) - def _execute(self, context: Context, params: dict) -> CustomAction.RunResult: + def _execute(self, context: Context, params: dict[str, Any]) -> CustomAction.RunResult: ctrl = context.tasker.controller # 优先 timeline_path(从文件加载,文件内含 map_code),兼容显式 timeline 数组 @@ -222,7 +223,7 @@ def _execute(self, context: Context, params: dict) -> CustomAction.RunResult: # 漏怪 = 本局失败(farm pipeline 会走放弃重试) return CustomAction.RunResult(success=not self._leaked and not self._abort_reason) - def _load_timeline_file(self, path: str) -> dict | None: + def _load_timeline_file(self, path: str) -> dict[str, Any] | None: """加载时间轴 JSON(纯文件名→config/timelines/,带路径→相对项目根)。""" from custom.reco.click_stage import resolve_timeline_path @@ -239,7 +240,9 @@ def _load_timeline_file(self, path: str) -> dict | None: logger.info("加载时间轴 %s(map_code=%s, %d 动作)", p, data.get("map_code"), n) return data - def _parse_actions(self, raw: list[dict], map_data: dict) -> list[Action]: + def _parse_actions( + self, raw: list[dict[str, Any]], map_data: dict[str, Any] + ) -> list[Action]: """解析 JSON 动作列表 → Action 对象(含投影坐标 + 目标帧)。""" h, w = map_data["height"], map_data["width"] front = transform_map_to_view(map_data, side=False) diff --git a/custom/reco/click_stage.py b/custom/reco/click_stage.py index 2029874..bb83f45 100644 --- a/custom/reco/click_stage.py +++ b/custom/reco/click_stage.py @@ -9,6 +9,7 @@ import json import time from pathlib import Path +from typing import Any from maa.context import Context from maa.custom_recognition import CustomRecognition @@ -65,7 +66,7 @@ def resolve_timeline_path(timeline_path: str) -> Path: return _TIMELINE_DIR / timeline_path -def _load_timeline_data(timeline_path: str | None) -> dict | None: +def _load_timeline_data(timeline_path: str | None) -> dict[str, Any] | None: """加载 timeline JSON。""" if not timeline_path: logger.error("timeline_path 为空") diff --git a/pyproject.toml b/pyproject.toml index 53a94b0..7137dcb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,16 +41,16 @@ extraPaths = ["custom", "aao", "tests", "tests/replay"] pythonVersion = "3.13" typeCheckingMode = "strict" -# strict 下关闭因 maafw 第三方库存根缺失导致的类型未知噪音 -# (reportMissingTypeStubs / reportUnknown* / reportMissingTypeArgument), -# 保留 strict 的其余实质检查(privateUsage / unnecessaryComparison / unusedFunction 等)。 +# MaaFramework 的 Python 包名是 MaaFw,导入名是 `maa`;当前不发布 PEP 561 类型信息。 reportMissingTypeStubs = "none" + +# 项目大量动态边界来自 MaaFramework 回调、Qt Signal 和 JSON 配置。 +# 保留 strict 的实质检查,但不为了消除 Unknown 传播而强行包一层项目内类型。 reportUnknownMemberType = "none" reportUnknownArgumentType = "none" reportUnknownVariableType = "none" reportUnknownParameterType = "none" reportUnknownLambdaType = "none" -reportMissingTypeArgument = "none" # tests: 访问 protected 成员 + 解构未使用变量是测试常见模式 [[tool.pyright.executionEnvironments]] @@ -67,4 +67,3 @@ reportUnusedVariable = "none" [tool.pytest.ini_options] testpaths = ["tests"] pythonpath = ["."] -