Update deps (#24623)

* bump backend and frontend dependencies

Bumps every outdated dependency that passed testing, with the code changes each one needs.

- peewee 4.5 and peewee_migrate 2.3: use SqliteDatabase in place of the removed SqliteExtDatabase, and fix types for peewee's bundled stubs
- mypy 2.4.0: remove unused type ignores and add the annotations mypy 2 needs
- pandas 3.0: convert the motion activity index with as_unit("s"), because pandas 3 infers second resolution for whole-second timestamps and the old // 10**9 returned 1
- transformers 5.19: load Jina tokenizers from the saved directory with trust_remote_code off and use CLIPImageProcessor, since v5 removed CLIPFeatureExtractor and remote code pulled in torch
- tensorflow-cpu 2.21: pin protobuf to 7.36 in the TensorRT amd64 image, because the old 3.20.3 pin was installed over TensorFlow and broke its import
- pydantic 2.13 and google-genai 2.29: regenerate the API spec
- ruff 0.16: set an explicit lint select, since 0.16 widened the default rules
- react-router-dom 7: set useTransitions={false} on BrowserRouter to keep v6 update timing, which useSearchEffect relies on
- typescript 6: drop baseUrl and add node types in tsconfig
- radix: update to the latest release and remove the slot patch, which upstream now includes
- apexcharts 7.8: guard the tooltip formatter against null values
- cryptography 50, joserfc 1.7, pywebpush 2.5, openai 3.26, aiohttp, uvicorn, onvif-zeep-async, scipy, framer-motion 14, react-day-picker 10 and other minor and patch bumps

* silence httpx2 request logging

* redownload an incomplete jina tokenizer

An interrupted tokenizer download left the Hugging Face cache directory without the saved tokenizer files, and since the download is skipped whenever that directory exists, every restart failed to load the tokenizer. The Jina constructors now remove a tokenizer directory that has no tokenizer_config.json so the download runs again.
This commit is contained in:
Josh Hawkins
2026-10-10 15:01:33 -05:00
committed by GitHub
parent e8e6326d37
commit 79177db969
39 changed files with 953 additions and 1143 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
ruff == 0.15.20
ruff == 0.16.10
# types
types-peewee == 4.0.*
+29 -29
View File
@@ -1,66 +1,66 @@
aiofiles == 25.1.*
click == 8.5.*
# FastAPI
aiohttp == 3.12.*
aiohttp == 3.14.*
starlette == 1.7.*
starlette-context == 0.5.*
fastapi[standard-no-fastapi-cloud-cli] == 0.142.*
uvicorn == 0.52.*
uvicorn == 0.54.*
slowapi == 0.1.*
joserfc == 1.6.*
cryptography == 46.0.*
joserfc == 1.7.*
cryptography == 50.0.*
pathvalidate == 3.3.*
markupsafe == 3.0.*
python-multipart == 0.0.31
python-multipart == 0.0.32
# Classification Model Training
tensorflow == 2.19.* ; platform_machine == 'aarch64'
tensorflow-cpu == 2.19.* ; platform_machine == 'x86_64'
tensorflow-cpu == 2.21.* ; platform_machine == 'x86_64'
# General
mypy == 1.6.1
onvif-zeep-async == 4.0.*
mypy == 2.4.0
onvif-zeep-async == 4.3.*
paho-mqtt == 2.1.*
pandas == 2.2.*
peewee == 3.17.*
peewee_migrate == 1.14.*
psutil == 7.1.*
pydantic == 2.10.*
pandas == 3.0.*
peewee == 4.5.*
peewee_migrate == 2.3.*
psutil == 7.2.*
pydantic == 2.13.*
git+https://github.com/fbcotter/py3nvml#egg=py3nvml
pytz == 2025.*
pyzmq == 27.1.*
pytz == 2026.*
pyzmq == 27.2.*
ruamel.yaml == 0.19.*
tzlocal == 5.2
requests == 2.33.*
types-requests == 2.32.*
tzlocal == 5.4.4
requests == 2.34.*
types-requests == 2.33.*
norfair == 2.3.*
setproctitle == 1.3.*
ws4py == 0.5.*
ws4py == 0.6.*
unidecode == 1.4.*
titlecase == 2.4.*
# Image Manipulation
numpy == 1.26.*
opencv-python-headless == 4.11.0.*
opencv-contrib-python == 4.11.0.*
scipy == 1.16.*
scipy == 1.17.*
# OpenVino & ONNX
openvino == 2025.4.*
onnxruntime == 1.30.*
# Embeddings
transformers == 4.45.*
transformers == 5.19.*
# Generative AI
google-genai == 1.58.*
google-genai == 2.29.*
ollama == 0.6.*
openai == 1.65.*
openai == 3.26.*
# push notifications
py-vapid == 1.9.4
pywebpush == 2.0.*
pywebpush == 2.5.*
# alpr
pyclipper == 1.4.*
shapely == 2.0.*
rapidfuzz==3.12.*
shapely == 2.2.*
rapidfuzz==3.14.*
# HailoRT
argcomplete==3.7.*
contextlib2==0.6.*
future==0.18.*
contextlib2==21.6.*
future==1.0.*
netaddr==1.3.*
netifaces==0.10.*
prometheus-client == 0.26.*
@@ -71,6 +71,6 @@ tflite_runtime @ https://github.com/feranick/TFlite-builds/releases/download/v2.
sherpa-onnx==1.13.*
faster-whisper==1.2.*
librosa==0.11.*
soundfile==0.13.*
soundfile==0.14.*
# Memory profiling
memray == 1.20.*
+1 -1
View File
@@ -14,4 +14,4 @@ nvidia-nccl-cu12==2.26.2.post1; platform_machine == 'x86_64'
nvidia-nvjitlink-cu12==12.8.93; platform_machine == 'x86_64'
onnx==1.16.*; platform_machine == 'x86_64'
onnxruntime-gpu==1.24.*; platform_machine == 'x86_64'
protobuf==3.20.3; platform_machine == 'x86_64'
protobuf==7.36.*; platform_machine == 'x86_64'
+24 -3
View File
@@ -1088,6 +1088,7 @@ paths:
application/json:
schema:
type: object
additionalProperties: true
title: Body
responses:
'200':
@@ -1131,6 +1132,7 @@ paths:
application/json:
schema:
type: object
additionalProperties: true
title: Body
responses:
'200':
@@ -1285,6 +1287,7 @@ paths:
application/json:
schema:
type: object
additionalProperties: true
title: Body
responses:
'200':
@@ -1644,6 +1647,7 @@ paths:
application/json:
schema:
type: object
additionalProperties: true
title: Body
responses:
'200':
@@ -1691,6 +1695,7 @@ paths:
application/json:
schema:
type: object
additionalProperties: true
title: Body
responses:
'200':
@@ -1738,6 +1743,7 @@ paths:
application/json:
schema:
type: object
additionalProperties: true
title: Body
responses:
'200':
@@ -1779,6 +1785,7 @@ paths:
application/json:
schema:
type: object
additionalProperties: true
title: Body
responses:
'200':
@@ -1862,6 +1869,7 @@ paths:
application/json:
schema:
type: object
additionalProperties: true
title: Body
responses:
'200':
@@ -3483,6 +3491,7 @@ paths:
application/json:
schema:
type: object
additionalProperties: true
title: Body
responses:
'200':
@@ -5652,6 +5661,7 @@ paths:
application/json:
schema:
type: object
additionalProperties: true
title: Response Create Trigger Embedding Trigger Embedding Post
'422':
description: Validation Error
@@ -5700,6 +5710,7 @@ paths:
application/json:
schema:
type: object
additionalProperties: true
title: Response Update Trigger Embedding Trigger Embedding
Camera Name Name Put
'422':
@@ -5742,6 +5753,7 @@ paths:
application/json:
schema:
type: object
additionalProperties: true
title: Response Delete Trigger Embedding Trigger Embedding
Camera Name Name Delete
'422':
@@ -5778,6 +5790,7 @@ paths:
application/json:
schema:
type: object
additionalProperties: true
title: Response Get Triggers Status Triggers Status Camera Name
Get
'422':
@@ -7574,6 +7587,7 @@ paths:
type: array
items:
type: object
additionalProperties: true
title: Response No Recordings Recordings Unavailable Get
'422':
description: Validation Error
@@ -7777,7 +7791,8 @@ components:
title: Update Topic
config_data:
anyOf:
- type: object
- additionalProperties: true
type: object
- type: 'null'
title: Config Data
skip_save:
@@ -8125,6 +8140,7 @@ components:
tool_calls:
anyOf:
- items:
additionalProperties: true
type: object
type: array
- type: 'null'
@@ -8420,6 +8436,7 @@ components:
- type: 'null'
title: Model Type
data:
additionalProperties: true
type: object
title: Data
type: object
@@ -8491,7 +8508,8 @@ components:
default: true
draw:
anyOf:
- type: object
- additionalProperties: true
type: object
- type: 'null'
title: Draw
default: {}
@@ -8734,7 +8752,8 @@ components:
description: Error message for failed jobs
results:
anyOf:
- type: object
- additionalProperties: true
type: object
- type: 'null'
title: Results
description: Result metadata for completed jobs
@@ -8984,6 +9003,7 @@ components:
- type: 'null'
title: Base Url
provider_options:
additionalProperties: true
type: object
title: Provider Options
type: object
@@ -9524,6 +9544,7 @@ components:
type: string
title: Tool Name
arguments:
additionalProperties: true
type: object
title: Arguments
type: object
+1 -1
View File
@@ -702,7 +702,7 @@ def motion_activity(
df = df[df["camera"] != ""]
# change types for output
df.index = df.index.astype(int) // (10**9)
df.index = df.index.as_unit("s").astype(int)
normalized = df.reset_index().to_dict("records")
return JSONResponse(content=normalized)
+3 -3
View File
@@ -12,8 +12,8 @@ from pathlib import Path
import psutil
import uvicorn
from peewee import SqliteDatabase
from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase
from frigate.api.auth import hash_password
from frigate.api.fastapi_app import create_fastapi_app
@@ -186,7 +186,7 @@ class FrigateApp:
self.timeline_queue: Queue = mp.Queue()
def init_database(self) -> None:
def vacuum_db(db: SqliteExtDatabase) -> None:
def vacuum_db(db: SqliteDatabase) -> None:
logger.info("Running database vacuum")
db.execute_sql("VACUUM;")
@@ -197,7 +197,7 @@ class FrigateApp:
logger.error("Unable to write to /config to save DB state")
# Migrate DB schema
migrate_db = SqliteExtDatabase(self.config.database.path)
migrate_db = SqliteDatabase(self.config.database.path)
# Run migrations
del logging.getLogger("peewee_migrate").handlers[:]
+6 -6
View File
@@ -54,12 +54,12 @@ class PTZMetrics:
reset: Event
def __init__(self) -> None:
self.start_time = mp.Value("d", 0) # type: ignore[assignment]
self.stop_time = mp.Value("d", 0) # type: ignore[assignment]
self.frame_time = mp.Value("d", 0) # type: ignore[assignment]
self.zoom_level = mp.Value("d", 0) # type: ignore[assignment]
self.max_zoom = mp.Value("d", 0) # type: ignore[assignment]
self.min_zoom = mp.Value("d", 0) # type: ignore[assignment]
self.start_time = mp.Value("d", 0)
self.stop_time = mp.Value("d", 0)
self.frame_time = mp.Value("d", 0)
self.zoom_level = mp.Value("d", 0)
self.max_zoom = mp.Value("d", 0)
self.min_zoom = mp.Value("d", 0)
self.motor_stopped = mp.Event()
self.reset = mp.Event()
+2 -2
View File
@@ -6,7 +6,7 @@ import logging
import os
import queue
import threading
from collections.abc import Callable
from collections.abc import Callable, Iterator
from dataclasses import dataclass
from multiprocessing.synchronize import Event as MpEvent
from typing import Any
@@ -72,7 +72,7 @@ class WebPushClient(Communicator):
# Pull keys from PEM or generate if they do not exist
self.vapid = Vapid01.from_file(os.path.join(CONFIG_DIR, "notifications.pem"))
users: list[dict[str, Any]] = (
users: Iterator[dict[str, Any]] = (
User.select(User.username, User.notification_tokens).dicts().iterator()
)
for user in users:
@@ -580,8 +580,8 @@ class LicensePlateProcessingMixin:
boxes = []
scores = []
for index in range(len(contours)): # type: ignore[arg-type]
contour = contours[index] # type: ignore[index]
for index in range(len(contours)):
contour = contours[index]
# get minimum bounding box (rotated rectangle) around the contour and the smallest side length.
points, sside = self._get_min_boxes(contour)
@@ -1222,7 +1222,7 @@ class LicensePlateProcessingMixin:
"""Look for license plates in image."""
self.metrics.alpr_pps.value = self.plates_rec_second.eps()
self.metrics.yolov9_lpr_pps.value = self.plates_det_second.eps()
camera = obj_data if dedicated_lpr else obj_data["camera"]
camera: str = obj_data if dedicated_lpr else obj_data["camera"] # type: ignore[assignment]
current_time = int(datetime.datetime.now().timestamp())
debug_frame_id = int(datetime.datetime.now().timestamp() * 1000)
+1 -1
View File
@@ -365,7 +365,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
img = cv2.imread(current_file)
if img is None:
return { # type: ignore[unreachable]
return {
"message": "Invalid image file.",
"success": False,
}
+13 -13
View File
@@ -2,10 +2,10 @@
import logging
import os
import shutil
import threading
import warnings
from transformers import AutoFeatureExtractor, AutoTokenizer
from transformers import AutoTokenizer, CLIPImageProcessor
from transformers.utils.logging import disable_progress_bar
from frigate.comms.inter_process import InterProcessRequestor
@@ -21,12 +21,6 @@ from frigate.util.downloader import ModelDownloader
from .base_embedding import BaseEmbedding
warnings.filterwarnings(
"ignore",
category=FutureWarning,
message="The class CLIPFeatureExtractor is deprecated",
)
# disables the progress bar for downloading tokenizers and feature extractors
disable_progress_bar()
logger = logging.getLogger(__name__)
@@ -58,6 +52,13 @@ class JinaV1TextEmbedding(BaseEmbedding):
self._lock = threading.Lock()
files_names = list(self.download_urls.keys()) + [self.tokenizer_file]
# an interrupted download leaves the hub cache without the saved tokenizer
tokenizer_path = os.path.join(self.download_path, self.tokenizer_file)
if os.path.isdir(tokenizer_path) and not os.path.exists(
os.path.join(tokenizer_path, "tokenizer_config.json")
):
shutil.rmtree(tokenizer_path)
if not all(
os.path.exists(os.path.join(self.download_path, n)) for n in files_names
):
@@ -92,7 +93,7 @@ class JinaV1TextEmbedding(BaseEmbedding):
tokenizer = AutoTokenizer.from_pretrained(
self.model_name,
trust_remote_code=True,
trust_remote_code=False,
cache_dir=f"{MODEL_CACHE_DIR}/{self.model_name}/tokenizer",
clean_up_tokenization_spaces=True,
)
@@ -123,9 +124,8 @@ class JinaV1TextEmbedding(BaseEmbedding):
f"{MODEL_CACHE_DIR}/{self.model_name}/tokenizer"
)
self.tokenizer = AutoTokenizer.from_pretrained(
self.model_name,
cache_dir=tokenizer_path,
trust_remote_code=True,
tokenizer_path,
trust_remote_code=False,
clean_up_tokenization_spaces=True,
)
@@ -209,7 +209,7 @@ class JinaV1ImageEmbedding(BaseEmbedding):
if self.downloader:
self.downloader.wait_for_download()
self.feature_extractor = AutoFeatureExtractor.from_pretrained(
self.feature_extractor = CLIPImageProcessor.from_pretrained(
f"{MODEL_CACHE_DIR}/{self.model_name}",
)
+12 -4
View File
@@ -3,6 +3,7 @@
import io
import logging
import os
import shutil
import threading
import numpy as np
@@ -60,6 +61,14 @@ class JinaV2Embedding(BaseEmbedding):
# download the model and tokenizer
files_names = list(self.download_urls.keys()) + [self.tokenizer_file]
# an interrupted download leaves the hub cache without the saved tokenizer
tokenizer_path = os.path.join(self.download_path, self.tokenizer_file)
if os.path.isdir(tokenizer_path) and not os.path.exists(
os.path.join(tokenizer_path, "tokenizer_config.json")
):
shutil.rmtree(tokenizer_path)
if not all(
os.path.exists(os.path.join(self.download_path, n)) for n in files_names
):
@@ -97,7 +106,7 @@ class JinaV2Embedding(BaseEmbedding):
tokenizer = AutoTokenizer.from_pretrained(
self.model_name,
trust_remote_code=True,
trust_remote_code=False,
cache_dir=os.path.join(
MODEL_CACHE_DIR, self.model_name, "tokenizer"
),
@@ -129,9 +138,8 @@ class JinaV2Embedding(BaseEmbedding):
f"{MODEL_CACHE_DIR}/{self.model_name}/tokenizer"
)
self.tokenizer = AutoTokenizer.from_pretrained(
self.model_name,
cache_dir=tokenizer_path,
trust_remote_code=True,
tokenizer_path,
trust_remote_code=False,
clean_up_tokenization_spaces=True,
)
+7 -9
View File
@@ -82,7 +82,7 @@ class EventCleanup(threading.Thread):
datetime.datetime.now() - datetime.timedelta(days=expire_days)
).timestamp()
# grab all events after specific time
expired_events: list[Event] = (
expired_events: list[Event] = list(
Event.select(
Event.id,
Event.camera,
@@ -97,7 +97,6 @@ class EventCleanup(threading.Thread):
.namedtuples()
.iterator()
)
expired_events = list(expired_events)
logger.debug(f"{len(expired_events)} events can be expired")
# delete the media from disk
@@ -159,7 +158,7 @@ class EventCleanup(threading.Thread):
datetime.datetime.now() - datetime.timedelta(days=expire_days)
).timestamp()
# grab all events after specific time
expired_events = (
camera_events = (
Event.select(
Event.id,
Event.camera,
@@ -178,7 +177,7 @@ class EventCleanup(threading.Thread):
# delete the grabbed clips from disk
# only snapshots are stored in /clips
# so no need to delete mp4 files
for event in expired_events:
for event in camera_events:
events_to_update.append(str(event.id))
deleted = delete_event_snapshot(event)
@@ -212,7 +211,7 @@ class EventCleanup(threading.Thread):
datetime.datetime.now() - datetime.timedelta(days=expire_days)
).timestamp()
# grab all events after specific time
expired_events: list[Event] = (
expired_events: list[Event] = list(
Event.select(
Event.id,
Event.camera,
@@ -225,7 +224,6 @@ class EventCleanup(threading.Thread):
.namedtuples()
.iterator()
)
expired_events = list(expired_events)
logger.debug(f"{len(expired_events)} events can be expired")
# delete the media from disk
for expired in expired_events:
@@ -235,7 +233,7 @@ class EventCleanup(threading.Thread):
try:
media_path.unlink(missing_ok=True)
if file_extension == "jpg":
media_path = Path(
media_path = Path( # type: ignore[unreachable]
f"{os.path.join(CLIPS_DIR, media_name)}-clean.webp"
)
media_path.unlink(missing_ok=True)
@@ -289,7 +287,7 @@ class EventCleanup(threading.Thread):
now - datetime.timedelta(days=camera.record.effective_detection_days)
).timestamp()
# grab all events after specific time
expired_events = (
camera_events = (
Event.select(
Event.id,
Event.camera,
@@ -316,7 +314,7 @@ class EventCleanup(threading.Thread):
# delete the grabbed clips from disk
# only snapshots are stored in /clips
# so no need to delete mp4 files
for event in expired_events:
for event in camera_events:
events_to_update.append(event.id)
# update the clips attribute for the db entry
+1 -1
View File
@@ -94,7 +94,7 @@ class EventProcessor(threading.Thread):
if update == None:
continue
source_type, event_type, camera, _, event_data = update # type: ignore[misc]
source_type, event_type, camera, _, event_data = update
logger.debug(
f"Event received: {source_type} {event_type} {camera} {event_data['id']}"
+1 -1
View File
@@ -38,7 +38,7 @@ __all__ = [
"register_genai_provider",
]
PROVIDERS = {}
PROVIDERS: dict[GenAIProviderEnum, type["GenAIClient"]] = {}
def register_genai_provider(key: GenAIProviderEnum) -> Callable:
+1
View File
@@ -85,6 +85,7 @@ def apply_log_levels(default: str, log_levels: dict[str, LogLevel]) -> None:
log_levels = {
"absl": LogLevel.error,
"httpx": LogLevel.error,
"httpx2": LogLevel.error,
"h5py": LogLevel.error,
"keras": LogLevel.error,
"matplotlib": LogLevel.error,
+2 -2
View File
@@ -360,7 +360,7 @@ class ObjectDetectProcess:
# detection_start is set only after detection_queue.get()
# returns. If it was reset during the grace period, the process
# recovered and may be waiting on the shared queue again.
if self.detection_start.value == 0.0: # type: ignore[attr-defined]
if self.detection_start.value == 0.0:
logging.info("Detection process recovered before restart")
return
@@ -369,7 +369,7 @@ class ObjectDetectProcess:
self.detect_process.join()
logging.info("Detection process has exited...")
self.detection_start.value = 0.0 # type: ignore[attr-defined]
self.detection_start.value = 0.0
# Async path for MemryX
if self.detector_config.type == "memryx":
+2 -2
View File
@@ -10,7 +10,7 @@ from multiprocessing.synchronize import Event as MpEvent
from pathlib import Path
from typing import Any
from playhouse.sqlite_ext import SqliteExtDatabase
from peewee import SqliteDatabase
from frigate.config import CameraConfig, FrigateConfig, RetainModeEnum
from frigate.const import (
@@ -88,7 +88,7 @@ class RecordingCleanup(threading.Thread):
if (
os.stat(f"{self.config.database.path}-wal").st_size / (1024 * 1024)
) > MAX_WAL_SIZE:
db = SqliteExtDatabase(self.config.database.path)
db = SqliteDatabase(self.config.database.path)
db.execute_sql("PRAGMA wal_checkpoint(TRUNCATE);")
db.close()
+1 -1
View File
@@ -16,7 +16,7 @@ from enum import Enum
from pathlib import Path
from typing import Any, cast
import pytz # type: ignore[import-untyped]
import pytz
from pathvalidate import sanitize_filename
from peewee import DoesNotExist
+1 -1
View File
@@ -859,7 +859,7 @@ class RecordingMaintainer(threading.Thread):
for box in motion_boxes:
if len(box) < 4:
continue
continue # type: ignore[unreachable]
x1, y1, x2, y2 = box
# Convert pixel coordinates to grid cells
+2 -2
View File
@@ -6,8 +6,8 @@ from unittest.mock import patch
from fastapi import Request
from fastapi.testclient import TestClient
from peewee import SqliteDatabase
from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase
from playhouse.sqliteq import SqliteQueueDatabase
from pydantic import Json
@@ -38,7 +38,7 @@ class AuthTestClient(TestClient):
class BaseTestHttp(unittest.TestCase):
def setUp(self, models):
# setup clean database for each test run
migrate_db = SqliteExtDatabase("test.db")
migrate_db = SqliteDatabase("test.db")
del logging.getLogger("peewee_migrate").handlers[:]
router = Router(migrate_db)
router.run()
@@ -7,7 +7,7 @@ import unittest
from types import SimpleNamespace
from unittest.mock import MagicMock
from playhouse.sqlite_ext import SqliteExtDatabase
from peewee import SqliteDatabase
from frigate.api.chat import (
_execute_find_similar_objects,
@@ -98,7 +98,7 @@ class TestExecuteFindSimilarObjects(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
self.tmp.close()
self.db = SqliteExtDatabase(self.tmp.name)
self.db = SqliteDatabase(self.tmp.name)
Event.bind(self.db, bind_refs=False, bind_backrefs=False)
self.db.connect()
self.db.create_tables([Event])
+2 -2
View File
@@ -12,7 +12,7 @@ from unittest.mock import AsyncMock, patch
import cv2
import numpy as np
from playhouse.sqlite_ext import SqliteExtDatabase
from peewee import SqliteDatabase
from frigate.api.chat import (
TOOL_REJECTED_RESULT,
@@ -214,7 +214,7 @@ class DatabaseTestCase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
self.tmp.close()
self.db = SqliteExtDatabase(self.tmp.name)
self.db = SqliteDatabase(self.tmp.name)
for model in self.models:
model.bind(self.db, bind_refs=False, bind_backrefs=False)
self.db.connect()
@@ -3,7 +3,7 @@
import unittest
from unittest.mock import MagicMock, patch
from playhouse.sqlite_ext import SqliteExtDatabase
from peewee import SqliteDatabase
from frigate.comms.dispatcher import Dispatcher
from frigate.const import INSERT_MANY_RECORDINGS
@@ -30,7 +30,7 @@ class TestInsertManyRecordings(unittest.TestCase):
"""A duplicate path must not cost the rest of the batch."""
def setUp(self):
self.db = SqliteExtDatabase(":memory:")
self.db = SqliteDatabase(":memory:")
self.db.bind([Recordings])
self.db.create_tables([Recordings])
+2 -2
View File
@@ -4,7 +4,7 @@ import datetime
import unittest
from unittest.mock import MagicMock
from playhouse.sqlite_ext import SqliteExtDatabase
from peewee import SqliteDatabase
from frigate.config import FrigateConfig
from frigate.events.cleanup import EventCleanup
@@ -15,7 +15,7 @@ class TestEventCleanupSubRetention(unittest.TestCase):
def setUp(self):
# in-memory database keeps these tests isolated from the shared
# on-disk test.db used by the http api tests
self.db = SqliteExtDatabase(":memory:")
self.db = SqliteDatabase(":memory:")
models = [Event, Timeline]
self.db.bind(models)
self.db.create_tables(models)
+2 -2
View File
@@ -9,8 +9,8 @@ import logging
import os
import unittest
from peewee import SqliteDatabase
from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase
from playhouse.sqliteq import SqliteQueueDatabase
from frigate.api.media_auth import (
@@ -208,7 +208,7 @@ class TestExportResolution(unittest.TestCase):
"""Export resolution requires a DB lookup."""
def setUp(self):
migrate_db = SqliteExtDatabase("test.db")
migrate_db = SqliteDatabase("test.db")
del logging.getLogger("peewee_migrate").handlers[:]
Router(migrate_db).run()
migrate_db.close()
+2 -2
View File
@@ -5,8 +5,8 @@ import os
import unittest
from unittest.mock import MagicMock, patch
from peewee import SqliteDatabase
from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase
from playhouse.sqliteq import SqliteQueueDatabase
from frigate.models import Notice, NoticeStats
@@ -49,7 +49,7 @@ class TestNoticeKinds(unittest.TestCase):
class RegistryTestCase(unittest.TestCase):
def setUp(self):
migrate_db = SqliteExtDatabase("test.db")
migrate_db = SqliteDatabase("test.db")
del logging.getLogger("peewee_migrate").handlers[:]
router = Router(migrate_db)
router.run()
+2 -2
View File
@@ -4,7 +4,7 @@ import datetime
import unittest
from unittest.mock import MagicMock
from playhouse.sqlite_ext import SqliteExtDatabase
from peewee import SqliteDatabase
from frigate.config import FrigateConfig
from frigate.models import Previews, Recordings, ReviewSegment, UserReviewStatus
@@ -15,7 +15,7 @@ class TestRecordingCleanupSubRetention(unittest.TestCase):
def setUp(self):
# in-memory database keeps these tests isolated from the shared
# on-disk test.db used by the http api tests
self.db = SqliteExtDatabase(":memory:")
self.db = SqliteDatabase(":memory:")
models = [Previews, Recordings, ReviewSegment, UserReviewStatus]
self.db.bind(models)
self.db.create_tables(models)
+2 -2
View File
@@ -8,7 +8,7 @@ import unittest
from collections import defaultdict
from unittest.mock import AsyncMock, MagicMock, patch
from playhouse.sqlite_ext import SqliteExtDatabase
from peewee import SqliteDatabase
from frigate.config import FrigateConfig
from frigate.models import Recordings
@@ -719,7 +719,7 @@ class TestSegmentChainSeeding(unittest.IsolatedAsyncioTestCase):
T0 = datetime.datetime(2026, 6, 10, 14, 30, 22, tzinfo=datetime.UTC).timestamp()
def setUp(self):
self.db = SqliteExtDatabase(":memory:")
self.db = SqliteDatabase(":memory:")
self.db.bind([Recordings])
self.db.create_tables([Recordings])
+2 -2
View File
@@ -5,7 +5,7 @@ import json
import unittest
from types import SimpleNamespace
from playhouse.sqlite_ext import SqliteExtDatabase
from peewee import SqliteDatabase
from frigate.api.media import _vod_response
from frigate.const import MAX_SEGMENT_DURATION
@@ -24,7 +24,7 @@ class CoverageDbTestCase(unittest.TestCase):
def setUp(self):
# in-memory database keeps these tests isolated from the shared
# on-disk test.db used by the http api tests
self.db = SqliteExtDatabase(":memory:")
self.db = SqliteDatabase(":memory:")
models = [Recordings]
self.db.bind(models)
self.db.create_tables(models)
+2 -3
View File
@@ -5,9 +5,8 @@ import tempfile
import unittest
from unittest.mock import MagicMock, patch
from peewee import DoesNotExist
from peewee import DoesNotExist, SqliteDatabase
from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase
from playhouse.sqliteq import SqliteQueueDatabase
from frigate.config import FrigateConfig
@@ -20,7 +19,7 @@ from frigate.test.const import TEST_DB, TEST_DB_CLEANUPS
class TestHttp(unittest.TestCase):
def setUp(self):
# setup clean database for each test run
migrate_db = SqliteExtDatabase("test.db")
migrate_db = SqliteDatabase("test.db")
del logging.getLogger("peewee_migrate").handlers[:]
router = Router(migrate_db)
router.run()
+1 -1
View File
@@ -116,7 +116,7 @@ class FrigateWatchdog(threading.Thread):
now = datetime.datetime.now().timestamp()
for name, detector in self.detectors.items():
detection_start = detector.detection_start.value # type: ignore[attr-defined]
detection_start = detector.detection_start.value
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
if detection_start > 0.0 and now - detection_start > 10:
+1
View File
@@ -2,5 +2,6 @@
target-version = "py311"
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F"]
ignore = ["E501","E711","E712","UP031","UP032","UP042","G004"]
extend-select = ["I", "UP", "G", "ASYNC210", "B904"]
+763 -935
View File
File diff suppressed because it is too large Load Diff
+51 -51
View File
@@ -23,33 +23,33 @@
"dependencies": {
"@cycjimmy/jsmpeg-player": "^6.1.2",
"@hookform/resolvers": "^5.9.1",
"@radix-ui/react-alert-dialog": "^1.1.23",
"@radix-ui/react-aspect-ratio": "^1.1.15",
"@radix-ui/react-checkbox": "^1.3.11",
"@radix-ui/react-collapsible": "^1.1.20",
"@radix-ui/react-context-menu": "^2.3.7",
"@radix-ui/react-dialog": "^1.1.23",
"@radix-ui/react-dropdown-menu": "^2.1.24",
"@radix-ui/react-hover-card": "^1.1.23",
"@radix-ui/react-label": "^2.1.15",
"@radix-ui/react-popover": "^1.1.23",
"@radix-ui/react-progress": "^1.1.16",
"@radix-ui/react-radio-group": "^1.4.7",
"@radix-ui/react-scroll-area": "^1.2.18",
"@radix-ui/react-select": "^2.3.7",
"@radix-ui/react-separator": "^1.1.15",
"@radix-ui/react-slider": "^1.4.7",
"@radix-ui/react-slot": "1.2.4",
"@radix-ui/react-switch": "^1.3.7",
"@radix-ui/react-tabs": "^1.1.21",
"@radix-ui/react-toggle": "^1.1.18",
"@radix-ui/react-toggle-group": "^1.1.19",
"@radix-ui/react-tooltip": "^1.2.16",
"@rjsf/core": "^6.10.0",
"@rjsf/shadcn": "^6.10.0",
"@rjsf/utils": "^6.10.0",
"@rjsf/validator-ajv8": "^6.10.0",
"apexcharts": "^7.3.0",
"@radix-ui/react-alert-dialog": "^1.1.24",
"@radix-ui/react-aspect-ratio": "^1.1.16",
"@radix-ui/react-checkbox": "^1.3.12",
"@radix-ui/react-collapsible": "^1.1.21",
"@radix-ui/react-context-menu": "^2.3.8",
"@radix-ui/react-dialog": "^1.2.0",
"@radix-ui/react-dropdown-menu": "^2.1.25",
"@radix-ui/react-hover-card": "^1.1.24",
"@radix-ui/react-label": "^2.1.16",
"@radix-ui/react-popover": "^1.2.0",
"@radix-ui/react-progress": "^1.1.17",
"@radix-ui/react-radio-group": "^1.4.8",
"@radix-ui/react-scroll-area": "^1.3.0",
"@radix-ui/react-select": "^2.3.8",
"@radix-ui/react-separator": "^1.1.16",
"@radix-ui/react-slider": "^1.5.0",
"@radix-ui/react-slot": "1.4.0",
"@radix-ui/react-switch": "^1.3.8",
"@radix-ui/react-tabs": "^1.1.22",
"@radix-ui/react-toggle": "^1.1.19",
"@radix-ui/react-toggle-group": "^1.1.20",
"@radix-ui/react-tooltip": "^1.3.0",
"@rjsf/core": "^6.10.1",
"@rjsf/shadcn": "^6.10.1",
"@rjsf/utils": "^6.10.1",
"@rjsf/validator-ajv8": "^6.10.1",
"apexcharts": "^7.8.0",
"axios": "^1.20.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -57,31 +57,31 @@
"copy-to-clipboard": "^4.0.2",
"date-fns": "^4.4.0",
"date-fns-tz": "^3.2.0",
"framer-motion": "^13.3.0",
"framer-motion": "^14.0.0",
"hls.js": "^1.7.3",
"i18next": "^26.4.2",
"i18next-http-backend": "^4.0.2",
"idb-keyval": "^6.3.0",
"js-yaml": "^5.4.2",
"konva": "^10.5.0",
"js-yaml": "^5.4.3",
"konva": "^10.7.1",
"lodash": "^4.18.1",
"lucide-react": "^1.46.0",
"lucide-react": "^1.52.0",
"monaco-yaml": "^5.5.1",
"next-themes": "^0.4.6",
"nosleep.js": "^0.12.0",
"react": "^19.3.0",
"react-apexcharts": "^2.1.1",
"react-day-picker": "^9.14.0",
"react-day-picker": "^10.0.2",
"react-device-detect": "^2.2.3",
"react-dom": "^19.3.0",
"react-dropzone": "^20.1.2",
"react-grid-layout": "^2.2.4",
"react-hook-form": "^7.88.0",
"react-i18next": "^17.0.14",
"react-grid-layout": "^2.3.0",
"react-hook-form": "^7.89.0",
"react-i18next": "^17.0.16",
"react-icons": "^5.7.0",
"react-konva": "^19.2.7",
"react-konva": "^19.3.0",
"react-markdown": "^10.1.0",
"react-router-dom": "^6.30.6",
"react-router-dom": "^7.18.4",
"react-swipeable": "^7.0.2",
"react-zoom-pan-pinch": "3.4.4",
"remark-gfm": "^4.0.0",
@@ -93,41 +93,41 @@
"tailwindcss-animate": "^1.0.7",
"use-long-press": "^3.3.0",
"vaul": "^1.1.2",
"virtua": "^0.51.3",
"virtua": "^0.53.3",
"vite-plugin-monaco-editor": "^1.1.0",
"zod": "^3.25.76"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@playwright/test": "^1.63.0",
"@playwright/test": "^1.64.0",
"@types/js-yaml": "^4.0.9",
"@types/lodash": "^4.17.25",
"@types/node": "^26.5.1",
"@types/node": "^26.6.4",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@vitejs/plugin-react": "^6.1.1",
"autoprefixer": "^10.6.0",
"@vitejs/plugin-react": "^6.1.2",
"autoprefixer": "^10.6.1",
"esbuild": "^0.28.2",
"eslint": "^10.10.0",
"eslint": "^10.12.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.7",
"globals": "^17.12.0",
"globals": "^17.13.0",
"i18next-cli": "^1.5.11",
"monaco-editor": "^0.52.2",
"patch-package": "^8.0.1",
"postcss": "^8.5.12",
"prettier": "^3.9.6",
"postcss": "^8.5.29",
"prettier": "^3.9.9",
"prettier-plugin-tailwindcss": "^0.8.1",
"tailwindcss": "^3.4.9",
"typescript": "^5.9.3",
"typescript-eslint": "^8.70.0",
"vite": "^8.3.0"
"tailwindcss": "^3.4.19",
"typescript": "^6.0.3",
"typescript-eslint": "^8.71.1",
"vite": "^8.3.3"
},
"overrides": {
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-popper": "1.2.8",
"@radix-ui/react-slot": "1.2.4"
"@radix-ui/react-slot": "1.4.0"
}
}
@@ -1,46 +0,0 @@
diff --git a/node_modules/@radix-ui/react-slot/dist/index.js b/node_modules/@radix-ui/react-slot/dist/index.js
index 3691205..3b62ea8 100644
--- a/node_modules/@radix-ui/react-slot/dist/index.js
+++ b/node_modules/@radix-ui/react-slot/dist/index.js
@@ -85,11 +85,12 @@ function createSlotClone(ownerName) {
if (isLazyComponent(children) && typeof use === "function") {
children = use(children._payload);
}
+ const childrenRef = React.isValidElement(children) ? getElementRef(children) : null;
+ const composedRef = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, childrenRef);
if (React.isValidElement(children)) {
- const childrenRef = getElementRef(children);
const props2 = mergeProps(slotProps, children.props);
if (children.type !== React.Fragment) {
- props2.ref = forwardedRef ? (0, import_react_compose_refs.composeRefs)(forwardedRef, childrenRef) : childrenRef;
+ props2.ref = forwardedRef ? composedRef : childrenRef;
}
return React.cloneElement(children, props2);
}
diff --git a/node_modules/@radix-ui/react-slot/dist/index.mjs b/node_modules/@radix-ui/react-slot/dist/index.mjs
index d7ea374..a990150 100644
--- a/node_modules/@radix-ui/react-slot/dist/index.mjs
+++ b/node_modules/@radix-ui/react-slot/dist/index.mjs
@@ -1,6 +1,6 @@
// src/slot.tsx
import * as React from "react";
-import { composeRefs } from "@radix-ui/react-compose-refs";
+import { composeRefs, useComposedRefs } from "@radix-ui/react-compose-refs";
import { Fragment as Fragment2, jsx } from "react/jsx-runtime";
var REACT_LAZY_TYPE = Symbol.for("react.lazy");
var use = React[" use ".trim().toString()];
@@ -45,11 +45,12 @@ function createSlotClone(ownerName) {
if (isLazyComponent(children) && typeof use === "function") {
children = use(children._payload);
}
+ const childrenRef = React.isValidElement(children) ? getElementRef(children) : null;
+ const composedRef = useComposedRefs(forwardedRef, childrenRef);
if (React.isValidElement(children)) {
- const childrenRef = getElementRef(children);
const props2 = mergeProps(slotProps, children.props);
if (children.type !== React.Fragment) {
- props2.ref = forwardedRef ? composeRefs(forwardedRef, childrenRef) : childrenRef;
+ props2.ref = forwardedRef ? composedRef : childrenRef;
}
return React.cloneElement(children, props2);
}
+1 -1
View File
@@ -44,7 +44,7 @@ function App() {
return (
<Providers>
<BrowserRouter basename={window.baseUrl}>
<BrowserRouter basename={window.baseUrl} useTransitions={false}>
<Wrapper>
{config?.safe_mode ? <SafeAppView /> : <DefaultAppView />}
</Wrapper>
@@ -155,7 +155,7 @@ export function CombinedStorageGraph({
formatter: function (val, opts) {
const entry = opts ? series[opts.seriesIndex] : undefined;
if (entry) {
return `${getUnitSize(entry.usage)} (${val.toFixed(2)}%)`;
return `${getUnitSize(entry.usage)} (${(val ?? 0).toFixed(2)}%)`;
}
},
},
+1 -1
View File
@@ -5,7 +5,7 @@
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"baseUrl": ".",
"types": ["node"],
"paths": {
"@/*": ["./src/*"]
},