Refactored timers module to use ctx.state and fixed new timers never firing.
CI / Formatting (push) Successful in 13s
CI / Linting (push) Successful in 14s
CI / Tests (Python 3.12) (push) Successful in 27s
CI / Tests (Python 3.13) (push) Successful in 26s
CI / Tests (Python 3.14) (push) Successful in 25s
CI / Type Checking (push) Successful in 24s
CI / Spelling (push) Successful in 14s

This commit is contained in:
2026-02-26 13:18:47 -05:00
parent 78722e73a5
commit 4e2c8b4d67
4 changed files with 29 additions and 42 deletions
+12 -27
View File
@@ -146,17 +146,19 @@ def _next_fire_time(row: aiosqlite.Row, now: datetime) -> datetime | None:
last_fired = row["last_fired_at"]
last_fired_dt = datetime.fromisoformat(last_fired) if last_fired else None
# Never fired before: immediately due.
if last_fired_dt is None:
return now
if row["interval_type"] == IntervalType.SIMPLE:
interval_secs = _duration_to_seconds(row["interval_value"])
if interval_secs <= 0:
return None
anchor = last_fired_dt if last_fired_dt is not None else now
return anchor + timedelta(seconds=interval_secs)
return last_fired_dt + timedelta(seconds=interval_secs)
# Cron timer: wait for the next scheduled tick.
try:
anchor = last_fired_dt if last_fired_dt is not None else now
return next(CronSim(row["interval_value"], anchor))
return next(CronSim(row["interval_value"], last_fired_dt))
except (ValueError, KeyError, CronSimError):
return None
@@ -377,31 +379,14 @@ async def _compute_next_delay(ctx: ModuleContext) -> tuple[float | None, str | N
return soonest_delay, soonest_display
# Module-level singleton for the scheduler instance.
_scheduler: TimerScheduler | None = None
def get_scheduler() -> TimerScheduler:
"""Return the active scheduler instance.
def get_scheduler(ctx: ModuleContext) -> TimerScheduler:
"""Return the scheduler stored in the module context's state.
:param ctx: The module context.
:return: The active TimerScheduler.
:raises RuntimeError: If the scheduler has not been initialized.
"""
if _scheduler is None:
scheduler = ctx.state.get("scheduler")
if not isinstance(scheduler, TimerScheduler):
raise RuntimeError("TimerScheduler is not initialized.")
return _scheduler
def set_scheduler(scheduler: TimerScheduler) -> None:
"""Set the active scheduler instance.
:param scheduler: The TimerScheduler to install.
"""
global _scheduler
_scheduler = scheduler
def clear_scheduler() -> None:
"""Clear the active scheduler instance."""
global _scheduler
_scheduler = None
return scheduler