From 57a7f13b4d193d5a064403c8fb2c54305f30f0a7 Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Wed, 28 Oct 2026 06:44:33 -0700 Subject: [PATCH] feat(settings): save_foreign_runs through POST /settings, applied live (#247) GET /settings reports settings.save_foreign_runs; a save takes a strict bool, writes gallery.monitoring.save_foreign_runs and publishes it to GalleryConfig in the commit, with no restart field. The diagnostics report lists it. The legacy contract snapshot gains the added key (an addition, no legacy key moves). --- py/api/diagnostics_report.py | 2 ++ py/api/settings_routes.py | 11 ++++-- tests/fixtures/legacy_contract_snapshot.json | 1 + tests/test_api_diagnostics_report.py | 1 + tests/test_settings_sources.py | 37 ++++++++++++++++++++ 5 files changed, 50 insertions(+), 2 deletions(-) diff --git a/py/api/diagnostics_report.py b/py/api/diagnostics_report.py index dc2de83c..d5040c7f 100644 --- a/py/api/diagnostics_report.py +++ b/py/api/diagnostics_report.py @@ -213,6 +213,8 @@ def public_config(): "max_image_age_days": GalleryConfig.MAX_IMAGE_AGE_DAYS, "max_concurrent_processing": GalleryConfig.MAX_CONCURRENT_PROCESSING, "metadata_extraction_timeout": GalleryConfig.METADATA_EXTRACTION_TIMEOUT, + # config.json gallery.monitoring.save_foreign_runs (#247) + "save_foreign_runs": GalleryConfig.SAVE_FOREIGN_RUNS, }, "integrations": { "lora_manager_enabled": bool(IntegrationConfig.LORA_MANAGER_ENABLED), diff --git a/py/api/settings_routes.py b/py/api/settings_routes.py index eb3da044..d617286f 100644 --- a/py/api/settings_routes.py +++ b/py/api/settings_routes.py @@ -8,7 +8,8 @@ is shared with the LoRA integration's save. GET also reports the image sources and the monitor (``sources.py``). A save that changes the sources runs as a ``source_change`` registry hold, so it is refused while a record-changing job (a scan) runs. ``monitoring_enabled`` -takes effect at the next restart. ``POST /prompt_manager/settings/validate-source`` +takes effect at the next restart; ``save_foreign_runs`` (#247) at once, since +the monitor reads it for every file. ``POST /prompt_manager/settings/validate-source`` checks one path and writes nothing. """ @@ -43,6 +44,7 @@ _SETTINGS_FILE_KEYS = { "INFINITE_SCROLL": ("web_ui", "infinite_scroll"), "WORKER_THREADS": ("performance", "worker_threads"), "MONITORING_ENABLED": ("gallery", "monitoring", "enabled"), + "SAVE_FOREIGN_RUNS": ("gallery", "monitoring", "save_foreign_runs"), "SAFETY_BACKUP": ("maintenance", "safety_backup"), "THUMBNAIL_FORMAT": ("thumbnails", "format"), "THUMBNAIL_QUALITY": ("thumbnails", "quality"), @@ -58,7 +60,7 @@ _SETTINGS_FILE_KEYS = { # The thumbnail settings a running Convert outdated job fixes (#317). _THUMBNAIL_SETTINGS = ("THUMBNAIL_FORMAT", "THUMBNAIL_QUALITY", "THUMBNAIL_EDGE") # Attributes that live on GalleryConfig; the others on PromptManagerConfig. -_GALLERY_SETTINGS = frozenset({"MONITORING_ENABLED"}) +_GALLERY_SETTINGS = frozenset({"MONITORING_ENABLED", "SAVE_FOREIGN_RUNS"}) # Attributes that live on AutotagConfig, named without the AUTOTAG_ prefix. _AUTOTAG_SETTINGS = frozenset( name for name in _SETTINGS_FILE_KEYS if name.startswith("AUTOTAG_") @@ -264,6 +266,7 @@ class SettingsRoutesMixin: "thumbnail_encoders": encoders, "settings": { "monitoring_enabled": GalleryConfig.MONITORING_ENABLED, + "save_foreign_runs": GalleryConfig.SAVE_FOREIGN_RUNS, "result_timeout": PromptManagerConfig.RESULT_TIMEOUT, "webui_display_mode": PromptManagerConfig.WEBUI_DISPLAY_MODE, "infinite_scroll": PromptManagerConfig.INFINITE_SCROLL, @@ -626,6 +629,10 @@ class SettingsRoutesMixin: if not isinstance(data["monitoring_enabled"], bool): raise ValueError("monitoring_enabled must be true or false") updates["MONITORING_ENABLED"] = data["monitoring_enabled"] + if "save_foreign_runs" in data: + if not isinstance(data["save_foreign_runs"], bool): + raise ValueError("save_foreign_runs must be true or false") + updates["SAVE_FOREIGN_RUNS"] = data["save_foreign_runs"] if "safety_backup" in data: if not isinstance(data["safety_backup"], bool): raise ValueError("safety_backup must be true or false") diff --git a/tests/fixtures/legacy_contract_snapshot.json b/tests/fixtures/legacy_contract_snapshot.json index 50a48f53..9a606577 100644 --- a/tests/fixtures/legacy_contract_snapshot.json +++ b/tests/fixtures/legacy_contract_snapshot.json @@ -182,6 +182,7 @@ "settings.monitoring_enabled", "settings.result_timeout", "settings.safety_backup", + "settings.save_foreign_runs", "settings.thumbnail_edge", "settings.thumbnail_format", "settings.thumbnail_quality", diff --git a/tests/test_api_diagnostics_report.py b/tests/test_api_diagnostics_report.py index f1422dbb..113d6c42 100644 --- a/tests/test_api_diagnostics_report.py +++ b/tests/test_api_diagnostics_report.py @@ -85,6 +85,7 @@ CONFIG = { "max_image_age_days", "max_concurrent_processing", "metadata_extraction_timeout", + "save_foreign_runs", }, "integrations": {"lora_manager_enabled", "has_civitai_api_key"}, } diff --git a/tests/test_settings_sources.py b/tests/test_settings_sources.py index 3ab7b031..2fda8459 100644 --- a/tests/test_settings_sources.py +++ b/tests/test_settings_sources.py @@ -323,6 +323,43 @@ class TestMonitoringSetting(SourcesTestCase): self.assertIs(GalleryConfig.MONITORING_ENABLED, False) +class TestSaveForeignRunsSetting(SourcesTestCase): + """#247 D-Q4: "Save prompts from other workflows", applied live.""" + + async def asyncSetUp(self): + await super().asyncSetUp() + saved = GalleryConfig.SAVE_FOREIGN_RUNS + self.addCleanup(setattr, GalleryConfig, "SAVE_FOREIGN_RUNS", saved) + GalleryConfig.SAVE_FOREIGN_RUNS = True + + async def test_read_and_write_published_live_without_a_restart(self): + self.assertIs((await self._get())["settings"]["save_foreign_runs"], True) + self._install_monitor(FakeMonitor([self.output_dir])) + status, data = await self._post( + "/prompt_manager/settings", {"save_foreign_runs": False} + ) + self.assertEqual(status, 200, data) + self.assertTrue(data["persisted"]) + self.assertEqual(data["restart_required_fields"], []) + saved = self._read_config() + self.assertIs(saved["gallery"]["monitoring"]["save_foreign_runs"], False) + self.assertIs(GalleryConfig.SAVE_FOREIGN_RUNS, False) + self.assertIs((await self._get())["settings"]["save_foreign_runs"], False) + + async def test_only_true_or_false_is_accepted(self): + self._write_config({"custom": {"keep": True}}) + before = self._config_bytes() + for value in ("false", 0, None, []): + with self.subTest(value=value): + status, data = await self._post( + "/prompt_manager/settings", {"save_foreign_runs": value} + ) + self.assertEqual(status, 400, data) + self.assertIn("save_foreign_runs must be true or false", data["error"]) + self.assertEqual(self._config_bytes(), before) + self.assertIs(GalleryConfig.SAVE_FOREIGN_RUNS, True) + + class TestSourceChangeHold(SourcesTestCase): async def test_root_save_refused_while_scan_job_runs(self): GalleryConfig.MONITORING_DIRECTORIES = [self._root(self.a)]