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
+16 -16
View File
@@ -9,9 +9,9 @@ import shutil
import string
import subprocess as sp
import threading
from collections.abc import Callable
from enum import Enum
from pathlib import Path
from typing import Callable, Optional
import pytz # type: ignore[import-untyped]
from peewee import DoesNotExist
@@ -209,17 +209,17 @@ class RecordingExporter(threading.Thread):
config: FrigateConfig,
id: str,
camera: str,
name: Optional[str],
image: Optional[str],
name: str | None,
image: str | None,
start_time: int,
end_time: int,
playback_source: PlaybackSourceEnum,
export_case_id: Optional[str] = None,
ffmpeg_input_args: Optional[str] = None,
ffmpeg_output_args: Optional[str] = None,
export_case_id: str | None = None,
ffmpeg_input_args: str | None = None,
ffmpeg_output_args: str | None = None,
cpu_fallback: bool = False,
chapters: Optional[ChaptersEnum] = None,
on_progress: Optional[Callable[[str, float], None]] = None,
chapters: ChaptersEnum | None = None,
on_progress: Callable[[str, float], None] | None = None,
) -> None:
super().__init__()
self.config = config
@@ -283,7 +283,7 @@ class RecordingExporter(threading.Thread):
return input_duration * factor
def _sum_source_duration_seconds(self) -> Optional[float]:
def _sum_source_duration_seconds(self) -> float | None:
"""Sum saved-video seconds inside [start_time, end_time].
Queries Recordings or Previews depending on the playback source,
@@ -383,7 +383,7 @@ class RecordingExporter(threading.Thread):
def _chapter_metadata_path(self) -> str:
return os.path.join(CACHE_DIR, f"export_chapters_{self.export_id}.txt")
def _build_chapter_metadata_file(self, recordings: list) -> Optional[str]:
def _build_chapter_metadata_file(self, recordings: list) -> str | None:
"""Write an FFmpeg metadata file with chapters for review items in range.
Chapter offsets are computed in *output time*: the VOD endpoint
@@ -514,7 +514,7 @@ class RecordingExporter(threading.Thread):
def _build_recording_segment_chapter_metadata_file(
self, recordings: list
) -> Optional[str]:
) -> str | None:
"""Write an FFmpeg metadata file with one chapter per recording segment.
Each chapter's title is the segment's wallclock start time in
@@ -530,14 +530,14 @@ class RecordingExporter(threading.Thread):
return None
tz_name = self.config.ui.timezone
tz: Optional[datetime.tzinfo] = None
tz: datetime.tzinfo | None = None
if tz_name:
try:
tz = pytz.timezone(tz_name)
except pytz.UnknownTimeZoneError:
tz = None
if tz is None:
tz = datetime.timezone.utc
tz = datetime.UTC
chapter_blocks: list[str] = []
output_offset_ms = 0
@@ -591,7 +591,7 @@ class RecordingExporter(threading.Thread):
if (
self.start_time
< datetime.datetime.now(datetime.timezone.utc)
< datetime.datetime.now(datetime.UTC)
.replace(minute=0, second=0, microsecond=0)
.timestamp()
):
@@ -767,7 +767,7 @@ class RecordingExporter(threading.Thread):
# add metadata
title = f"Frigate Recording for {self.camera}, {self.get_datetime_from_timestamp(self.start_time)} - {self.get_datetime_from_timestamp(self.end_time)}"
creation_time = datetime.datetime.fromtimestamp(
self.start_time, tz=datetime.timezone.utc
self.start_time, tz=datetime.UTC
).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
ffmpeg_cmd.extend(
[
@@ -876,7 +876,7 @@ class RecordingExporter(threading.Thread):
# add metadata
title = f"Frigate Preview for {self.camera}, {self.get_datetime_from_timestamp(self.start_time)} - {self.get_datetime_from_timestamp(self.end_time)}"
creation_time = datetime.datetime.fromtimestamp(
self.start_time, tz=datetime.timezone.utc
self.start_time, tz=datetime.UTC
).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
ffmpeg_cmd.extend(
[
+9 -11
View File
@@ -11,7 +11,7 @@ import time
from collections import defaultdict
from multiprocessing.synchronize import Event as MpEvent
from pathlib import Path
from typing import Any, Optional, Tuple
from typing import Any
import numpy as np
import psutil
@@ -100,7 +100,7 @@ class RecordingMaintainer(threading.Thread):
self.stop_event = stop_event
self.object_recordings_info: dict[str, list] = defaultdict(list)
self.audio_recordings_info: dict[str, list] = defaultdict(list)
self.end_time_cache: dict[str, Tuple[datetime.datetime, float]] = {}
self.end_time_cache: dict[str, tuple[datetime.datetime, float]] = {}
self.unexpected_cache_files_logged: bool = False
async def move_files(self) -> None:
@@ -127,7 +127,7 @@ class RecordingMaintainer(threading.Thread):
start_time = datetime.datetime.strptime(
date, CACHE_SEGMENT_FORMAT
).astimezone(datetime.timezone.utc)
).astimezone(datetime.UTC)
if (
camera not in newest_cache_segments
or start_time > newest_cache_segments[camera]["start_time"]
@@ -187,7 +187,7 @@ class RecordingMaintainer(threading.Thread):
# important that start_time is utc because recordings are stored and compared in utc
start_time = datetime.datetime.strptime(
date, CACHE_SEGMENT_FORMAT
).astimezone(datetime.timezone.utc)
).astimezone(datetime.UTC)
grouped_recordings[camera].append(
{
@@ -305,9 +305,7 @@ class RecordingMaintainer(threading.Thread):
self._expire_stale_recordings_info(grouped_recordings)
recordings_to_insert: list[Optional[dict[str, Any]]] = await asyncio.gather(
*tasks
)
recordings_to_insert: list[dict[str, Any] | None] = await asyncio.gather(*tasks)
# fire and forget recordings entries
self.requestor.send_data(
@@ -336,7 +334,7 @@ class RecordingMaintainer(threading.Thread):
async def validate_and_move_segment(
self, camera: str, reviews: Any, recording: dict[str, Any]
) -> Optional[dict[str, Any]]:
) -> dict[str, Any] | None:
cache_path: str = recording["cache_path"]
start_time: datetime.datetime = recording["start_time"]
@@ -413,7 +411,7 @@ class RecordingMaintainer(threading.Thread):
if (
datetime.datetime.fromtimestamp(
most_recently_processed_frame_time
).astimezone(datetime.timezone.utc)
).astimezone(datetime.UTC)
>= end_time
):
record_mode = (
@@ -495,7 +493,7 @@ class RecordingMaintainer(threading.Thread):
)
retain_cutoff = datetime.datetime.fromtimestamp(
most_recently_processed_frame_time - record_config.event_pre_capture
).astimezone(datetime.timezone.utc)
).astimezone(datetime.UTC)
if end_time < retain_cutoff:
self.drop_segment(cache_path)
@@ -621,7 +619,7 @@ class RecordingMaintainer(threading.Thread):
duration: float,
cache_path: str,
segment_info: SegmentInfo,
) -> Optional[dict[str, Any]]:
) -> dict[str, Any] | None:
# directory will be in utc due to start_time being in utc
directory = os.path.join(
RECORD_DIR,