Skip to content

Commit 5221c97

Browse files
Sbussisoclaude
andcommitted
chore(main): move background-loop constants above lifespan
The ``lifespan`` async function at line 48 referenced ``LOG_RETENTION_DAYS`` (defined at line 198) in its f-string. It worked at runtime — uvicorn calls ``lifespan`` after the module finishes importing, so the constant exists by the time the body executes — but the dependency ran backwards on the page and any refactor that called ``lifespan`` during import would NameError. Moved the entire background-loop tunables block (LOG_RETENTION_DAYS, LOG_CLEANUP_INTERVAL_HOURS, INACTIVE_CAMERA_CLEANUP_HOURS, OFFLINE_SWEEP_INTERVAL_SECONDS, OFFLINE_HEARTBEAT_TIMEOUT_SECONDS) up to immediately above the ``lifespan`` definition. Left a one-line breadcrumb at the old location so anyone search-grepping for the old spot lands cleanly. No runtime change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent faaf023 commit 5221c97

1 file changed

Lines changed: 35 additions & 21 deletions

File tree

backend/app/main.py

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,38 @@
4545
mcp_app = mcp.http_app(path="/", stateless_http=True, json_response=True)
4646

4747

48+
# ── Background-loop tunables ──────────────────────────────────────
49+
# These are defined *here* (above the functions that reference them)
50+
# so the f-string in ``lifespan`` doesn't depend on the rest of the
51+
# module having loaded first. lifespan is invoked by uvicorn during
52+
# app startup, after the module is fully imported, so the old layout
53+
# worked — but it was fragile to any refactor that called the
54+
# function during import. Putting the constants up top makes the
55+
# dependency direction obvious.
56+
57+
# Fallback retention for orgs whose plan can't be resolved (Clerk lookup
58+
# failed AND no cached Setting). The per-org tiered retention —
59+
# 30d / 90d / 365d for Free / Pro / Pro Plus — is sourced from
60+
# ``app.core.plans.PLAN_LIMITS[plan]["log_retention_days"]`` and applied
61+
# in ``_log_cleanup_loop`` below. This env var only matters when plan
62+
# resolution breaks entirely; we keep a 90-day default so a transient
63+
# Clerk outage doesn't silently wipe a paid customer's logs.
64+
LOG_RETENTION_DAYS = int(os.getenv("LOG_RETENTION_DAYS", "90"))
65+
LOG_CLEANUP_INTERVAL_HOURS = 24 # Run once per day
66+
# Cameras offline for longer than this get their in-memory caches freed.
67+
# HLS segments are live-only fragments — useless once streaming stops.
68+
INACTIVE_CAMERA_CLEANUP_HOURS = int(os.getenv("INACTIVE_CAMERA_CLEANUP_HOURS", "24"))
69+
70+
# How often to sweep for stale "online" entities and flip them to offline.
71+
# Needs to be shorter than the heartbeat-miss threshold (90s) for
72+
# timely notifications but longer than a few seconds to keep DB load low.
73+
OFFLINE_SWEEP_INTERVAL_SECONDS = int(os.getenv("OFFLINE_SWEEP_INTERVAL_SECONDS", "30"))
74+
# If a node/camera hasn't heart-beat in this many seconds, the sweep
75+
# marks it offline. Matches the 90s threshold used by the model's
76+
# ``effective_status`` property so the UI and DB agree.
77+
OFFLINE_HEARTBEAT_TIMEOUT_SECONDS = 90
78+
79+
4880
@asynccontextmanager
4981
async def lifespan(app):
5082
"""Application lifespan: startup and shutdown hooks."""
@@ -188,27 +220,9 @@ async def security_headers(request: Request, call_next):
188220
app.mount("/mcp", mcp_app)
189221

190222

191-
# Fallback retention for orgs whose plan can't be resolved (Clerk lookup
192-
# failed AND no cached Setting). The per-org tiered retention —
193-
# 30d / 90d / 365d for Free / Pro / Pro Plus — is sourced from
194-
# ``app.core.plans.PLAN_LIMITS[plan]["log_retention_days"]`` and applied
195-
# in ``_log_cleanup_loop`` below. This env var only matters when plan
196-
# resolution breaks entirely; we keep a 90-day default so a transient
197-
# Clerk outage doesn't silently wipe a paid customer's logs.
198-
LOG_RETENTION_DAYS = int(os.getenv("LOG_RETENTION_DAYS", "90"))
199-
LOG_CLEANUP_INTERVAL_HOURS = 24 # Run once per day
200-
# Cameras offline for longer than this get their in-memory caches freed.
201-
# HLS segments are live-only fragments — useless once streaming stops.
202-
INACTIVE_CAMERA_CLEANUP_HOURS = int(os.getenv("INACTIVE_CAMERA_CLEANUP_HOURS", "24"))
203-
204-
# How often to sweep for stale "online" entities and flip them to offline.
205-
# Needs to be shorter than the heartbeat-miss threshold (90s) for
206-
# timely notifications but longer than a few seconds to keep DB load low.
207-
OFFLINE_SWEEP_INTERVAL_SECONDS = int(os.getenv("OFFLINE_SWEEP_INTERVAL_SECONDS", "30"))
208-
# If a node/camera hasn't heart-beat in this many seconds, the sweep
209-
# marks it offline. Matches the 90s threshold used by the model's
210-
# ``effective_status`` property so the UI and DB agree.
211-
OFFLINE_HEARTBEAT_TIMEOUT_SECONDS = 90
223+
# Background-loop constants moved up to just above the ``lifespan``
224+
# function (search "Background-loop tunables") so the f-string in
225+
# ``lifespan`` doesn't reference a name defined later in the module.
212226

213227

214228
async def _log_cleanup_loop():

0 commit comments

Comments
 (0)