Skip to content

Commit 176eec9

Browse files
committed
build: configure ty and fix type annotations
1 parent 59f228f commit 176eec9

70 files changed

Lines changed: 338 additions & 244 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.pre-commit-config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,13 @@ repos:
2727
hooks:
2828
- id: codespell
2929
additional_dependencies: [".[toml]"]
30+
- repo: local
31+
hooks:
32+
- id: ty-check
33+
name: ty type check
34+
description: Run Astral's ty type checker.
35+
entry: uvx ty check
36+
language: system
37+
pass_filenames: false
38+
require_serial: true
39+
types: [python]

AGENTS.md

Lines changed: 37 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Agent Guidelines
22

3-
## 开发环境
3+
## 安装
44

55
```bash
66
git clone git@github.com:OpenWSGR/AutoWSGR.git
@@ -14,70 +14,65 @@ pre-commit install
1414
```bash
1515
source .venv/bin/activate # Linux/macOS
1616
# .venv\Scripts\activate # Windows
17-
pytest
18-
pre-commit run --all-files
1917
```
2018

21-
## 代码风格
22-
23-
- Python 版本:3.12+
24-
- 格式化与 lint:**Ruff**(已覆盖 isort / black 功能),配置见 `pyproject.toml`
25-
- 目标行宽 100,单引号字符串
26-
- 禁止相对导入(`ban-relative-imports = all`
27-
- 英语拼写检查:**codespell**,忽略词表见 `docs/spelling_wordlist.txt`
28-
29-
提交前务必运行:
19+
## pytest
3020

3121
```bash
32-
pre-commit run --all-files
22+
pytest -n auto
3323
```
3424

35-
## 测试
25+
测试目录结构:
26+
27+
| 目录 | 说明 |
28+
|------|------|
29+
| `tests/unit/` | pytest 自动运行的单元测试 |
30+
| `tests/manual/` | 需真实设备的手动 e2e 测试 |
3631

37-
- 单元测试:`pytest`(测试目录 `testing/`
38-
- 功能测试:运行 `examples/` 目录中的脚本进行端到端验证
32+
## pre-commit 检查
33+
34+
提交前务必运行:
3935

4036
```bash
41-
pytest
37+
pre-commit run --all-files
4238
```
4339

44-
## 约定式提交(Conventional Commits)
40+
包含 **Ruff**(格式化与 lint)和 **ty**(类型检查)。
4541

46-
提交信息格式:
42+
## 类型检查
4743

48-
```
49-
<type>(<scope>): <简短描述>
44+
本项目使用 **ty**(Astral 出品的 Python 类型检查器)进行静态类型检查。
5045

51-
<正文>
52-
```
46+
- 优先通过正确的类型注解、返回值标注和类型窄化来消除类型错误。
47+
- **禁止**在工作代码中使用 `typing.cast` 来掩盖类型问题;`cast` 只允许在测试文件的 Mock 场景中使用。
48+
- 若类型检查器因容器型变(如 `list` 的 invariant)报错,优先考虑将函数参数改为 `Sequence``Mapping` 等协变抽象基类,而非使用 `cast`
49+
- 修复类型问题时尽量保持最小改动,避免不必要的重构。
5350

54-
常用类型:
51+
### `ty: ignore` 注释规范
5552

56-
- `feat`:新功能
57-
- `fix`:修复
58-
- `build`:构建系统或依赖变更
59-
- `docs`:文档
60-
- `style`:不影响代码逻辑的格式调整
61-
- `refactor`:重构
62-
- `test`:测试
53+
当必须通过注释忽略类型错误时,**必须使用 ty 原生格式**
6354

64-
示例:
55+
```python
56+
# 正确
57+
c.r = 10 # ty: ignore[invalid-assignment]
58+
ctrl._device.shell.assert_called_once_with('input tap 480 270') # ty: ignore[unresolved-attribute]
6559

66-
```
67-
build: migrate from setuptools to hatchling
60+
# 错误 —— ty 无法识别 mypy 的 error code
61+
# type: ignore[invalid-assignment]
62+
# type: ignore[misc]
6863

69-
- Replace setuptools with hatchling as build backend
70-
- Remove obsolete MANIFEST.in
64+
# 错误 —— 裸 ignore 会被 ruff PGH003 拦截,且无法精确控制
65+
# type: ignore
66+
# type: ignore # noqa: PGH003
7167
```
7268

73-
## 构建与打包
69+
> 项目已启用 `unused-type-ignore-comment = "error"`,未使用的 `# ty: ignore[...]` 会导致 CI 失败。
7470
75-
- Build backend:**hatchling**
76-
- 包数据(图片、YAML、JAR 等)位于 `autowsgr/data/`,由 hatchling 自动包含,无需 `MANIFEST.in`
71+
## 单元测试要求
7772

78-
```bash
79-
uv build
80-
```
73+
新增功能或修改核心逻辑时,必须在 `tests/unit/` 下提供对应的 pytest 单元测试。测试文件应与被测源文件一一对应。
74+
75+
## 约定式提交
8176

8277
## 文档
8378

