SmartTimer - Non-Blocking Timers
The smarttimer module provides setTimeout/setInterval semantics (like JavaScript) that work transparently in both sync and async Python contexts.
Overview
flowchart TD
subgraph "Call Site"
A[set_timeout / set_interval]
end
subgraph "Context Detection"
B{Running event loop?}
end
subgraph "Execution Strategy"
C[threading.Timer / Thread]
D[asyncio.create_task]
end
A --> B
B -->|No - Sync context| C
B -->|Yes - Async context| D
Installation
smarttimer is included in genro-toolbox:
pip install genro-toolbox
API
set_timeout
Schedule a one-shot callback after delay seconds:
from genro_toolbox import set_timeout
timer_id = set_timeout(2.0, print, "Hello!")
# "Hello!" printed after 2 seconds
set_interval
Schedule a repeating callback every delay seconds:
from genro_toolbox import set_interval, cancel_timer
timer_id = set_interval(1.0, print, "tick")
# "tick" printed every second until cancelled
cancel_timer(timer_id)
Use initial_delay to control the delay before the first execution (defaults to delay):
# Check immediately (after 1s), then every 30s
tid = set_interval(30.0, check_status, initial_delay=1)
cancel_timer
Cancel a pending timer by its ID. Returns True if the timer was found and cancelled, False otherwise:
from genro_toolbox import set_timeout, cancel_timer
timer_id = set_timeout(10.0, expensive_task)
cancel_timer(timer_id) # True — cancelled before firing
cancel_timer(timer_id) # False — already gone
Context Detection
The module automatically detects whether it’s running in a sync or async context:
Context |
Timer mechanism |
Callback: sync |
Callback: async |
|---|---|---|---|
Sync |
|
Direct call |
Temp event loop |
Async |
|
|
|
Real-World Examples
Token refresh (inside a server/worker)
Renew an auth token before it expires, without blocking request handling:
from genro_toolbox import set_timeout, cancel_timer
class TokenManager:
def __init__(self, auth_client):
self.auth_client = auth_client
self._refresh_timer = None
def on_token_received(self, token, expires_in):
self.token = token
# Schedule refresh 5 minutes before expiry
if self._refresh_timer:
cancel_timer(self._refresh_timer)
self._refresh_timer = set_timeout(
expires_in - 300, self._refresh
)
def _refresh(self):
new_token = self.auth_client.refresh()
self.on_token_received(new_token, new_token.expires_in)
Heartbeat / keepalive (ASGI server)
Send periodic pings on a WebSocket connection:
from genro_toolbox import set_interval, cancel_timer
class WebSocketHandler:
def __init__(self, ws):
self.ws = ws
self._heartbeat = None
async def on_connect(self):
self._heartbeat = set_interval(30.0, self.ws.send_json, {"type": "ping"})
async def on_disconnect(self):
if self._heartbeat:
cancel_timer(self._heartbeat)
Job polling (async worker)
Poll a job queue and stop when the job completes:
from genro_toolbox import set_interval, cancel_timer
pollers = {}
async def check_job(job_id):
status = await api.get_job_status(job_id)
if status in ("completed", "failed"):
cancel_timer(pollers.pop(job_id))
await handle_result(job_id, status)
def start_polling(job_id):
pollers[job_id] = set_interval(5.0, check_job, job_id)
Cache invalidation (sync WSGI server)
Periodically clear a cache while the server is running:
from genro_toolbox import set_interval
def setup_cache(app):
app.cache = {}
set_interval(60.0, app.cache.clear)
Timer IDs
Each timer gets a unique 22-character ID (from get_uuid), suitable for logging and tracking:
tid = set_timeout(5.0, callback)
print(tid) # e.g., "Z00005KmLxHj7F9aGbCd3e"
Thread Safety
The timer registry is protected by a lock. Creating and cancelling timers is safe from any thread.
Note on Standalone Scripts
In sync standalone scripts (no server/framework), the process must stay alive for timers to fire — daemon threads die when the main thread exits. Inside servers, workers, or async event loops the process is already alive and timers work as expected.