Skip to content

Commit 7c2f83d

Browse files
a16797Windsland52
authored andcommitted
style: keep pyright strict for MaaFramework
1 parent 7067c29 commit 7c2f83d

26 files changed

Lines changed: 236 additions & 106 deletions

aao/app.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
from aao.measure.overlay import OverlayWindow # noqa: E402
5454
from aao.measure.worker import MeasurementWorker # noqa: E402
5555
from aao.timeline.editor_window import EditorWindow # noqa: E402
56+
from aao.types import MeasureState # noqa: E402
5657
from aao.ui.about_page import AboutPage # noqa: E402
5758
from aao.ui.background import BackgroundContainer # noqa: E402
5859
from aao.ui.calibration_page import CalibrationPage # noqa: E402
@@ -493,7 +494,7 @@ def _reset_measure_timer(self, node_name: str) -> None:
493494
logger.debug("计时器重置(pipeline 节点: %s)", node_name)
494495
self.worker.request_reset_timer()
495496

496-
def _on_measure_state(self, state: dict) -> None:
497+
def _on_measure_state(self, state: MeasureState) -> None:
497498
from aao.core.timing.time_source import format_timer
498499

499500
total = state.get("totalElapsedFrames", 0)

aao/core/avatar.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import json
1212
import time
1313
from pathlib import Path
14-
from typing import TYPE_CHECKING
14+
from typing import TYPE_CHECKING, TypedDict, cast
1515

1616
import numpy as np
1717

@@ -25,6 +25,12 @@
2525
_DETAIL_WAIT = 1 # 等详情页打开
2626

2727

28+
class Slot(TypedDict):
29+
flag_rect: tuple[int, int, int, int]
30+
avatar_rect: tuple[int, int, int, int]
31+
click_pos: tuple[int, int]
32+
33+
2834
def _avatar_dir() -> Path:
2935
from aao.utils.runtime_paths import project_root
3036

@@ -39,7 +45,14 @@ def _match_avatar_roi_offset() -> tuple[int, int, int, int]:
3945

4046
path = project_root() / "resource" / "base" / "pipeline" / "reco.json"
4147
raw = json.loads(path.read_text(encoding="utf-8"))
42-
offset = raw["MatchAvatar"].get("roi_offset", [0, 0, 0, 0])
48+
if not isinstance(raw, dict):
49+
return (0, 0, 0, 0)
50+
data = cast(dict[str, object], raw)
51+
match_avatar = data.get("MatchAvatar")
52+
if not isinstance(match_avatar, dict):
53+
return (0, 0, 0, 0)
54+
match_avatar = cast(dict[str, object], match_avatar)
55+
offset = match_avatar.get("roi_offset", [0, 0, 0, 0])
4356
return tuple(int(v) for v in offset) # type: ignore[return-value]
4457

4558

@@ -56,7 +69,9 @@ def _get_char_id(oper_name: str) -> str:
5669
if not mapping_path.exists():
5770
return ""
5871
mapping = json.loads(mapping_path.read_text(encoding="utf-8"))
59-
return mapping.get(oper_name, "")
72+
if not isinstance(mapping, dict):
73+
return ""
74+
return str(cast(dict[str, object], mapping).get(oper_name, ""))
6075

6176

6277
def _normalize_name(name: str) -> str:
@@ -71,14 +86,14 @@ def _normalize_name(name: str) -> str:
7186
def detect_slots(
7287
context: Context,
7388
image: np.ndarray,
74-
) -> list[dict]:
89+
) -> list[Slot]:
7590
"""用 pipeline 节点 DetectSlots 检测待部署区所有干员槽位。"""
7691
reco_detail = context.run_recognition("DetectSlots", image)
7792

7893
if not reco_detail or not reco_detail.hit:
7994
return []
8095

81-
slots = []
96+
slots: list[Slot] = []
8297
for result in reco_detail.all_results:
8398
box = getattr(result, "box", None)
8499
if box is None:
@@ -217,7 +232,7 @@ def _ocr_oper_name(context: Context, detail_img: np.ndarray) -> str | None:
217232

218233
def _save_avatar_from_image(
219234
image: np.ndarray,
220-
slot: dict,
235+
slot: Slot,
221236
oper_name: str,
222237
) -> bool:
223238
"""从截图截取槽位头像并存盘。"""

aao/core/geometry/map_loader.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010

1111
import json
1212
from pathlib import Path
13+
from typing import cast
1314

15+
from aao.types import JsonObject
1416
from aao.utils.logger import logger
1517
from aao.utils.runtime_paths import project_root
1618

@@ -25,7 +27,8 @@ def _level_codes() -> dict[str, str]:
2527
path = project_root() / "data" / "level_codes.json"
2628
if not path.exists():
2729
return {}
28-
return json.loads(path.read_text(encoding="utf-8"))
30+
data = json.loads(path.read_text(encoding="utf-8"))
31+
return cast(dict[str, str], data) if isinstance(data, dict) else {}
2932

3033

3134
def find_map_file(code: str) -> Path | None:
@@ -52,17 +55,21 @@ def find_map_file(code: str) -> Path | None:
5255
return None
5356

