mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-05-10 07:25:27 +03:00
Compare commits
5 Commits
cbfefd6df5
...
f7271e0a5b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7271e0a5b | ||
|
|
67604eb61d | ||
|
|
0835aa7ea5 | ||
|
|
3c3cf11da4 | ||
|
|
b657f04d0e |
@ -119,6 +119,7 @@ class FrigateApp:
|
|||||||
self.ptz_metrics: dict[str, PTZMetrics] = {}
|
self.ptz_metrics: dict[str, PTZMetrics] = {}
|
||||||
self.processes: dict[str, int] = {}
|
self.processes: dict[str, int] = {}
|
||||||
self.embeddings: Optional[EmbeddingsContext] = None
|
self.embeddings: Optional[EmbeddingsContext] = None
|
||||||
|
self.profile_manager: Optional[ProfileManager] = None
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
def ensure_dirs(self) -> None:
|
def ensure_dirs(self) -> None:
|
||||||
@ -351,9 +352,7 @@ class FrigateApp:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def init_profile_manager(self) -> None:
|
def init_profile_manager(self) -> None:
|
||||||
self.profile_manager = ProfileManager(
|
self.profile_manager = ProfileManager(self.config, self.inter_config_updater)
|
||||||
self.config, self.inter_config_updater
|
|
||||||
)
|
|
||||||
self.dispatcher.profile_manager = self.profile_manager
|
self.dispatcher.profile_manager = self.profile_manager
|
||||||
|
|
||||||
persisted = ProfileManager.load_persisted_profile()
|
persisted = ProfileManager.load_persisted_profile()
|
||||||
|
|||||||
@ -16,6 +16,7 @@ from frigate.config.camera.updater import (
|
|||||||
CameraConfigUpdateTopic,
|
CameraConfigUpdateTopic,
|
||||||
)
|
)
|
||||||
from frigate.config.config import RuntimeFilterConfig, RuntimeMotionConfig
|
from frigate.config.config import RuntimeFilterConfig, RuntimeMotionConfig
|
||||||
|
from frigate.config.profile_manager import ProfileManager
|
||||||
from frigate.const import (
|
from frigate.const import (
|
||||||
CLEAR_ONGOING_REVIEW_SEGMENTS,
|
CLEAR_ONGOING_REVIEW_SEGMENTS,
|
||||||
EXPIRE_AUDIO_ACTIVITY,
|
EXPIRE_AUDIO_ACTIVITY,
|
||||||
@ -93,7 +94,7 @@ class Dispatcher:
|
|||||||
"notifications": self._on_global_notification_command,
|
"notifications": self._on_global_notification_command,
|
||||||
"profile": self._on_profile_command,
|
"profile": self._on_profile_command,
|
||||||
}
|
}
|
||||||
self.profile_manager = None
|
self.profile_manager: Optional[ProfileManager] = None
|
||||||
|
|
||||||
for comm in self.comms:
|
for comm in self.comms:
|
||||||
comm.subscribe(self._receive)
|
comm.subscribe(self._receive)
|
||||||
@ -569,7 +570,9 @@ class Dispatcher:
|
|||||||
logger.error("Profile manager not initialized")
|
logger.error("Profile manager not initialized")
|
||||||
return
|
return
|
||||||
|
|
||||||
profile_name = payload.strip() if payload.strip() not in ("", "none", "None") else None
|
profile_name = (
|
||||||
|
payload.strip() if payload.strip() not in ("", "none", "None") else None
|
||||||
|
)
|
||||||
err = self.profile_manager.activate_profile(profile_name)
|
err = self.profile_manager.activate_profile(profile_name)
|
||||||
if err:
|
if err:
|
||||||
logger.error("Failed to activate profile: %s", err)
|
logger.error("Failed to activate profile: %s", err)
|
||||||
|
|||||||
@ -2,16 +2,29 @@ import sys
|
|||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
# Mock complex imports before importing maintainer
|
# Mock complex imports before importing maintainer, saving originals so we can
|
||||||
sys.modules["frigate.comms.inter_process"] = MagicMock()
|
# restore them after import and avoid polluting sys.modules for other tests.
|
||||||
sys.modules["frigate.comms.detections_updater"] = MagicMock()
|
_MOCKED_MODULES = [
|
||||||
sys.modules["frigate.comms.recordings_updater"] = MagicMock()
|
"frigate.comms.inter_process",
|
||||||
sys.modules["frigate.config.camera.updater"] = MagicMock()
|
"frigate.comms.detections_updater",
|
||||||
|
"frigate.comms.recordings_updater",
|
||||||
|
"frigate.config.camera.updater",
|
||||||
|
]
|
||||||
|
_originals = {name: sys.modules.get(name) for name in _MOCKED_MODULES}
|
||||||
|
for name in _MOCKED_MODULES:
|
||||||
|
sys.modules[name] = MagicMock()
|
||||||
|
|
||||||
# Now import the class under test
|
# Now import the class under test
|
||||||
from frigate.config import FrigateConfig # noqa: E402
|
from frigate.config import FrigateConfig # noqa: E402
|
||||||
from frigate.record.maintainer import RecordingMaintainer # noqa: E402
|
from frigate.record.maintainer import RecordingMaintainer # noqa: E402
|
||||||
|
|
||||||
|
# Restore original modules (or remove mock if there was no original)
|
||||||
|
for name, orig in _originals.items():
|
||||||
|
if orig is None:
|
||||||
|
sys.modules.pop(name, None)
|
||||||
|
else:
|
||||||
|
sys.modules[name] = orig
|
||||||
|
|
||||||
|
|
||||||
class TestMaintainer(unittest.IsolatedAsyncioTestCase):
|
class TestMaintainer(unittest.IsolatedAsyncioTestCase):
|
||||||
async def test_move_files_survives_bad_filename(self):
|
async def test_move_files_survives_bad_filename(self):
|
||||||
|
|||||||
@ -48,9 +48,7 @@ class TestCameraProfileConfig(unittest.TestCase):
|
|||||||
|
|
||||||
def test_partial_review(self):
|
def test_partial_review(self):
|
||||||
"""Profile with nested review.alerts.labels."""
|
"""Profile with nested review.alerts.labels."""
|
||||||
profile = CameraProfileConfig(
|
profile = CameraProfileConfig(review={"alerts": {"labels": ["person", "car"]}})
|
||||||
review={"alerts": {"labels": ["person", "car"]}}
|
|
||||||
)
|
|
||||||
assert profile.review is not None
|
assert profile.review is not None
|
||||||
assert profile.review.alerts.labels == ["person", "car"]
|
assert profile.review.alerts.labels == ["person", "car"]
|
||||||
|
|
||||||
@ -116,9 +114,7 @@ class TestCameraProfileConfig(unittest.TestCase):
|
|||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
CameraProfileConfig(
|
CameraProfileConfig(review={"alerts": {"labels": "not_a_list"}})
|
||||||
review={"alerts": {"labels": "not_a_list"}}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_invalid_profile_in_camera_config(self):
|
def test_invalid_profile_in_camera_config(self):
|
||||||
"""Invalid profile section in full config is caught at parse time."""
|
"""Invalid profile section in full config is caught at parse time."""
|
||||||
@ -410,14 +406,14 @@ class TestProfileManager(unittest.TestCase):
|
|||||||
self.manager.activate_profile("disarmed")
|
self.manager.activate_profile("disarmed")
|
||||||
|
|
||||||
# Back camera has no "disarmed" profile, should be unchanged
|
# Back camera has no "disarmed" profile, should be unchanged
|
||||||
assert self.config.cameras["back"].notifications.enabled == back_base_notifications
|
assert (
|
||||||
|
self.config.cameras["back"].notifications.enabled == back_base_notifications
|
||||||
|
)
|
||||||
|
|
||||||
@patch.object(ProfileManager, "_persist_active_profile")
|
@patch.object(ProfileManager, "_persist_active_profile")
|
||||||
def test_activate_profile_disables_camera(self, mock_persist):
|
def test_activate_profile_disables_camera(self, mock_persist):
|
||||||
"""Profile with enabled=false disables the camera."""
|
"""Profile with enabled=false disables the camera."""
|
||||||
self.config.profiles["away"] = ProfileDefinitionConfig(
|
self.config.profiles["away"] = ProfileDefinitionConfig(friendly_name="Away")
|
||||||
friendly_name="Away"
|
|
||||||
)
|
|
||||||
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
|
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
|
||||||
enabled=False
|
enabled=False
|
||||||
)
|
)
|
||||||
@ -431,9 +427,7 @@ class TestProfileManager(unittest.TestCase):
|
|||||||
@patch.object(ProfileManager, "_persist_active_profile")
|
@patch.object(ProfileManager, "_persist_active_profile")
|
||||||
def test_deactivate_restores_enabled(self, mock_persist):
|
def test_deactivate_restores_enabled(self, mock_persist):
|
||||||
"""Deactivating a profile restores the camera's base enabled state."""
|
"""Deactivating a profile restores the camera's base enabled state."""
|
||||||
self.config.profiles["away"] = ProfileDefinitionConfig(
|
self.config.profiles["away"] = ProfileDefinitionConfig(friendly_name="Away")
|
||||||
friendly_name="Away"
|
|
||||||
)
|
|
||||||
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
|
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
|
||||||
enabled=False
|
enabled=False
|
||||||
)
|
)
|
||||||
@ -450,9 +444,7 @@ class TestProfileManager(unittest.TestCase):
|
|||||||
"""Profile with zones adds/overrides zones on camera."""
|
"""Profile with zones adds/overrides zones on camera."""
|
||||||
from frigate.config.camera.zone import ZoneConfig
|
from frigate.config.camera.zone import ZoneConfig
|
||||||
|
|
||||||
self.config.profiles["away"] = ProfileDefinitionConfig(
|
self.config.profiles["away"] = ProfileDefinitionConfig(friendly_name="Away")
|
||||||
friendly_name="Away"
|
|
||||||
)
|
|
||||||
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
|
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
|
||||||
zones={
|
zones={
|
||||||
"driveway": ZoneConfig(
|
"driveway": ZoneConfig(
|
||||||
@ -474,9 +466,7 @@ class TestProfileManager(unittest.TestCase):
|
|||||||
"""Deactivating a profile restores base zones."""
|
"""Deactivating a profile restores base zones."""
|
||||||
from frigate.config.camera.zone import ZoneConfig
|
from frigate.config.camera.zone import ZoneConfig
|
||||||
|
|
||||||
self.config.profiles["away"] = ProfileDefinitionConfig(
|
self.config.profiles["away"] = ProfileDefinitionConfig(friendly_name="Away")
|
||||||
friendly_name="Away"
|
|
||||||
)
|
|
||||||
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
|
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
|
||||||
zones={
|
zones={
|
||||||
"driveway": ZoneConfig(
|
"driveway": ZoneConfig(
|
||||||
@ -502,9 +492,7 @@ class TestProfileManager(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
from frigate.config.camera.zone import ZoneConfig
|
from frigate.config.camera.zone import ZoneConfig
|
||||||
|
|
||||||
self.config.profiles["away"] = ProfileDefinitionConfig(
|
self.config.profiles["away"] = ProfileDefinitionConfig(friendly_name="Away")
|
||||||
friendly_name="Away"
|
|
||||||
)
|
|
||||||
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
|
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
|
||||||
zones={
|
zones={
|
||||||
"driveway": ZoneConfig(
|
"driveway": ZoneConfig(
|
||||||
@ -534,9 +522,7 @@ class TestProfileManager(unittest.TestCase):
|
|||||||
CameraConfigUpdateTopic,
|
CameraConfigUpdateTopic,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.config.profiles["away"] = ProfileDefinitionConfig(
|
self.config.profiles["away"] = ProfileDefinitionConfig(friendly_name="Away")
|
||||||
friendly_name="Away"
|
|
||||||
)
|
|
||||||
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
|
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
|
||||||
enabled=False
|
enabled=False
|
||||||
)
|
)
|
||||||
@ -598,9 +584,7 @@ class TestProfilePersistence(unittest.TestCase):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
with patch.object(type(PERSISTENCE_FILE), "exists", return_value=True):
|
with patch.object(type(PERSISTENCE_FILE), "exists", return_value=True):
|
||||||
with patch.object(
|
with patch.object(type(PERSISTENCE_FILE), "read_text", return_value=""):
|
||||||
type(PERSISTENCE_FILE), "read_text", return_value=""
|
|
||||||
):
|
|
||||||
result = ProfileManager.load_persisted_profile()
|
result = ProfileManager.load_persisted_profile()
|
||||||
assert result is None
|
assert result is None
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user