style: ruff format 10 python files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -58,9 +58,7 @@ def upgrade() -> None:
|
||||
)
|
||||
|
||||
conn.execute(
|
||||
sa.text(
|
||||
"SELECT create_hypertable('wireless_registrations', 'time', if_not_exists => TRUE)"
|
||||
)
|
||||
sa.text("SELECT create_hypertable('wireless_registrations', 'time', if_not_exists => TRUE)")
|
||||
)
|
||||
|
||||
# Primary lookup: device + time range
|
||||
@@ -126,9 +124,7 @@ def upgrade() -> None:
|
||||
)
|
||||
|
||||
conn.execute(
|
||||
sa.text(
|
||||
"SELECT create_hypertable('rf_monitor_stats', 'time', if_not_exists => TRUE)"
|
||||
)
|
||||
sa.text("SELECT create_hypertable('rf_monitor_stats', 'time', if_not_exists => TRUE)")
|
||||
)
|
||||
|
||||
conn.execute(
|
||||
@@ -157,23 +153,17 @@ def upgrade() -> None:
|
||||
conn.execute(sa.text("GRANT SELECT, INSERT ON rf_monitor_stats TO app_user"))
|
||||
conn.execute(sa.text("GRANT SELECT, INSERT ON rf_monitor_stats TO poller_user"))
|
||||
|
||||
conn.execute(
|
||||
sa.text("SELECT add_retention_policy('rf_monitor_stats', INTERVAL '30 days')")
|
||||
)
|
||||
conn.execute(sa.text("SELECT add_retention_policy('rf_monitor_stats', INTERVAL '30 days')"))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
|
||||
# Remove retention policies before dropping tables
|
||||
conn.execute(
|
||||
sa.text("SELECT remove_retention_policy('rf_monitor_stats', if_exists => true)")
|
||||
)
|
||||
conn.execute(sa.text("SELECT remove_retention_policy('rf_monitor_stats', if_exists => true)"))
|
||||
conn.execute(sa.text("DROP TABLE IF EXISTS rf_monitor_stats CASCADE"))
|
||||
|
||||
conn.execute(
|
||||
sa.text(
|
||||
"SELECT remove_retention_policy('wireless_registrations', if_exists => true)"
|
||||
)
|
||||
sa.text("SELECT remove_retention_policy('wireless_registrations', if_exists => true)")
|
||||
)
|
||||
conn.execute(sa.text("DROP TABLE IF EXISTS wireless_registrations CASCADE"))
|
||||
|
||||
@@ -183,11 +183,7 @@ def upgrade() -> None:
|
||||
)
|
||||
""")
|
||||
)
|
||||
conn.execute(
|
||||
sa.text(
|
||||
"GRANT SELECT, INSERT, UPDATE, DELETE ON site_alert_rules TO app_user"
|
||||
)
|
||||
)
|
||||
conn.execute(sa.text("GRANT SELECT, INSERT, UPDATE, DELETE ON site_alert_rules TO app_user"))
|
||||
|
||||
# site_alert_events RLS
|
||||
conn.execute(sa.text("ALTER TABLE site_alert_events ENABLE ROW LEVEL SECURITY"))
|
||||
@@ -205,23 +201,15 @@ def upgrade() -> None:
|
||||
)
|
||||
""")
|
||||
)
|
||||
conn.execute(
|
||||
sa.text(
|
||||
"GRANT SELECT, INSERT, UPDATE, DELETE ON site_alert_events TO app_user"
|
||||
)
|
||||
)
|
||||
conn.execute(sa.text("GRANT SELECT, INSERT, UPDATE, DELETE ON site_alert_events TO app_user"))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
|
||||
# Drop RLS policies
|
||||
conn.execute(
|
||||
sa.text("DROP POLICY IF EXISTS tenant_isolation ON site_alert_events")
|
||||
)
|
||||
conn.execute(
|
||||
sa.text("DROP POLICY IF EXISTS tenant_isolation ON site_alert_rules")
|
||||
)
|
||||
conn.execute(sa.text("DROP POLICY IF EXISTS tenant_isolation ON site_alert_events"))
|
||||
conn.execute(sa.text("DROP POLICY IF EXISTS tenant_isolation ON site_alert_rules"))
|
||||
|
||||
# Drop tables (indexes drop automatically with tables)
|
||||
op.drop_table("site_alert_events")
|
||||
|
||||
@@ -150,9 +150,7 @@ class SiteAlertEvent(Base):
|
||||
triggered_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now(), nullable=False
|
||||
)
|
||||
resolved_at: Mapped[datetime | None] = mapped_column(
|
||||
DateTime(timezone=True), nullable=True
|
||||
)
|
||||
resolved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
resolved_by: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("users.id", ondelete="SET NULL"),
|
||||
|
||||
@@ -36,14 +36,18 @@ router = APIRouter(tags=["links"])
|
||||
)
|
||||
async def list_links(
|
||||
tenant_id: uuid.UUID,
|
||||
state: Optional[str] = Query(None, description="Filter by link state (active, degraded, down, stale)"),
|
||||
state: Optional[str] = Query(
|
||||
None, description="Filter by link state (active, degraded, down, stale)"
|
||||
),
|
||||
device_id: Optional[uuid.UUID] = Query(None, description="Filter by device (AP or CPE side)"),
|
||||
current_user: CurrentUser = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> LinkListResponse:
|
||||
"""List all wireless links for a tenant with optional state and device filters."""
|
||||
await _check_tenant_access(current_user, tenant_id, db)
|
||||
return await link_service.get_links(db=db, tenant_id=tenant_id, state=state, device_id=device_id)
|
||||
return await link_service.get_links(
|
||||
db=db, tenant_id=tenant_id, state=state, device_id=device_id
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -91,7 +95,9 @@ async def list_device_registrations(
|
||||
) -> RegistrationListResponse:
|
||||
"""Get latest wireless registration data for a device (most recent per MAC)."""
|
||||
await _check_tenant_access(current_user, tenant_id, db)
|
||||
return await link_service.get_device_registrations(db=db, tenant_id=tenant_id, device_id=device_id)
|
||||
return await link_service.get_device_registrations(
|
||||
db=db, tenant_id=tenant_id, device_id=device_id
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
|
||||
@@ -98,9 +98,7 @@ async def get_alert_rule(
|
||||
db=db, tenant_id=tenant_id, site_id=site_id, rule_id=rule_id
|
||||
)
|
||||
if not result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Alert rule not found"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Alert rule not found")
|
||||
return result
|
||||
|
||||
|
||||
@@ -124,9 +122,7 @@ async def update_alert_rule(
|
||||
db=db, tenant_id=tenant_id, site_id=site_id, rule_id=rule_id, data=data
|
||||
)
|
||||
if not result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Alert rule not found"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Alert rule not found")
|
||||
return result
|
||||
|
||||
|
||||
@@ -149,9 +145,7 @@ async def delete_alert_rule(
|
||||
db=db, tenant_id=tenant_id, site_id=site_id, rule_id=rule_id
|
||||
)
|
||||
if not deleted:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Alert rule not found"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Alert rule not found")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -166,9 +166,7 @@ async def unassign_device(
|
||||
) -> None:
|
||||
"""Remove a device from a site. Requires operator role or above."""
|
||||
await _check_tenant_access(current_user, tenant_id, db)
|
||||
await site_service.remove_device_from_site(
|
||||
db=db, tenant_id=tenant_id, device_id=device_id
|
||||
)
|
||||
await site_service.remove_device_from_site(db=db, tenant_id=tenant_id, device_id=device_id)
|
||||
|
||||
|
||||
@router.post(
|
||||
|
||||
@@ -43,8 +43,7 @@ async def _evaluate_condition(session, rule) -> bool: # noqa: ANN001
|
||||
|
||||
offline_result = await session.execute(
|
||||
text(
|
||||
"SELECT count(*) AS cnt FROM devices "
|
||||
"WHERE site_id = :site_id AND is_online = false"
|
||||
"SELECT count(*) AS cnt FROM devices WHERE site_id = :site_id AND is_online = false"
|
||||
),
|
||||
{"site_id": site_id},
|
||||
)
|
||||
@@ -55,8 +54,7 @@ async def _evaluate_condition(session, rule) -> bool: # noqa: ANN001
|
||||
elif rule_type == "device_offline_count":
|
||||
offline_result = await session.execute(
|
||||
text(
|
||||
"SELECT count(*) AS cnt FROM devices "
|
||||
"WHERE site_id = :site_id AND is_online = false"
|
||||
"SELECT count(*) AS cnt FROM devices WHERE site_id = :site_id AND is_online = false"
|
||||
),
|
||||
{"site_id": site_id},
|
||||
)
|
||||
@@ -171,9 +169,11 @@ async def _evaluate_rules() -> None:
|
||||
# Events with consecutive_hits < 2 are considered "pending"
|
||||
# (not yet confirmed). On next evaluation if still met,
|
||||
# consecutive_hits increments to 2 (confirmed alert).
|
||||
severity = "critical" if rule.rule_type in (
|
||||
"device_offline_percent", "device_offline_count"
|
||||
) else "warning"
|
||||
severity = (
|
||||
"critical"
|
||||
if rule.rule_type in ("device_offline_percent", "device_offline_count")
|
||||
else "warning"
|
||||
)
|
||||
|
||||
await session.execute(
|
||||
text("""
|
||||
|
||||
@@ -122,9 +122,7 @@ async def _subscribe_with_retry(js: JetStreamContext) -> None:
|
||||
durable="api-interface-consumer",
|
||||
stream="DEVICE_EVENTS",
|
||||
)
|
||||
logger.info(
|
||||
"NATS: subscribed to device.interfaces.> (durable: api-interface-consumer)"
|
||||
)
|
||||
logger.info("NATS: subscribed to device.interfaces.> (durable: api-interface-consumer)")
|
||||
return
|
||||
except Exception as exc:
|
||||
if attempt < max_attempts:
|
||||
|
||||
@@ -31,8 +31,8 @@ _link_discovery_client: Optional[NATSClient] = None
|
||||
|
||||
# Configurable thresholds for link state transitions
|
||||
DEGRADED_SIGNAL_THRESHOLD = -80 # dBm — signals weaker than this mark link as degraded
|
||||
CONSECUTIVE_MISS_THRESHOLD = 3 # Missed polls before marking link as down
|
||||
STALE_HOURS = 24 # Hours after down before marking link as stale
|
||||
CONSECUTIVE_MISS_THRESHOLD = 3 # Missed polls before marking link as down
|
||||
STALE_HOURS = 24 # Hours after down before marking link as stale
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -187,14 +187,16 @@ async def on_wireless_registration_for_links(msg) -> None:
|
||||
|
||||
# Mark stale: any links in 'down' state where last_seen > STALE_HOURS ago
|
||||
await session.execute(
|
||||
text("""
|
||||
text(
|
||||
"""
|
||||
UPDATE wireless_links
|
||||
SET state = 'stale', updated_at = NOW()
|
||||
WHERE ap_device_id = :ap_device_id
|
||||
AND tenant_id = :tenant_id
|
||||
AND state = 'down'
|
||||
AND last_seen < NOW() - INTERVAL ':stale_hours hours'
|
||||
""".replace(":stale_hours", str(STALE_HOURS))),
|
||||
""".replace(":stale_hours", str(STALE_HOURS))
|
||||
),
|
||||
{
|
||||
"ap_device_id": device_id,
|
||||
"tenant_id": tenant_id,
|
||||
|
||||
@@ -29,7 +29,9 @@ logger = structlog.get_logger("site_service")
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _site_response(site: Site, device_count: int = 0, online_count: int = 0, alert_count: int = 0) -> SiteResponse:
|
||||
def _site_response(
|
||||
site: Site, device_count: int = 0, online_count: int = 0, alert_count: int = 0
|
||||
) -> SiteResponse:
|
||||
"""Build a SiteResponse from an ORM Site instance with health stats."""
|
||||
online_percent = (online_count / device_count * 100) if device_count > 0 else 0.0
|
||||
return SiteResponse(
|
||||
@@ -51,9 +53,7 @@ def _site_response(site: Site, device_count: int = 0, online_count: int = 0, ale
|
||||
|
||||
async def _get_site_or_404(db: AsyncSession, tenant_id: uuid.UUID, site_id: uuid.UUID) -> Site:
|
||||
"""Fetch a site by id and tenant, or raise 404."""
|
||||
result = await db.execute(
|
||||
select(Site).where(Site.id == site_id, Site.tenant_id == tenant_id)
|
||||
)
|
||||
result = await db.execute(select(Site).where(Site.id == site_id, Site.tenant_id == tenant_id))
|
||||
site = result.scalar_one_or_none()
|
||||
if not site:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Site not found")
|
||||
|
||||
Reference in New Issue
Block a user