From 1a6b0f78a393b7c59fee3dbfcb02175ecdc3af1a Mon Sep 17 00:00:00 2001 From: Will Miao Date: Fri, 9 Oct 2026 11:10:04 +0800 Subject: [PATCH] chore(libraries): deprecate multi-library support ahead of removal Warn at startup (log + dismissible startup message) when settings.json contains more than one library, marking the feature as deprecated while keeping it fully functional. Removal is planned for one of the next releases, at which point all libraries' folder paths will be merged into a single library. Multiple folder roots within one library remain fully supported and unaffected. - SettingsManager: add _warn_if_multi_library() invoked from _migrate_to_library_registry - Settings UI: label the Active Library selector as deprecated and note the removal plan in its help text (en.json) - Docs: add deprecation banners to multi_library_design.md and library-switching.md - Remove dead code: SettingsManager.update_active_library_paths had no call sites --- docs/architecture/multi_library_design.md | 5 ++ docs/library-switching.md | 5 ++ locales/en.json | 4 +- py/services/settings_manager.py | 61 ++++++++++++----------- tests/services/test_settings_manager.py | 53 ++++++++++++++++++++ 5 files changed, 98 insertions(+), 30 deletions(-) diff --git a/docs/architecture/multi_library_design.md b/docs/architecture/multi_library_design.md index 3e222f0e..8022df5a 100644 --- a/docs/architecture/multi_library_design.md +++ b/docs/architecture/multi_library_design.md @@ -1,5 +1,10 @@ # Multi-Library Management for Standalone Mode +> **Deprecated:** Multi-library support is deprecated and will be removed in an +> upcoming release. Multiple folder roots within a single library +> (`folder_paths` / `extra_folder_paths`) remain fully supported. This document +> is kept for historical reference only. + ## Requirements Summary - **Independent libraries**: In standalone mode, users can maintain multiple libraries, where each library represents a distinct set of model folders (LoRAs, checkpoints, embeddings, etc.). Only one library is active at any given time, but users need a fast way to switch between them. - **Library-specific settings**: The fields that vary per library are `folder_paths`, `default_lora_root`, `default_checkpoint_root`, and `default_embedding_root` inside `settings.json`. diff --git a/docs/library-switching.md b/docs/library-switching.md index b75db4f7..824ca946 100644 --- a/docs/library-switching.md +++ b/docs/library-switching.md @@ -1,5 +1,10 @@ # Library Switching and Preview Routes +> **Deprecated:** Multi-library support (and therefore library switching) is +> deprecated and will be removed in an upcoming release. The dynamic preview +> route described here will remain — it also serves single-library +> installations. + Library switching no longer requires restarting the backend. The preview thumbnails shown in the UI are now served through a dynamic endpoint that resolves files against the folders registered for the active library at request diff --git a/locales/en.json b/locales/en.json index 040a5f89..0db5e294 100644 --- a/locales/en.json +++ b/locales/en.json @@ -555,8 +555,8 @@ "cardBlurAmountHelp": "Adjust the blur intensity of the header and footer overlays on model and recipe cards (0 = no blur, 20 = maximum blur)." }, "folderSettings": { - "activeLibrary": "Active Library", - "activeLibraryHelp": "Switch between configured libraries to update default folders. Changing the selection reloads the page.", + "activeLibrary": "Active Library (Deprecated)", + "activeLibraryHelp": "Switch between configured libraries to update default folders. Changing the selection reloads the page. Deprecated: multi-library support will be removed in an upcoming release. Multiple folder roots within a single library remain fully supported.", "loadingLibraries": "Loading libraries...", "noLibraries": "No libraries configured", "defaultLoraRoot": "LoRA Root", diff --git a/py/services/settings_manager.py b/py/services/settings_manager.py index e06910af..21b71a12 100644 --- a/py/services/settings_manager.py +++ b/py/services/settings_manager.py @@ -583,6 +583,39 @@ class SettingsManager: if changed and initial_bootstrap: self._needs_initial_save = True + self._warn_if_multi_library() + + def _warn_if_multi_library(self) -> None: + """Surface a deprecation notice when more than one library is configured.""" + libraries = self.settings.get("libraries", {}) + if not isinstance(libraries, Mapping) or len(libraries) <= 1: + return + + names = ", ".join(sorted(str(name) for name in libraries)) + logger.warning( + "Multi-library support is deprecated and will be removed in an " + "upcoming release. Detected %d libraries in settings.json (%s). " + "Multiple folder roots within a single library remain fully " + "supported; please consolidate your libraries into one.", + len(libraries), + names, + ) + self._add_startup_message( + code="multi-library-deprecated", + title="Multi-library support is deprecated", + message=( + f"This installation has {len(libraries)} libraries configured in " + f"settings.json ({names}). Multi-library support is deprecated and " + "will be removed in an upcoming release; at that point all " + "libraries' folder paths will be merged into a single library. " + "Multiple folder roots within a single library remain fully " + "supported, so consider consolidating into one library now." + ), + severity="warning", + actions=self._default_settings_actions(), + dismissible=True, + ) + def _sync_active_library_to_root(self, *, save: bool = False) -> None: """Update top-level folder path settings to mirror the active library.""" libraries = self.settings.get("libraries", {}) @@ -2382,34 +2415,6 @@ class SettingsManager: if was_active: self._notify_library_change(self.settings["active_library"]) - def update_active_library_paths( - self, - folder_paths: Mapping[str, Iterable[str]], - *, - extra_folder_paths: Optional[Mapping[str, Iterable[str]]] = None, - default_lora_root: Optional[str] = None, - default_checkpoint_root: Optional[str] = None, - default_unet_root: Optional[str] = None, - default_embedding_root: Optional[str] = None, - default_other_roots: Optional[Mapping[str, str]] = None, - recipes_path: Optional[str] = None, - ) -> None: - """Update folder paths for the active library.""" - - active_name = self.get_active_library_name() - self.upsert_library( - active_name, - folder_paths=folder_paths, - extra_folder_paths=extra_folder_paths, - default_lora_root=default_lora_root, - default_checkpoint_root=default_checkpoint_root, - default_unet_root=default_unet_root, - default_embedding_root=default_embedding_root, - default_other_roots=default_other_roots, - recipes_path=recipes_path, - activate=True, - ) - def _notify_library_change(self, library_name: str) -> None: """Notify dependent services that the active library changed.""" libraries = self.settings.get("libraries", {}) diff --git a/tests/services/test_settings_manager.py b/tests/services/test_settings_manager.py index 4bdbd44d..afa16004 100644 --- a/tests/services/test_settings_manager.py +++ b/tests/services/test_settings_manager.py @@ -1,6 +1,7 @@ import asyncio import copy import json +import logging import os from concurrent.futures import Future @@ -133,6 +134,58 @@ def test_existing_folder_paths_seed_default_library(tmp_path, monkeypatch): assert manager.get_startup_messages() == [] +def test_multi_library_configuration_triggers_deprecation_warning( + tmp_path, monkeypatch, caplog +): + initial = { + "libraries": { + "default": {"folder_paths": {"loras": ["/loras-a"]}}, + "archive": {"folder_paths": {"loras": ["/loras-b"]}}, + }, + "active_library": "default", + } + + with caplog.at_level(logging.WARNING, logger="py.services.settings_manager"): + manager = _create_manager_with_settings(tmp_path, monkeypatch, initial) + + assert any( + "Multi-library support is deprecated" in record.getMessage() + for record in caplog.records + ) + deprecation = [ + message + for message in manager.get_startup_messages() + if message["code"] == "multi-library-deprecated" + ] + assert len(deprecation) == 1 + assert deprecation[0]["severity"] == "warning" + assert deprecation[0]["dismissible"] is True + assert "archive" in deprecation[0]["message"] + + +def test_single_library_configuration_has_no_deprecation_warning( + tmp_path, monkeypatch, caplog +): + initial = { + "libraries": { + "default": {"folder_paths": {"loras": ["/loras-a"]}}, + }, + "active_library": "default", + } + + with caplog.at_level(logging.WARNING, logger="py.services.settings_manager"): + manager = _create_manager_with_settings(tmp_path, monkeypatch, initial) + + assert not any( + "Multi-library support is deprecated" in record.getMessage() + for record in caplog.records + ) + assert not any( + message["code"] == "multi-library-deprecated" + for message in manager.get_startup_messages() + ) + + def test_environment_variable_overrides_settings(tmp_path, monkeypatch): monkeypatch.setattr(SettingsManager, "_save_settings", lambda self: None) monkeypatch.setenv("CIVITAI_API_KEY", "secret")