fix mypy errors from types-peewee 4.0 (#24323)

types-peewee 4.0 types model fields precisely, so 17 `type: ignore` comments and 2 `cast(str, ...)` calls are no longer needed. Its stubs type `.namedtuples()` and `.dicts()` queries as returning model instances, so the review cleanup reads namedtuple fields by name and the storage usage query casts its dict rows. `start_time` is declared `DateTimeField` but stores unix timestamps, so two reads cast it like `debug_replay.py` already does. `Export` gets an annotation for the `export_case_id` attribute peewee adds at runtime.
This commit is contained in:
Josh Hawkins
2026-09-14 07:52:23 -05:00
committed by GitHub
parent 5be162a22c
commit 3931ab74a8
12 changed files with 41 additions and 31 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
ruff == 0.15.20
# types
types-peewee == 3.17.*
types-peewee == 4.0.*
@@ -253,7 +253,7 @@ class ObjectDescriptionProcessor(PostProcessorApi):
# Crop snapshot based on region
# provide full image if region doesn't exist (manual events)
height, width = img.shape[:2]
x1_rel, y1_rel, width_rel, height_rel = event.data.get( # type: ignore[attr-defined]
x1_rel, y1_rel, width_rel, height_rel = event.data.get(
"region", [0, 0, 1, 1]
)
x1, y1 = int(x1_rel * width), int(y1_rel * height)
@@ -8,7 +8,7 @@ import os
import shutil
import threading
from pathlib import Path
from typing import Any
from typing import Any, cast
import cv2
from peewee import DoesNotExist
@@ -528,7 +528,8 @@ class ReviewDescriptionProcessor(PostProcessorApi):
.get()
)
time_in_segment = ts - recording.start_time
# start_time is a DateTimeField holding a unix timestamp
time_in_segment = ts - cast(float, recording.start_time)
return get_image_from_recording(
self.config.ffmpeg,
recording.path,
@@ -237,7 +237,7 @@ class SemanticTriggerProcessor(PostProcessorApi):
return
# Skip the event if not an object
if event.data.get("type") != "object": # type: ignore[attr-defined]
if event.data.get("type") != "object":
return
thumbnail_bytes = get_event_thumbnail_bytes(event)
+5 -5
View File
@@ -37,7 +37,7 @@ class EventCleanup(threading.Thread):
if self.removed_camera_labels is None:
self.removed_camera_labels = list(
Event.select(Event.label)
.where(Event.camera.not_in(self.camera_keys)) # type: ignore[arg-type,call-arg,misc]
.where(Event.camera.not_in(self.camera_keys))
.distinct()
.execute()
)
@@ -89,7 +89,7 @@ class EventCleanup(threading.Thread):
Event.thumbnail,
)
.where(
Event.camera.not_in(self.camera_keys), # type: ignore[arg-type,call-arg,misc]
Event.camera.not_in(self.camera_keys),
Event.start_time < expire_after,
Event.label == event.label,
Event.retain_indefinitely == False,
@@ -111,7 +111,7 @@ class EventCleanup(threading.Thread):
# update the clips attribute for the db entry
query = Event.select(Event.id).where(
Event.camera.not_in(self.camera_keys), # type: ignore[arg-type,call-arg,misc]
Event.camera.not_in(self.camera_keys),
Event.start_time < expire_after,
Event.label == event.label,
Event.retain_indefinitely == False,
@@ -218,7 +218,7 @@ class EventCleanup(threading.Thread):
Event.camera,
)
.where(
Event.camera.not_in(self.camera_keys), # type: ignore[arg-type,call-arg,misc]
Event.camera.not_in(self.camera_keys),
Event.start_time < expire_after,
Event.retain_indefinitely == False,
)
@@ -249,7 +249,7 @@ class EventCleanup(threading.Thread):
# update the clips attribute for the db entry
query = Event.select(Event.id).where(
Event.camera.not_in(self.camera_keys), # type: ignore[arg-type,call-arg,misc]
Event.camera.not_in(self.camera_keys),
Event.start_time < expire_after,
Event.retain_indefinitely == False,
)
+2 -2
View File
@@ -216,11 +216,11 @@ class ExportDebugReplaySource(DebugReplaySource):
"""
def __init__(self, export: Export, duration: float) -> None:
self._camera = cast(str, export.camera)
self._camera = export.camera
# Export.date is declared DateTimeField but Frigate writes raw unix
# timestamps to the column.
self._start_ts = float(cast(Any, export.date))
self._video_path = cast(str, export.video_path)
self._video_path = export.video_path
self._duration = duration
@property
+2
View File
@@ -109,6 +109,8 @@ class Export(Model):
backref="exports",
column_name="export_case_id",
)
# peewee adds this accessor for the export_case column at runtime
export_case_id: str | None
class ReviewSegment(Model):
+3 -3
View File
@@ -186,7 +186,7 @@ class NoticeRegistry:
deleted = (
Notice.delete()
.where(
Notice.kind.in_(camera_kinds), # type: ignore[call-arg, arg-type, misc]
Notice.kind.in_(camera_kinds),
Notice.scope == camera,
)
.execute()
@@ -257,7 +257,7 @@ class NoticeRegistry:
"""Dismissed config and stream check rows, newest first."""
rows = (
Notice.select()
.where(Notice.kind.in_(list(CHECK_KINDS))) # type: ignore[call-arg, arg-type, misc]
.where(Notice.kind.in_(list(CHECK_KINDS)))
.order_by(Notice.dismissed_at.desc())
)
return [{"id": row.id, "dismissed_at": row.dismissed_at} for row in rows]
@@ -351,7 +351,7 @@ class NoticeRegistry:
)
Notice.delete().where(
Notice.kind == kind,
Notice.id.not_in(newest), # type: ignore[call-arg, misc]
Notice.id.not_in(newest),
).execute()
def _bump_occurrences(self, kind: str, count: int, now: float) -> None:
+6 -5
View File
@@ -5,6 +5,7 @@ import itertools
import logging
import os
import threading
from collections.abc import Iterable
from multiprocessing.synchronize import Event as MpEvent
from pathlib import Path
from typing import Any
@@ -28,7 +29,7 @@ logger = logging.getLogger(__name__)
def _filter_reviews_for_pass(
reviews: list[Any],
reviews: Iterable[Any],
now: datetime.datetime,
alerts_days: float,
detections_days: float,
@@ -121,14 +122,14 @@ class RecordingCleanup(threading.Thread):
)
maybe_empty_dirs = set()
thumbs_to_delete = list(map(lambda x: x[1], expired_reviews))
for thumb_path in thumbs_to_delete:
thumb_path = Path(thumb_path)
thumbs_to_delete = list(map(lambda x: x.thumb_path, expired_reviews))
for thumb in thumbs_to_delete:
thumb_path = Path(thumb)
thumb_path.unlink(missing_ok=True)
maybe_empty_dirs.add(thumb_path.parent)
max_deletes = 100000
deleted_reviews_list = list(map(lambda x: x[0], expired_reviews))
deleted_reviews_list = list(map(lambda x: x.id, expired_reviews))
for i in range(0, len(deleted_reviews_list), max_deletes):
ReviewSegment.delete().where(
ReviewSegment.id << deleted_reviews_list[i : i + max_deletes]
+5 -2
View File
@@ -14,7 +14,7 @@ from collections.abc import Callable
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Any
from typing import Any, cast
import pytz # type: ignore[import-untyped]
from pathvalidate import sanitize_filename
@@ -1055,7 +1055,10 @@ class RecordingExporter(threading.Thread):
except DoesNotExist:
return ""
diff = max(0.0, float(self.start_time) - float(preview.start_time))
# start_time is a DateTimeField holding a unix timestamp
diff = max(
0.0, float(self.start_time) - float(cast(Any, preview.start_time))
)
ffmpeg_cmd = [
"/usr/lib/ffmpeg/8.0/bin/ffmpeg", # hardcode path for exports thumbnail due to missing libwebp support
"-hide_banner",
+5 -2
View File
@@ -3,8 +3,10 @@
import logging
import shutil
import threading
from collections.abc import Iterable
from multiprocessing.synchronize import Event as MpEvent
from pathlib import Path
from typing import Any, cast
from peewee import SQL, Case, fn
@@ -191,14 +193,15 @@ class StorageMaintainer(threading.Thread):
stream_usages = {
row["stream_type"]: row["usage"] or 0
for row in (
for row in cast(
Iterable[dict[str, Any]],
Recordings.select(
Recordings.stream_type,
fn.SUM(Recordings.segment_size).alias("usage"),
)
.where(Recordings.camera == camera, Recordings.segment_size != 0)
.group_by(Recordings.stream_type)
.dicts()
.dicts(),
)
}
stream_bandwidths = self.camera_storage_stats.get(camera, {}).get(
+7 -7
View File
@@ -406,12 +406,12 @@ class TrackedObjectProcessor(threading.Thread):
tracked_obj.obj_data["sub_label"] = (sub_label, score)
if event:
event.sub_label = sub_label # type: ignore[assignment]
event.sub_label = sub_label
data = event.data
if sub_label is None:
data["sub_label_score"] = None # type: ignore[index]
data["sub_label_score"] = None
elif score is not None:
data["sub_label_score"] = score # type: ignore[index]
data["sub_label_score"] = score
event.data = data
event.save()
@@ -440,7 +440,7 @@ class TrackedObjectProcessor(threading.Thread):
objects_list = []
sub_labels = set()
events = Event.select(Event.id, Event.label, Event.sub_label).where(
Event.id.in_(detection_ids) # type: ignore[call-arg, misc]
Event.id.in_(detection_ids)
)
for det_event in events:
if det_event.sub_label:
@@ -506,11 +506,11 @@ class TrackedObjectProcessor(threading.Thread):
if event:
data = event.data
data[field_name] = field_value # type: ignore[index]
data[field_name] = field_value
if field_value is None:
data[f"{field_name}_score"] = None # type: ignore[index]
data[f"{field_name}_score"] = None
elif score is not None:
data[f"{field_name}_score"] = score # type: ignore[index]
data[f"{field_name}_score"] = score
event.data = data
event.save()