style: ruff format 10 python files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-19 13:49:59 -05:00
parent 9d6b68760f
commit 6a5829e0ff
10 changed files with 41 additions and 67 deletions

View File

@@ -58,9 +58,7 @@ def upgrade() -> None:
) )
conn.execute( conn.execute(
sa.text( sa.text("SELECT create_hypertable('wireless_registrations', 'time', if_not_exists => TRUE)")
"SELECT create_hypertable('wireless_registrations', 'time', if_not_exists => TRUE)"
)
) )
# Primary lookup: device + time range # Primary lookup: device + time range
@@ -126,9 +124,7 @@ def upgrade() -> None:
) )
conn.execute( conn.execute(
sa.text( sa.text("SELECT create_hypertable('rf_monitor_stats', 'time', if_not_exists => TRUE)")
"SELECT create_hypertable('rf_monitor_stats', 'time', if_not_exists => TRUE)"
)
) )
conn.execute( 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 app_user"))
conn.execute(sa.text("GRANT SELECT, INSERT ON rf_monitor_stats TO poller_user")) conn.execute(sa.text("GRANT SELECT, INSERT ON rf_monitor_stats TO poller_user"))
conn.execute( conn.execute(sa.text("SELECT add_retention_policy('rf_monitor_stats', INTERVAL '30 days')"))
sa.text("SELECT add_retention_policy('rf_monitor_stats', INTERVAL '30 days')")
)
def downgrade() -> None: def downgrade() -> None:
conn = op.get_bind() conn = op.get_bind()
# Remove retention policies before dropping tables # Remove retention policies before dropping tables
conn.execute( conn.execute(sa.text("SELECT remove_retention_policy('rf_monitor_stats', if_exists => true)"))
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("DROP TABLE IF EXISTS rf_monitor_stats CASCADE"))
conn.execute( conn.execute(
sa.text( sa.text("SELECT remove_retention_policy('wireless_registrations', if_exists => true)")
"SELECT remove_retention_policy('wireless_registrations', if_exists => true)"
)
) )
conn.execute(sa.text("DROP TABLE IF EXISTS wireless_registrations CASCADE")) conn.execute(sa.text("DROP TABLE IF EXISTS wireless_registrations CASCADE"))

View File

@@ -183,11 +183,7 @@ def upgrade() -> None:
) )
""") """)
) )
conn.execute( conn.execute(sa.text("GRANT SELECT, INSERT, UPDATE, DELETE ON site_alert_rules TO app_user"))
sa.text(
"GRANT SELECT, INSERT, UPDATE, DELETE ON site_alert_rules TO app_user"
)
)
# site_alert_events RLS # site_alert_events RLS
conn.execute(sa.text("ALTER TABLE site_alert_events ENABLE ROW LEVEL SECURITY")) conn.execute(sa.text("ALTER TABLE site_alert_events ENABLE ROW LEVEL SECURITY"))
@@ -205,23 +201,15 @@ def upgrade() -> None:
) )
""") """)
) )
conn.execute( conn.execute(sa.text("GRANT SELECT, INSERT, UPDATE, DELETE ON site_alert_events TO app_user"))
sa.text(
"GRANT SELECT, INSERT, UPDATE, DELETE ON site_alert_events TO app_user"
)
)
def downgrade() -> None: def downgrade() -> None:
conn = op.get_bind() conn = op.get_bind()
# Drop RLS policies # Drop RLS policies
conn.execute( conn.execute(sa.text("DROP POLICY IF EXISTS tenant_isolation ON site_alert_events"))
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_rules")
)
# Drop tables (indexes drop automatically with tables) # Drop tables (indexes drop automatically with tables)
op.drop_table("site_alert_events") op.drop_table("site_alert_events")

View File

@@ -150,9 +150,7 @@ class SiteAlertEvent(Base):
triggered_at: Mapped[datetime] = mapped_column( triggered_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), nullable=False DateTime(timezone=True), server_default=func.now(), nullable=False
) )
resolved_at: Mapped[datetime | None] = mapped_column( resolved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
DateTime(timezone=True), nullable=True
)
resolved_by: Mapped[uuid.UUID | None] = mapped_column( resolved_by: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True), UUID(as_uuid=True),
ForeignKey("users.id", ondelete="SET NULL"), ForeignKey("users.id", ondelete="SET NULL"),

View File