autowsgr/combat/engine.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def __init__(
6262

6363
# 运行时状态 (由 fight() 重置)
6464
self._plan: CombatPlan = CombatPlan(name='', mode=CombatMode.BATTLE)
65-
self._recognizer: CombatRecognizer = None # type: ignore[assignment] # set in fight()
65+
self._recognizer: CombatRecognizer | None = None # set in fight()
6666
self._phase = CombatPhase.PROCEED
6767
self._last_action = 'yes'
6868
self._node = '0'
@@ -83,7 +83,7 @@ def __init__(
8383
def fight( # noqa: PLR0912
8484
self,
8585
plan: CombatPlan,
86-
initial_ship_stats: list[ShipDamageState],
86+
initial_ship_stats: list[ShipDamageState] | None,
8787
) -> CombatResult:
8888
"""执行一次完整的战斗循环。
8989

autowsgr/combat/handlers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,9 @@ def _handle_spot_enemy(self) -> ConditionFlag: # noqa: PLR0912
163163
# ── 信息采集 ──
164164
mode = 'exercise' if self._plan.mode == CombatMode.EXERCISE else 'fight'
165165
enemies = get_enemy_info(self._device, mode=mode)
166-
enemy_formation = get_enemy_formation(self._device, self._ocr)
166+
enemy_formation = (
167+
get_enemy_formation(self._device, self._ocr) if self._ocr is not None else ''
168+
)
167169
_log.info('[Combat] 敌方编成: {} 阵型: {}', enemies, enemy_formation)
168170

169171
decision = self._get_current_decision()

autowsgr/combat/history.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class CombatEvent:
9494
extra: dict[str, Any] = field(default_factory=dict)
9595

9696
def __str__(self) -> str:
97-
parts = [f'[{self.event_type.name}]']
97+
parts: list[str] = [f'[{self.event_type.name}]']
9898
if self.node:
9999
parts.append(f'节点={self.node}')
100100
if self.action:

autowsgr/combat/rules.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,16 @@
3030
import re
3131
from dataclasses import dataclass, field
3232
from enum import Enum, auto
33-
from typing import Any
33+
from typing import TYPE_CHECKING, Any
3434

3535
from autowsgr.infra.logger import get_logger
3636
from autowsgr.types import Formation
3737

3838

39+
if TYPE_CHECKING:
40+
from collections.abc import Mapping
41+
42+
3943
# 允许在规则中出现的舰种标识符
4044
_log = get_logger('combat.recognition')
4145

@@ -134,7 +138,7 @@ def __post_init__(self) -> None:
134138
if self.op not in _OPERATORS:
135139
raise ValueError(f"不支持的操作符: '{self.op}',支持: {list(_OPERATORS)}")
136140

137-
def evaluate(self, context: dict[str, int | float]) -> bool:
141+
def evaluate(self, context: Mapping[str, int | float]) -> bool:
138142
"""在给定上下文中评估此条件。"""
139143
if '+' in self.field:
140144
parts = [p.strip() for p in self.field.split('+')]
@@ -159,7 +163,7 @@ class Rule:
159163
conditions: list[Condition]
160164
action: RuleAction
161165

162-
def evaluate(self, context: dict[str, int | float]) -> bool:
166+
def evaluate(self, context: Mapping[str, int | float]) -> bool:
163167
"""所有条件是否均满足。"""
164168
return all(c.evaluate(context) for c in self.conditions)
165169

@@ -184,7 +188,7 @@ class RuleEngine:
184188
rules: list[Rule] = field(default_factory=list)
185189
default: RuleAction = field(default_factory=RuleAction.no_action)
186190

187-
def evaluate(self, context: dict[str, int | float]) -> RuleAction:
191+
def evaluate(self, context: Mapping[str, int | float]) -> RuleAction:
188192
"""对敌方编成上下文评估所有规则。
189193
190194
Parameters

autowsgr/context/game_context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class GameContext:
6666

6767
# ── 基础设施引用 (可选) ──
6868

69-
ocr: OCREngine
69+
ocr: OCREngine | None = None
7070
"""OCR 引擎实例 (章节/阵型识别等)。"""
7171

7272
# ── 游戏运行时状态 ──

autowsgr/emulator/os_control/windows.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def stop(self) -> None:
9393
_log.info('云手机无需关闭')
9494
return
9595
case _:
96+
assert self._process_name is not None
9697
subprocess.run( # noqa: S603
9798
['taskkill', '-f', '-im', self._process_name], # noqa: S607
9899
check=True,

autowsgr/image_resources/keys.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ def _build_map() -> dict[TemplateKey, list[ImageTemplate]]:
106106
TemplateKey.END_EXERCISE_PAGE: [T.END_EXERCISE_PAGE],
107107
# 船坞已满
108108
TemplateKey.DOCK_FULL: [T.DOCK_FULL],
109+
# 战役次数耗尽
110+
TemplateKey.BATTLE_TIMES_EXCEED: [T.BATTLE_TIMES_EXCEED],
109111
# 战果评级
110112
TemplateKey.GRADE_SS: [T.Result.SS],
111113
TemplateKey.GRADE_S: [T.Result.S],

autowsgr/infra/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ def load(path: str | Path | None = None) -> UserConfig:
486486
return UserConfig()
487487
except ValidationError:
488488
# WSL/Linux 下默认配置缺少 serial/path 无法通过验证,提供占位值
489-
return UserConfig(emulator={'serial': '', 'path': ''})
489+
return UserConfig(emulator=EmulatorConfig(serial='', path=''))
490490
config = UserConfig.from_yaml(path)
491491
_log.info('已加载配置: {}', path)
492492
return config

0 commit comments

Comments
 (0)