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
TConnectApi and TandemSourceApi defaulted their region parameter to a
hardcoded 'US', so callers that construct them without an explicit
region (e.g. tconnectsync-heroku's check_login path) sent EU accounts
to the US login endpoint, which rejected them with HTTP 401
account/invalid_credentials even when TCONNECT_REGION=EU was set.
Both classes (and fetch_oneshot) now fall back to the configured
TCONNECT_REGION when no region argument is given, so downstream
consumers honor the .env/environment configuration.
Adds top-level integration tests that configure the EU region via each
supported mechanism (TCONNECT_REGION environment variable, .env file,
and --region CLI flag) and drive the real downstream code -- including
the full main(['--check-login']) entrypoint -- against a mocked HTTP
layer with a real RS256-signed OIDC id_token. Only the EU endpoints are
registered, and the tests assert the EU login/token/jwks/pumper/
pump-logs endpoints are actually invoked and that no US host is ever
contacted.
Fixes#152
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DEvvZSWHo2dki5h1HikUU
Since the 2.0 migration to Tandem Source, the live sync path
(api.tandemsource + sync/tandemsource/*) no longer references the
legacy t:connect APIs. This removes that now-unreachable code.
Removed modules:
- api/controliq.py, api/ws2.py, api/android.py, api/webui.py
(the legacy controliq / tconnectws2 / android / webui clients)
- process.py (old process_time_range; already broken since it
imported sync submodules that no longer exist)
- parser/ciq_therapy_events.py, parser/tconnect.py (TConnectEntry)
- domain/therapy_event.py, domain/bolus.py, domain/device_settings.py,
domain/utility.py
Trimmed dead wiring from live modules:
- api/__init__.py: dropped the controliq/ws2/android/webui properties,
keeping only the tandemsource accessor
- check.py: removed the unused TConnectEntry import
- parser/nightscout.py: removed the unused legacy profile_store() plus
the now-orphaned tandem_to_ns_time / tandem_to_ns_time_seconds helpers
and InvalidTimeException (the live path uses tandemsource_profile_store)
Tests: removed suites covering the deleted modules; pared tests/api/fake.py
down to the TConnectApi fake still used by the tandemsource tests. README
"Tandem APIs" sections updated to reflect the single Tandem Source API.
Full test suite passes (48 passed, 1 skipped).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQNn3mBG1kXTAdQb9c2jfW