@@ -36,14 +36,18 @@ router = APIRouter(tags=["links"])
) )
async def list_links( async def list_links(
tenant_id: uuid.UUID, 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)"), device_id: Optional[uuid.UUID] = Query(None, description="Filter by device (AP or CPE side)"),
current_user: CurrentUser = Depends(get_current_user), current_user: CurrentUser = Depends(get_current_user),
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
) -> LinkListResponse: ) -> LinkListResponse:
"""List all wireless links for a tenant with optional state and device filters.""" """List all wireless links for a tenant with optional state and device filters."""
await _check_tenant_access(current_user, tenant_id, db) 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( @router.get(
@@ -91,7 +95,9 @@ async def list_device_registrations(
) -> RegistrationListResponse: ) -> RegistrationListResponse:
"""Get latest wireless registration data for a device (most recent per MAC).""" """Get latest wireless registration data for a device (most recent per MAC)."""
await _check_tenant_access(current_user, tenant_id, db) 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( @router.get(

View File

@@ -98,9 +98,7 @@ async def get_alert_rule(
db=db, tenant_id=tenant_id, site_id=site_id, rule_id=rule_id db=db, tenant_id=tenant_id, site_id=site_id, rule_id=rule_id
) )
if not result: if not result:
raise HTTPException( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Alert rule not found")
status_code=status.HTTP_404_NOT_FOUND, detail="Alert rule not found"
)
return result 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 db=db, tenant_id=tenant_id, site_id=site_id, rule_id=rule_id, data=data
) )
if not result: if not result:
raise HTTPException( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Alert rule not found")
status_code=status.HTTP_404_NOT_FOUND, detail="Alert rule not found"
)
return result 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 db=db, tenant_id=tenant_id, site_id=site_id, rule_id=rule_id
) )
if not deleted: if not deleted:
raise HTTPException( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Alert rule not found")
status_code=status.HTTP_404_NOT_FOUND, detail="Alert rule not found"
)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@@ -166,9 +166,7 @@ async def unassign_device(
) -> None: ) -> None:
"""Remove a device from a site. Requires operator role or above.""" """Remove a device from a site. Requires operator role or above."""
await _check_tenant_access(current_user, tenant_id, db) await _check_tenant_access(current_user, tenant_id, db)
await site_service.remove_device_from_site( await site_service.remove_device_from_site(db=db, tenant_id=tenant_id, device_id=device_id)
db=db, tenant_id=tenant_id, device_id=device_id
)
@router.post( @router.post(

View File

@@ -43,8 +43,7 @@ async def _evaluate_condition(session, rule) -> bool: # noqa: ANN001
offline_result = await session.execute( offline_result = await session.execute(
text( text(
"SELECT count(*) AS cnt FROM devices " "SELECT count(*) AS cnt FROM devices WHERE site_id = :site_id AND is_online = false"
"WHERE site_id = :site_id AND is_online = false"
), ),
{"site_id": site_id}, {"site_id": site_id},
) )
@@ -55,8 +54,7 @@ async def _evaluate_condition(session, rule) -> bool: # noqa: ANN001
elif rule_type == "device_offline_count": elif rule_type == "device_offline_count":
offline_result = await session.execute( offline_result = await session.execute(
text( text(
"SELECT count(*) AS cnt FROM devices " "SELECT count(*) AS cnt FROM devices WHERE site_id = :site_id AND is_online = false"
"WHERE site_id = :site_id AND is_online = false"
), ),
{"site_id": site_id}, {"site_id": site_id},
) )
@@ -171,9 +169,11 @@ async def _evaluate_rules() -> None:
# Events with consecutive_hits < 2 are considered "pending" # Events with consecutive_hits < 2 are considered "pending"
# (not yet confirmed). On next evaluation if still met, # (not yet confirmed). On next evaluation if still met,
# consecutive_hits increments to 2 (confirmed alert). # consecutive_hits increments to 2 (confirmed alert).
severity = "critical" if rule.rule_type in ( severity = (
"device_offline_percent", "device_offline_count" "critical"
) else "warning" if rule.rule_type in ("device_offline_percent", "device_offline_count")
else "warning"
)
await session.execute( await session.execute(
text(""" text("""

View File

@@ -122,9 +122,7 @@ async def _subscribe_with_retry(js: JetStreamContext) -> None:
durable="api-interface-consumer", durable="api-interface-consumer",
stream="DEVICE_EVENTS", stream="DEVICE_EVENTS",
) )
logger.info( logger.info("NATS: subscribed to device.interfaces.> (durable: api-interface-consumer)")
"NATS: subscribed to device.interfaces.> (durable: api-interface-consumer)"
)
return return
except Exception as exc: except Exception as exc:
if attempt < max_attempts: if attempt < max_attempts:

View File

@@ -31,8 +31,8 @@ _link_discovery_client: Optional[NATSClient] = None
# Configurable thresholds for link state transitions # Configurable thresholds for link state transitions
DEGRADED_SIGNAL_THRESHOLD = -80 # dBm — signals weaker than this mark link as degraded DEGRADED_SIGNAL_THRESHOLD = -80 # dBm — signals weaker than this mark link as degraded
CONSECUTIVE_MISS_THRESHOLD = 3 # Missed polls before marking link as down CONSECUTIVE_MISS_THRESHOLD = 3 # Missed polls before marking link as down
STALE_HOURS = 24 # Hours after down before marking link as stale 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 # Mark stale: any links in 'down' state where last_seen > STALE_HOURS ago
await session.execute( await session.execute(
text(""" text(
"""
UPDATE wireless_links UPDATE wireless_links
SET state = 'stale', updated_at = NOW() SET state = 'stale', updated_at = NOW()
WHERE ap_device_id = :ap_device_id WHERE ap_device_id = :ap_device_id
AND tenant_id = :tenant_id AND tenant_id = :tenant_id
AND state = 'down' AND state = 'down'
AND last_seen < NOW() - INTERVAL ':stale_hours hours' 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, "ap_device_id": device_id,
"tenant_id": tenant_id, "tenant_id": tenant_id,

View File

@@ -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.""" """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 online_percent = (online_count / device_count * 100) if device_count > 0 else 0.0
return SiteResponse( 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: 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.""" """Fetch a site by id and tenant, or raise 404."""
result = await db.execute( result = await db.execute(select(Site).where(Site.id == site_id, Site.tenant_id == tenant_id))
select(Site).where(Site.id == site_id, Site.tenant_id == tenant_id)
)
site = result.scalar_one_or_none() site = result.scalar_one_or_none()
if not site: if not site:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Site not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Site not found")