5457

55-
def load_map(code: str) -> dict | None:
58+
def load_map(code: str) -> JsonObject | None:
5659
"""加载关卡数据。code 如 '1-7'。"""
5760
path = find_map_file(code)
5861
if path is None:
5962
logger.error("未找到关卡 %s 的地图数据", code)
6063
return None
6164
data = json.loads(path.read_text(encoding="utf-8"))
62-
logger.info(
63-
"加载关卡 %s (%s): %dx%d", code, data.get("name", "?"), data["height"], data["width"]
64-
)
65-
return data
65+
if not isinstance(data, dict):
66+
logger.error("关卡 %s 的地图数据格式无效", code)
67+
return None
68+
name = str(cast(JsonObject, data).get("name", "?"))
69+
height = int(cast(JsonObject, data)["height"])
70+
width = int(cast(JsonObject, data)["width"])
71+
logger.info("加载关卡 %s (%s): %dx%d", code, name, height, width)
72+
return cast(JsonObject, data)
6673

6774

6875
def list_codes() -> list[str]:

aao/core/geometry/view.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
import numpy as np
1515

16+
from aao.types import JsonObject
17+
1618
# 投影参数(prts-plus ViewCalculationConfig)
1719
_FROM_RATIO = 9 / 16
1820
_NEAR = 0.3
@@ -69,7 +71,7 @@ def _build_matrix(view_offset: list[float], side: bool) -> np.ndarray:
6971

7072

