last_used.py 1.6 KB

123456789101112131415161718192021222324252627282930313233343536
  1. """When a credential's last-used time is due to be written again (#3360).
  2. API keys and long-lived tokens record when they were last used. The time is
  3. shown to people, so writing it at most once an interval is enough, and writing
  4. it on every request (each poll of a kiosk) costs a write and a commit per
  5. request. The attempt is remembered in this process too: when writes fail (a
  6. read-only replica after a failover, a locked SQLite database) the stored time
  7. does not move, and without that every request would try again, and on SQLite
  8. wait out the busy timeout first.
  9. """
  10. from datetime import datetime, timedelta, timezone
  11. class LastUsedThrottle:
  12. def __init__(self, interval: timedelta):
  13. self._interval = interval
  14. # Keyed by the credential's stored hash, which is unique to it and,
  15. # unlike a row id, never handed to another credential.
  16. self._attempts: dict[str, datetime] = {}
  17. def _recent(self, then: datetime | None, now: datetime) -> bool:
  18. if then is None:
  19. return False
  20. if then.tzinfo is None:
  21. then = then.replace(tzinfo=timezone.utc)
  22. # A time in the future (a clock that ran ahead, a restored backup) is
  23. # not recent: it would otherwise stop updating until the clock got there.
  24. return timedelta(0) <= now - then < self._interval
  25. def due(self, key: str, stored: datetime | None, now: datetime) -> bool:
  26. """Whether to write the time now, and if so, counts this as the attempt."""
  27. if self._recent(stored, now) or self._recent(self._attempts.get(key), now):
  28. return False
  29. self._attempts[key] = now
  30. return True