Increase ruff coverage (#23644)
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / Jetson Jetpack 6 (push) Waiting to run

* Pin ruff

* Add python upgrade fixes

This enables python upgrade checks in ruff to look for deprecated types and patterns. This namely fixes:
- usage of deprecated `Typing` which is now built in
- some specific exceptions which are caught and have new aliases

Some specific UP checks were also ignored as they are stylistic / unimportant and likely to cause bugs

* Remove async blocking calls

Use asyncio.to_thread on two remaining blocking calls to fix hanging event thread loop. Enable this specific rule to block it in the future.

* Use proper logging mechanism

* Correctly format logs

* Raise with context

When raising an exception include the from context to improve debugging

* Cleanup
This commit is contained in:
Nicolas Mowen
2026-07-06 12:28:02 -05:00
committed by GitHub
parent 455b8687e8
commit 4ee12e6237
169 changed files with 1053 additions and 1150 deletions
+40 -40
View File
@@ -12,7 +12,7 @@ import subprocess as sp
import time
import traceback
from datetime import datetime
from typing import Any, List, Optional, Tuple
from typing import Any
import cv2
import psutil
@@ -59,7 +59,7 @@ def get_cgroups_version() -> str:
return "unknown"
try:
with open("/proc/mounts", "r") as f:
with open("/proc/mounts") as f:
mounts = f.readlines()
for mount in mounts:
@@ -89,7 +89,7 @@ def get_docker_memlimit_bytes() -> int:
memlimit_path = "/sys/fs/cgroup/memory.max"
try:
with open(memlimit_path, "r") as f:
with open(memlimit_path) as f:
value = f.read().strip()
if value.isnumeric():
@@ -127,7 +127,7 @@ def get_cpu_stats() -> dict[str, dict]:
if not any(keyword in cmdline for keyword in keywords):
continue
with open(f"/proc/{pid}/stat", "r") as f:
with open(f"/proc/{pid}/stat") as f:
stats = f.readline().split()
utime = int(stats[13])
stime = int(stats[14])
@@ -146,7 +146,7 @@ def get_cpu_stats() -> dict[str, dict]:
process_usage_sec = process_utime_sec + process_stime_sec
cpu_average_usage = process_usage_sec * 100 // process_elapsed_sec
with open(f"/proc/{pid}/statm", "r") as f:
with open(f"/proc/{pid}/statm") as f:
mem_stats = f.readline().split()
mem_res = int(mem_stats[1]) * os.sysconf("SC_PAGE_SIZE") / 1024
@@ -171,7 +171,7 @@ def get_physical_interfaces(interfaces) -> list:
if not interfaces:
return []
with open("/proc/net/dev", "r") as file:
with open("/proc/net/dev") as file:
lines = file.readlines()
physical_interfaces = []
@@ -238,7 +238,7 @@ def is_vaapi_amd_driver() -> bool:
return any("AMD Radeon Graphics" in line for line in output)
def get_amd_gpu_stats() -> Optional[dict[str, str]]:
def get_amd_gpu_stats() -> dict[str, str] | None:
"""Get stats using radeontop."""
radeontop_command = ["radeontop", "-d", "-", "-l", "1"]
@@ -287,7 +287,7 @@ _XE_ENGINE_KEYS = {
}
def _resolve_intel_gpu_pdev(device: Optional[str]) -> Optional[str]:
def _resolve_intel_gpu_pdev(device: str | None) -> str | None:
"""Map a configured GPU hint (/dev/dri/card1, renderD128, or a PCI bus
address) to its drm-pdev string so we can filter fdinfo entries to that
device. Returns None when no hint is supplied or it cannot be resolved."""
@@ -304,7 +304,7 @@ def _resolve_intel_gpu_pdev(device: Optional[str]) -> Optional[str]:
return None
def _read_intel_drm_fdinfo(target_pdev: Optional[str]) -> dict:
def _read_intel_drm_fdinfo(target_pdev: str | None) -> dict:
"""Snapshot DRM fdinfo for every Intel client visible in /proc.
Returns a dict keyed by (pdev, drm-client-id, pid) so the same context
@@ -394,8 +394,8 @@ def _read_intel_drm_fdinfo(target_pdev: Optional[str]) -> dict:
def get_intel_gpu_stats(
intel_gpu_device: Optional[str],
) -> Optional[dict[str, dict[str, Any]]]:
intel_gpu_device: str | None,
) -> dict[str, dict[str, Any]] | None:
"""Get stats by reading DRM fdinfo files, bucketed per-pdev.
Each DRM client FD exposes monotonic per-engine busy counters via
@@ -509,12 +509,12 @@ def get_intel_gpu_stats(
return results
def get_openvino_npu_stats() -> Optional[dict[str, str]]:
def get_openvino_npu_stats() -> dict[str, str] | None:
"""Get NPU stats using openvino."""
NPU_RUNTIME_PATH = "/sys/devices/pci0000:00/0000:00:0b.0/power/runtime_active_time"
try:
with open(NPU_RUNTIME_PATH, "r") as f:
with open(NPU_RUNTIME_PATH) as f:
initial_runtime = float(f.read().strip())
initial_time = time.time()
@@ -523,7 +523,7 @@ def get_openvino_npu_stats() -> Optional[dict[str, str]]:
time.sleep(1.0)
# Read runtime value again
with open(NPU_RUNTIME_PATH, "r") as f:
with open(NPU_RUNTIME_PATH) as f:
current_runtime = float(f.read().strip())
current_time = time.time()
@@ -542,10 +542,10 @@ def get_openvino_npu_stats() -> Optional[dict[str, str]]:
return None
def get_rockchip_gpu_stats() -> Optional[dict[str, str | float]]:
def get_rockchip_gpu_stats() -> dict[str, str | float] | None:
"""Get GPU stats using rk."""
try:
with open("/sys/kernel/debug/rkrga/load", "r") as f:
with open("/sys/kernel/debug/rkrga/load") as f:
content = f.read()
except FileNotFoundError:
return None
@@ -563,7 +563,7 @@ def get_rockchip_gpu_stats() -> Optional[dict[str, str | float]]:
stats: dict[str, str | float] = {"gpu": average_load, "mem": "-%"}
try:
with open("/sys/class/thermal/thermal_zone5/temp", "r") as f:
with open("/sys/class/thermal/thermal_zone5/temp") as f:
line = f.readline().strip()
stats["temp"] = round(int(line) / 1000, 1)
except (FileNotFoundError, OSError, ValueError):
@@ -572,10 +572,10 @@ def get_rockchip_gpu_stats() -> Optional[dict[str, str | float]]:
return stats
def get_rockchip_npu_stats() -> Optional[dict[str, float | str]]:
def get_rockchip_npu_stats() -> dict[str, float | str] | None:
"""Get NPU stats using rk."""
try:
with open("/sys/kernel/debug/rknpu/load", "r") as f:
with open("/sys/kernel/debug/rknpu/load") as f:
npu_output = f.read()
if "Core0:" in npu_output:
@@ -595,7 +595,7 @@ def get_rockchip_npu_stats() -> Optional[dict[str, float | str]]:
stats: dict[str, float | str] = {"npu": mean, "mem": "-%"}
try:
with open("/sys/class/thermal/thermal_zone6/temp", "r") as f:
with open("/sys/class/thermal/thermal_zone6/temp") as f:
line = f.readline().strip()
stats["temp"] = round(int(line) / 1000, 1)
except (FileNotFoundError, OSError, ValueError):
@@ -604,7 +604,7 @@ def get_rockchip_npu_stats() -> Optional[dict[str, float | str]]:
return stats
def get_axcl_npu_stats() -> Optional[dict[str, str | float]]:
def get_axcl_npu_stats() -> dict[str, str | float] | None:
"""Get NPU stats using axcl."""
# Check if axcl-smi exists
axcl_smi_path = "/usr/bin/axcl/axcl-smi"
@@ -721,18 +721,18 @@ def get_nvidia_gpu_stats() -> dict[int, dict]:
return results
def get_jetson_stats() -> Optional[dict[int, dict]]:
def get_jetson_stats() -> dict[int, dict] | None:
results = {}
try:
results["mem"] = "-" # no discrete gpu memory
if os.path.exists("/sys/devices/gpu.0/load"):
with open("/sys/devices/gpu.0/load", "r") as f:
with open("/sys/devices/gpu.0/load") as f:
gpuload = float(f.readline()) / 10
results["gpu"] = f"{gpuload}%"
elif os.path.exists("/sys/devices/platform/gpu.0/load"):
with open("/sys/devices/platform/gpu.0/load", "r") as f:
with open("/sys/devices/platform/gpu.0/load") as f:
gpuload = float(f.readline()) / 10
results["gpu"] = f"{gpuload}%"
else:
@@ -793,7 +793,7 @@ def get_hailo_temps() -> dict[str, float]:
def is_go2rtc_arbitrary_exec_allowed() -> bool:
"""Read the GO2RTC_ALLOW_ARBITRARY_EXEC override from env, docker
secrets, or the Home Assistant add-on options file."""
raw: Optional[str] = None
raw: str | None = None
if "GO2RTC_ALLOW_ARBITRARY_EXEC" in os.environ:
raw = os.environ.get("GO2RTC_ALLOW_ARBITRARY_EXEC")
elif (
@@ -839,7 +839,7 @@ def ffprobe_stream(ffmpeg, path: str, detailed: bool = False) -> sp.CompletedPro
else:
format_entries = None
def run(rtsp_transport: Optional[str] = None) -> sp.CompletedProcess:
def run(rtsp_transport: str | None = None) -> sp.CompletedProcess:
cmd = [ffmpeg.ffprobe_path]
if rtsp_transport:
cmd += ["-rtsp_transport", rtsp_transport]
@@ -883,14 +883,14 @@ KEYFRAME_PROBE_WINDOW_SECONDS = 20
KEYFRAME_GAP_WARNING_SECONDS = 4.0
def parse_keyframe_packets(output: str) -> Tuple[List[float], Optional[float]]:
def parse_keyframe_packets(output: str) -> tuple[list[float], float | None]:
"""Parse ffprobe CSV `pts_time,flags` output.
Returns the presentation timestamps of keyframes (flags containing "K")
and the maximum timestamp observed across all packets.
"""
keyframe_pts: List[float] = []
max_pts: Optional[float] = None
keyframe_pts: list[float] = []
max_pts: float | None = None
for line in output.splitlines():
parts = line.split(",")
@@ -909,7 +909,7 @@ def parse_keyframe_packets(output: str) -> Tuple[List[float], Optional[float]]:
def classify_keyframe_gaps(
keyframe_pts: List[float], segment_time: int
keyframe_pts: list[float], segment_time: int
) -> dict[str, Any]:
"""Classify keyframe spacing for recording suitability.
@@ -990,7 +990,7 @@ async def analyze_record_keyframes(
stderr=asyncio.subprocess.PIPE,
)
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=window + 15)
except asyncio.TimeoutError:
except TimeoutError:
logger.warning("Keyframe probe timed out for record stream")
proc.kill()
return classify_keyframe_gaps([], segment_time)
@@ -1004,7 +1004,7 @@ async def analyze_record_keyframes(
return result
def vainfo_hwaccel(device_name: Optional[str] = None) -> sp.CompletedProcess:
def vainfo_hwaccel(device_name: str | None = None) -> sp.CompletedProcess:
"""Run vainfo."""
if not device_name:
cmd = ["vainfo"]
@@ -1081,8 +1081,8 @@ async def get_video_properties(
) -> dict[str, Any]:
async def probe_with_ffprobe(
url: str,
rtsp_transport: Optional[str] = None,
) -> tuple[bool, int, int, Optional[str], float]:
rtsp_transport: str | None = None,
) -> tuple[bool, int, int, str | None, float]:
"""Fallback using ffprobe: returns (valid, width, height, codec, duration)."""
cmd = [ffmpeg.ffprobe_path]
if rtsp_transport:
@@ -1105,7 +1105,7 @@ async def get_video_properties(
)
try:
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=6)
except asyncio.TimeoutError:
except TimeoutError:
logger.info(
"ffprobe timed out while probing %s (transport=%s)",
clean_camera_user_pass(url),
@@ -1137,7 +1137,7 @@ async def get_video_properties(
except (json.JSONDecodeError, ValueError, KeyError, sp.SubprocessError):
return False, 0, 0, None, -1
def probe_with_cv2(url: str) -> tuple[bool, int, int, Optional[str], float]:
def probe_with_cv2(url: str) -> tuple[bool, int, int, str | None, float]:
"""Primary attempt using cv2: returns (valid, width, height, fourcc, duration)."""
cap = cv2.VideoCapture(url)
if not cap.isOpened():
@@ -1197,10 +1197,10 @@ async def get_video_properties(
def process_logs(
contents: str,
service: Optional[str] = None,
start: Optional[int] = None,
end: Optional[int] = None,
) -> Tuple[int, List[str]]:
service: str | None = None,
start: int | None = None,
end: int | None = None,
) -> tuple[int, list[str]]:
log_lines = []
last_message = None
last_timestamp = None