The Python 3.8 CI job installs an older mypy that behaves differently from
newer releases, surfacing two things the newer local mypy did not:
- requests (and other stub-less deps) raise `import-untyped`, which older
mypy does not silence via ignore_missing_imports. Add
`disable_error_code = import-untyped` so stub-less third-party libs are
treated as untyped rather than failing the run.
- TandemSourceApi.__init__: older mypy infers secret.TCONNECT_REGION as
Optional[str], so region.upper() tripped union-attr. Guard against an
unset region (also avoids an AttributeError at runtime) which narrows it
to str.
Verified with both mypy 1.19.1 and 2.3.0; tests still green (461 passed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaQXTakxAKEfeeys5cv3en
Remove the single-file opt-in in setup.cfg ([mypy] files) so mypy checks
the entire tconnectsync package, and fix everything that surfaced.
Config:
- setup.cfg: mypy `files` now points at the whole `tconnectsync` package.
Event processors (were typed as BaseEvent, which lacks the per-event fields
like seqNum and the event-specific attributes):
- Type each *_to_nsentry / helper with the concrete event type(s) it handles
(or a Union of them), matching the existing process_alarm.py pattern.
- process_basal_suspension, process_basal_resume, process_cgm_start_join_stop:
filter out None before appending to ns_entries, matching process_basal /
process_cgm_alert. Previously a None from a non-matching event would have
been appended and passed to upload_entry().
- Add explicit `return None` fall-throughs where a helper annotated to return
a value could implicitly return None.
- process_cgm_reading: give each sensor's GlucosevaluestatusEnum its own local
variable so the enum types don't clash.
process.py:
- Add an EventProcessor Protocol and annotate event_classes with it, so the
instantiated handlers are typed instead of `object`.
- Rename the updater-loop variable so it no longer collides with the
processor-loop variable's type.
Other:
- api/tandemsource.py: narrow the JWKS key to RSAPublicKey before jwt.decode,
and use arrow.get() (not time.time()) when forcing token expiry so the
attribute type stays consistent.
- api/common.py: split_days_range returns Arrow tuples, not str tuples.
- parser/nightscout.py: sort profile segments by the typed startTime rather
than an untyped dict value (same resulting order).
- domain/tandemsource/pump_settings.py: PumpSettings inherits DataClassJsonMixin
so from_dict is visible to the type checker.
- domain/tandemsource/event_class.py: ignore the set/Enum __hash__ clash.
- nightscout.py: explicit return None when a ConnectionError is swallowed.
No runtime behavior change; full test suite (461 passed, 1 skipped) still green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaQXTakxAKEfeeys5cv3en
ProcessAlarm.skip_event() read event.alarmId on every EventClass.ALARM event,
but LidMalfunctionActivated (a sibling of LidAlarmActivated in that class) has
no alarmId, so a malfunction alarm crashed the sync with AttributeError.
- Narrow with isinstance before reading alarmId; malfunction events now upload
as "Malfunction" as intended, and sync continues.
- Type the alarm handlers against an explicit AlarmEvent union and add an
assert_never exhaustiveness guard, so a type checker rejects unguarded
subtype attribute access and flags any newly added ALARM event type.
- Fix a latent None-leak: alarm_to_nsentry now always returns a dict.
- Add mypy as a gradual-typing beachhead (setup.cfg [mypy], CI step, Pipfile
typecheck script), scoped to process_alarm.py. This configuration fails on
exactly the #156 class of bug.
- Add regression tests: malfunction processing, mixed alarm batches, the event
shape, and an AlarmEvent/EventClass.ALARM sync guard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpMTd5LzkqFLZTUKd3H8em