polish(sprint-0): clean code per ruff + mypy strict

Run the dev linters over Sprint 0's core/library/shared modules and
address every finding. Behaviour unchanged; tests still 80/80 green.

Changes:

- Replace `class Foo(str, Enum)` with `class Foo(StrEnum)` (PEP 663
  / Python 3.11+) in 7 enum classes: ActuatorType, AlarmSeverity,
  AlarmType, KnobMode, KnobFunction, AutopilotMode, AccessLevel,
  VesselType. Pydantic v2 serialises StrEnum the same way, so YAML/JSON
  round-trips are byte-identical.
- Use `datetime.UTC` alias in place of `datetime.timezone.utc`
  (UP017) across alarms.py, knob_state.py, project_config.py, and
  test_knob_state.py.
- Remove now-unnecessary forward-reference quotes from method return
  type annotations (UP037) — `from __future__ import annotations` is
  already in scope everywhere.
- Tighten `_read_json_resource` / `_read_yaml_resource` in the library
  loader: validate that the deserialised payload is actually a dict
  before returning, instead of leaking `Any` from json.loads /
  yaml.safe_load. Fixes the only two `mypy --strict` findings.
- Add `.claude/settings.local.json` to .gitignore (personal
  Claude Code overrides are not committed).

Verification:
  ruff check arautopilot/                 -> All checks passed
  mypy arautopilot/core library shared    -> Success, 0 issues, 12 files
  pytest                                  -> 80 passed in 0.25s

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 07:26:37 -04:00
parent 700756c16f
commit 8d4a698144
10 changed files with 54 additions and 41 deletions
+9 -9
View File
@@ -8,7 +8,7 @@ transport into the firmware build pipeline.
from __future__ import annotations
import json
from datetime import datetime, timezone
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
@@ -38,8 +38,8 @@ class ProjectConfig(BaseModel):
vessel: VesselConfig
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
modified_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
modified_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
# --- Serialisation -------------------------------------------------------
def to_dict(self) -> dict[str, Any]:
@@ -75,19 +75,19 @@ class ProjectConfig(BaseModel):
# --- Deserialisation -----------------------------------------------------
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "ProjectConfig":
def from_dict(cls, data: dict[str, Any]) -> ProjectConfig:
return cls.model_validate(data)
@classmethod
def from_json(cls, text: str) -> "ProjectConfig":
def from_json(cls, text: str) -> ProjectConfig:
return cls.model_validate(json.loads(text))
@classmethod
def from_yaml(cls, text: str) -> "ProjectConfig":
def from_yaml(cls, text: str) -> ProjectConfig:
return cls.model_validate(yaml.safe_load(text))
@classmethod
def load(cls, path: Path | str) -> "ProjectConfig":
def load(cls, path: Path | str) -> ProjectConfig:
"""Load from disk; format inferred from the file extension."""
p = Path(path)
text = p.read_text(encoding="utf-8")
@@ -98,6 +98,6 @@ class ProjectConfig(BaseModel):
return cls.from_json(text)
raise ValueError(f"Unsupported project file extension: {suffix!r}")
def touch(self) -> "ProjectConfig":
def touch(self) -> ProjectConfig:
"""Return a copy with ``modified_at`` refreshed to now (UTC)."""
return self.model_copy(update={"modified_at": datetime.now(timezone.utc)})
return self.model_copy(update={"modified_at": datetime.now(UTC)})