Commit Graph
2 Commits
Author SHA1 Message Date
xannasavinandJames Woglom 758204d4de URL-encode Nightscout date filters instead of retrying with a mangled timestamp
arrow's isoformat() ends a timestamp with its UTC offset, e.g.
'2026-07-16T00:00:00+02:00'. Placed raw into a query string, '+' is a
reserved character that servers decode as a space, so Nightscout receives
'2026-07-16T00:00:00 02:00' and answers 'could not parse as a valid ISO-8601
date'. Any user on a positive UTC offset hits this on every date-filtered
query.

The current workaround retries the request with 'T' replaced by a space
(t_to_space) and treats a failure as 'no previous entry'. That gets a
response, but it works around the symptom: the value is still mangled, every
affected query costs two round-trips, and a genuinely empty result is
indistinguishable from a rejected one.

Percent-encoding the value fixes the cause: the offset survives, the first
request succeeds, and the t_to_space fallback and its retry wrapper are no
longer needed and are removed.

Adds tests for time_range(): that a positive offset is encoded rather than
emitted raw, that the encoded value round-trips back to the original instant,
that negative offsets and 'Z' still parse, and the bounds/no-bounds cases.
There was no coverage of time_range() before. The encoding test fails on
master with '+' present in the query and passes with this change.

Running with this in my EU deployment since May.
2026-07-20 20:50:13 -04:00
xannasavinandJames Woglom 823bb785ba Harden autoupdate against transient errors instead of exiting
get() retries only HTTP 401 and 500, so any other API error propagates out of
the autoupdate loop and exits the process. When Tandem retired the
reportsfacade endpoints and pumpeventmetadata began returning 404 (#146), a
container with a restart policy would crash-loop. That is the worst possible
response to an API outage: the credentials cache dies with the process, so
every restart performs a full login against sso.tandemdiabetes.com. In my EU
deployment that was a fresh login roughly every two minutes for hours from a
single IP, which seems a good way to earn a WAF ban while already broken.

Transient network errors (DNS failures, timeouts, mid-stream disconnects,
urllib3 retry-budget exhaustion) have the same problem.

This keeps both failure families inside the loop and backs off exponentially:
30s doubling to a cap of AUTOUPDATE_DEFAULT_SLEEP_SECONDS (300s default),
reset on any successful poll. The cap reuses the existing poll interval, so a
failing API is never contacted more often than a healthy one. After three
consecutive failures the log escalates from WARNING to ERROR.

Staying alive forever would make a real outage silent on deployments whose
only alarm is the container dying, so after AUTOUPDATE_API_FAILURE_MINUTES
(default 45) of unbroken failure the process gives up and exits non-zero.
That is roughly one restart per hour during a genuine outage instead of one
every two minutes, while short blips stay silent. Set 0 to disable.

This is deliberately not gated on AUTOUPDATE_RESTART_ON_FAILURE, which covers
the pump-not-uploading watchdog where restarting achieves nothing (as the
existing TODO notes) and which many users therefore disable. An unreachable
API is a different failure and gets its own knob.

ApiLoginException stays fatal: bad credentials are not transient, and
retrying them in-process would hammer the login endpoint with attempts that
cannot succeed.

Also included:

- A defensive clamp so a negative rolling-average entry can never reach
  time.sleep() and crash with ValueError.
- Tests covering the backoff sequence, reset-on-success, the sustained-failure
  exit, the opt-out, and that login failures and programming errors still
  propagate.
- README documentation for all nine AUTOUPDATE_* variables, none of which were
  documented outside secret.py.
2026-07-20 19:12:24 -04:00