7173
def transform_map_to_view(
72-
level_data: dict,
74+
level_data: JsonObject,
7375
side: bool = False,
7476
) -> list[list[tuple[float, float]]]:
7577
"""把关卡数据投影为屏幕坐标(0-1 比例)。

aao/core/timing/tick.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from __future__ import annotations
1313

1414
import bisect
15+
from typing import cast
1516

1617
import numpy as np
1718

@@ -260,9 +261,11 @@ def detect_negative_cost(frame: np.ndarray) -> bool:
260261

261262
region = frame[y : y + h, x : x + w].astype(np.int16) # (h, w, 3) BGR
262263
white = (region > config.WHITE_THRESHOLD).all(axis=2) # (h, w) bool
264+
white_rows = cast(np.ndarray, white)
263265

264266
# 逐行求最长连续纯白 run:对每行做行内累计,遇非白清零,取全局最大。
265-
for row in white: # pyright: ignore[reportGeneralTypeIssues]
267+
for row_index in range(int(white_rows.shape[0])):
268+
row = cast(np.ndarray, white_rows[row_index])
266269
if not row.any():
267270
continue
268271
# 累计连续 True 长度:cumsum 在非白处“断点”归零的经典向量化写法。

aao/measure/api_server.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import websockets
1717

18+
from aao.types import MeasureState
1819
from aao.utils.logger import logger
1920

2021
DEFAULT_PORT = 2606
@@ -25,7 +26,7 @@ class ApiServer:
2526

2627
def __init__(
2728
self,
28-
get_state: Callable[[], dict],
29+
get_state: Callable[[], MeasureState],
2930
host: str = "localhost",
3031
port: int = DEFAULT_PORT,
3132
rate_hz: float = 60.0,
@@ -34,7 +35,7 @@ def __init__(
3435
self.host = host
3536
self.port = port
3637
self.rate_hz = rate_hz
37-
self._clients: set = set()
38+
self._clients: set[Any] = set()
3839
self._thread: threading.Thread | None = None
3940

4041
def start(self) -> None:

aao/measure/overlay.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from PySide6.QtWidgets import QApplication, QLabel, QSizeGrip, QVBoxLayout, QWidget
2121

2222
from aao.core.timing.time_source import format_timer
23+
from aao.types import MeasureState
2324
from aao.ui import floating_state
2425
from aao.ui.window_snap import (
2526
create_snap_follow,
@@ -131,7 +132,7 @@ def reset_layout(self, x: int, y: int) -> None:
131132
self.move(x, y)
132133
self._save_window_state()
133134

134-
def on_state(self, state: dict) -> None:
135+
def on_state(self, state: MeasureState) -> None:
135136
running = state.get("isRunning", False)
136137
cf = state.get("currentFrame")
137138
total = state.get("totalFramesInCycle", 0)

aao/measure/worker.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from aao.core.timing.calibration import FullCalibrationData
1717
from aao.core.timing.time_source import TimeSource
18+
from aao.types import MeasureState
1819
from aao.utils.logger import logger
1920

2021
if TYPE_CHECKING:
@@ -47,13 +48,13 @@ def __init__(
4748
self._running = False
4849
self._reset_requested = False
4950
self._consecutive_errors = 0
50-
self._latest: dict = {}
51+
self._latest: MeasureState = {}
5152
self._lock = threading.Lock()
5253

5354
@property
54-
def latest_state(self) -> dict:
55+
def latest_state(self) -> MeasureState:
5556
with self._lock:
56-
return dict(self._latest)
57+
return self._latest.copy()
5758

5859
def run(self) -> None:
5960
self._running = True
@@ -98,7 +99,7 @@ def run(self) -> None:
9899
time.sleep(backoff)
99100
continue
100101

101-
state = {
102+
state: MeasureState = {
102103
"isRunning": self.time_source.is_running,
103104
"currentFrame": self.time_source.current_frame_in_cycle,
104105
"totalFramesInCycle": self.time_source.total_frames_in_cycle,

aao/resources/updater.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
from collections.abc import Callable
2626
from dataclasses import dataclass
2727
from pathlib import Path
28+
from typing import cast
2829

2930
from aao import __version__
3031
from aao.resources import syncer
32+
from aao.types import JsonObject
3133
from aao.utils.logger import logger, setup_logging
3234
from aao.utils.runtime_paths import is_frozen, project_root
3335

@@ -93,7 +95,8 @@ def check_software(self) -> ReleaseInfo | None:
9395
if token:
9496
req.add_header("Authorization", f"Bearer {token}")
9597
with urllib.request.urlopen(req, timeout=10) as resp:
96-
data = json.loads(resp.read())
98+
raw = json.loads(resp.read())
99+
data = cast(JsonObject, raw) if isinstance(raw, dict) else {}
97100
except Exception as e: # noqa: BLE001
98101
logger.warning("检查更新失败: %s", e)
99102
return None
@@ -140,14 +143,12 @@ def _fetch_changelog_since(self, current: str) -> str:
140143
try:
141144
req = _make_request(_RELEASES_LIST_API, _settings_github_token())
142145
with urllib.request.urlopen(req, timeout=10) as resp:
143-
releases = json.loads(resp.read())
146+
raw = json.loads(resp.read())
147+
releases = cast(list[JsonObject], raw) if isinstance(raw, list) else []
144148
except Exception as e: # noqa: BLE001
145149
logger.warning("拉取 release 列表失败,降级为单版 changelog: %s", e)
146150
return ""
147151

148-
if not isinstance(releases, list):
149-
return ""
150-
151152
# 筛 version > current 的,按版本降序排
152153
entries: list[tuple[str, str]] = [] # (version, body)
153154
for r in releases:
@@ -209,13 +210,13 @@ def update_resources(
209210
progress_cb(r.message)
210211
return results
211212

212-
def update_all(self, progress_cb: Callable[[str], None] | None = None) -> dict:
213+
def update_all(self, progress_cb: Callable[[str], None] | None = None) -> JsonObject:
213214
"""检查软件更新 + 更新资源。
214215
215216
Returns:
216217
{"software": ReleaseInfo | None, "resources": [SyncResult]}
217218
"""
218-
result: dict = {}
219+
result: JsonObject = {}
219220

220221
if progress_cb:
221222
progress_cb("检查软件更新...")
@@ -417,7 +418,7 @@ def apply_update(self, zip_path: Path) -> None:
417418
_DOWNLOAD_BACKOFF_SEC = 1.0
418419

419420

420-
def _pick_win_asset(assets: list) -> AssetInfo | None:
421+
def _pick_win_asset(assets: list[JsonObject]) -> AssetInfo | None:
421422
"""从 release assets 里选 win-x64 zip。"""
422423
for a in assets:
423424
name = str(a.get("name", "")).lower()
@@ -437,7 +438,9 @@ def _settings_proxy() -> str | None:
437438
path = project_root() / "config" / "settings.json"
438439
if not path.exists():
439440
return None
440-
proxy = json.loads(path.read_text(encoding="utf-8")).get("proxy", "")
441+
raw = json.loads(path.read_text(encoding="utf-8"))
442+
data = cast(JsonObject, raw) if isinstance(raw, dict) else {}
443+
proxy = data.get("proxy", "")
441444
return str(proxy).strip() or None
442445
except Exception: # noqa: BLE001
443446
return None
@@ -450,7 +453,9 @@ def _settings_github_token() -> str | None:
450453
path = project_root() / "config" / "settings.json"
451454
if not path.exists():
452455
return None
453-
enc = json.loads(path.read_text(encoding="utf-8")).get("github_token_enc", "")
456+
raw = json.loads(path.read_text(encoding="utf-8"))
457+
data = cast(JsonObject, raw) if isinstance(raw, dict) else {}
458+
enc = data.get("github_token_enc", "")
454459
return decrypt_text(str(enc)) if enc else None
455460
except Exception: # noqa: BLE001
456461
return None

aao/timeline/editor_window.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from __future__ import annotations
1313

1414
import json
15+
from typing import cast
1516

1617
from PySide6.QtCore import QEvent, QObject, QRect, Qt, Signal
1718
from PySide6.QtGui import QColor, QFont, QPainter, QPalette
@@ -316,6 +317,7 @@ def _restore_side_panel_state(self) -> None:
316317
s = load_settings()
317318
states = s.get("collapsible_sections", {})
318319
if isinstance(states, dict) and "timeline_side_panel" in states:
320+
states = cast(dict[str, object], states)
319321
self._set_side_panel_visible(bool(states["timeline_side_panel"]))
320322

321323
def _style_frame_label(self) -> None:

0 commit comments

Comments
 (0)