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
+11 -11
View File
@@ -11,13 +11,13 @@ happens in the ESP32 firmware (Sprint 7).
from __future__ import annotations
from datetime import datetime, timezone
from enum import Enum
from datetime import UTC, datetime
from enum import StrEnum
from pydantic import BaseModel, ConfigDict, Field, model_validator
class KnobMode(str, Enum):
class KnobMode(StrEnum):
"""High-level state of the bridge knob."""
LIBRE = "libre"
@@ -30,7 +30,7 @@ class KnobMode(str, Enum):
"""Pending value being shown for operator confirmation."""
class KnobFunction(str, Enum):
class KnobFunction(StrEnum):
"""The set of values the knob may target once armed."""
NONE = "none"
@@ -84,7 +84,7 @@ class KnobState(BaseModel):
)
@model_validator(mode="after")
def _consistency(self) -> "KnobState":
def _consistency(self) -> KnobState:
if self.mode == KnobMode.LIBRE:
if self.function != KnobFunction.NONE:
raise ValueError("LIBRE mode requires function == NONE")
@@ -105,7 +105,7 @@ class KnobState(BaseModel):
# --- Pure transition helpers (return new immutable states) --------------
@classmethod
def idle(cls) -> "KnobState":
def idle(cls) -> KnobState:
"""Construct the canonical idle (LIBRE) state."""
return cls()
@@ -116,7 +116,7 @@ class KnobState(BaseModel):
current_value: float,
timeout_s: float = DEFAULT_ARMED_TIMEOUT_S,
now: datetime | None = None,
) -> "KnobState":
) -> KnobState:
"""Transition LIBRE → ARMADO for the given function."""
if self.mode != KnobMode.LIBRE:
raise ValueError(f"Cannot arm from mode {self.mode.value}")
@@ -127,11 +127,11 @@ class KnobState(BaseModel):
function=function,
current_value=current_value,
pending_value=None,
armed_at=now or datetime.now(timezone.utc),
armed_at=now or datetime.now(UTC),
timeout_remaining_s=timeout_s,
)
def propose(self, value: float, *, timeout_s: float = DEFAULT_ARMED_TIMEOUT_S) -> "KnobState":
def propose(self, value: float, *, timeout_s: float = DEFAULT_ARMED_TIMEOUT_S) -> KnobState:
"""Operator turned the knob: stage a pending value (ARMADO → CONFIRMANDO)."""
if self.mode not in (KnobMode.ARMADO, KnobMode.CONFIRMANDO):
raise ValueError(f"Cannot propose from mode {self.mode.value}")
@@ -144,7 +144,7 @@ class KnobState(BaseModel):
timeout_remaining_s=timeout_s,
)
def confirm(self, *, timeout_s: float = DEFAULT_ARMED_TIMEOUT_S) -> "KnobState":
def confirm(self, *, timeout_s: float = DEFAULT_ARMED_TIMEOUT_S) -> KnobState:
"""Operator pressed to confirm; commits ``pending_value`` and stays armed."""
if self.mode != KnobMode.CONFIRMANDO:
raise ValueError(f"Cannot confirm from mode {self.mode.value}")
@@ -157,6 +157,6 @@ class KnobState(BaseModel):
timeout_remaining_s=timeout_s,
)
def disarm(self) -> "KnobState":
def disarm(self) -> KnobState:
"""Force the knob back to LIBRE (timeout, alarm, mode change, long-press)."""
return KnobState.idle()