Merge pull request #634 from willmiao/codex/refactor-config-to-handle-default-library

fix: drop template default library when saving ComfyUI paths
This commit is contained in:
pixelpaws
2025-11-02 09:12:26 +08:00
committed by GitHub
3 changed files with 194 additions and 6 deletions

View File

@@ -2,12 +2,12 @@ import os
import platform import platform
from pathlib import Path from pathlib import Path
import folder_paths # type: ignore import folder_paths # type: ignore
from typing import Dict, Iterable, List, Mapping, Set from typing import Any, Dict, Iterable, List, Mapping, Optional, Set
import logging import logging
import json import json
import urllib.parse import urllib.parse
from .utils.settings_paths import ensure_settings_file from .utils.settings_paths import ensure_settings_file, load_settings_template
# Use an environment variable to control standalone mode # Use an environment variable to control standalone mode
standalone_mode = os.environ.get("LORA_MANAGER_STANDALONE", "0") == "1" or os.environ.get("HF_HUB_DISABLE_TELEMETRY", "0") == "0" standalone_mode = os.environ.get("LORA_MANAGER_STANDALONE", "0") == "1" or os.environ.get("HF_HUB_DISABLE_TELEMETRY", "0") == "0"
@@ -45,6 +45,30 @@ def _normalize_folder_paths_for_comparison(
return normalized return normalized
def _normalize_library_folder_paths(
library_payload: Mapping[str, Any]
) -> Dict[str, Set[str]]:
"""Return normalized folder paths extracted from a library payload."""
folder_paths = library_payload.get("folder_paths")
if isinstance(folder_paths, Mapping):
return _normalize_folder_paths_for_comparison(folder_paths)
return {}
def _get_template_folder_paths() -> Dict[str, Set[str]]:
"""Return normalized folder paths defined in the bundled template."""
template_payload = load_settings_template()
if not template_payload:
return {}
folder_paths = template_payload.get("folder_paths")
if isinstance(folder_paths, Mapping):
return _normalize_folder_paths_for_comparison(folder_paths)
return {}
class Config: class Config:
"""Global configuration for LoRA Manager""" """Global configuration for LoRA Manager"""
@@ -81,6 +105,43 @@ class Config:
comfy_library = libraries.get("comfyui", {}) comfy_library = libraries.get("comfyui", {})
default_library = libraries.get("default", {}) default_library = libraries.get("default", {})
template_folder_paths = _get_template_folder_paths()
default_library_paths: Dict[str, Set[str]] = {}
if isinstance(default_library, Mapping):
default_library_paths = _normalize_library_folder_paths(default_library)
libraries_changed = False
if (
isinstance(default_library, Mapping)
and template_folder_paths
and default_library_paths == template_folder_paths
):
if "comfyui" in libraries:
try:
settings_service.delete_library("default")
libraries_changed = True
logger.info("Removed template 'default' library entry")
except Exception as delete_error:
logger.debug(
"Failed to delete template 'default' library: %s",
delete_error,
)
else:
try:
settings_service.rename_library("default", "comfyui")
libraries_changed = True
logger.info("Renamed template 'default' library to 'comfyui'")
except Exception as rename_error:
logger.debug(
"Failed to rename template 'default' library: %s",
rename_error,
)
if libraries_changed:
libraries = settings_service.get_libraries()
comfy_library = libraries.get("comfyui", {})
default_library = libraries.get("default", {})
target_folder_paths = { target_folder_paths = {
'loras': list(self.loras_roots), 'loras': list(self.loras_roots),
'checkpoints': list(self.checkpoints_roots or []), 'checkpoints': list(self.checkpoints_roots or []),
@@ -90,9 +151,16 @@ class Config:
normalized_target_paths = _normalize_folder_paths_for_comparison(target_folder_paths) normalized_target_paths = _normalize_folder_paths_for_comparison(target_folder_paths)
if (not comfy_library and default_library and normalized_target_paths and normalized_default_paths: Optional[Dict[str, Set[str]]] = None
_normalize_folder_paths_for_comparison(default_library.get("folder_paths", {})) == if isinstance(default_library, Mapping):
normalized_target_paths): normalized_default_paths = _normalize_library_folder_paths(default_library)
if (
not comfy_library
and default_library
and normalized_target_paths
and normalized_default_paths == normalized_target_paths
):
try: try:
settings_service.rename_library("default", "comfyui") settings_service.rename_library("default", "comfyui")
logger.info("Renamed legacy 'default' library to 'comfyui'") logger.info("Renamed legacy 'default' library to 'comfyui'")

View File

@@ -6,7 +6,7 @@ import json
import logging import logging
import os import os
import shutil import shutil
from typing import Optional from typing import Any, Dict, Optional
from platformdirs import user_config_dir from platformdirs import user_config_dir
@@ -129,3 +129,32 @@ def _should_use_portable_settings(path: str, logger: logging.Logger) -> bool:
) )
return False return False
def load_settings_template() -> Optional[Dict[str, Any]]:
"""Return the parsed contents of ``settings.json.example`` when available."""
template_path = os.path.join(get_project_root(), "settings.json.example")
try:
with open(template_path, "r", encoding="utf-8") as handle:
payload = json.load(handle)
except FileNotFoundError:
_LOGGER.debug("settings.json.example not found at %s", template_path)
return None
except json.JSONDecodeError as exc:
_LOGGER.warning("Failed to parse settings.json.example: %s", exc)
return None
except OSError as exc:
_LOGGER.warning(
"Could not read settings.json.example at %s: %s", template_path, exc
)
return None
if not isinstance(payload, dict):
_LOGGER.debug(
"settings.json.example at %s does not contain a JSON object", template_path
)
return None
return payload

View File

@@ -43,6 +43,7 @@ def test_save_paths_renames_default_library(monkeypatch: pytest.MonkeyPatch, tmp
def __init__(self, default_paths: Dict[str, List[str]]): def __init__(self, default_paths: Dict[str, List[str]]):
self._default_paths = default_paths self._default_paths = default_paths
self.rename_calls = [] self.rename_calls = []
self.delete_calls = []
self.upsert_calls = [] self.upsert_calls = []
self._renamed = False self._renamed = False
@@ -62,6 +63,10 @@ def test_save_paths_renames_default_library(monkeypatch: pytest.MonkeyPatch, tmp
self.rename_calls.append((old_name, new_name)) self.rename_calls.append((old_name, new_name))
self._renamed = True self._renamed = True
def delete_library(self, name: str): # pragma: no cover - defensive guard
self.delete_calls.append(name)
raise AssertionError("delete_library should not be invoked in this scenario")
def upsert_library(self, name: str, **payload): def upsert_library(self, name: str, **payload):
self.upsert_calls.append((name, payload)) self.upsert_calls.append((name, payload))
@@ -124,3 +129,89 @@ def test_save_paths_logs_warning_when_upsert_fails(
assert isinstance(config_instance, config_module.Config) assert isinstance(config_instance, config_module.Config)
assert fake_settings.upsert_attempts and fake_settings.upsert_attempts[0][0] == "comfyui" assert fake_settings.upsert_attempts and fake_settings.upsert_attempts[0][0] == "comfyui"
assert "Failed to save folder paths: boom" in caplog.text assert "Failed to save folder paths: boom" in caplog.text
def test_save_paths_removes_template_default_library(monkeypatch, tmp_path):
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
placeholder_paths = {
"loras": [
"C:/path/to/your/loras_folder",
"C:/path/to/another/loras_folder",
],
"checkpoints": [
"C:/path/to/your/checkpoints_folder",
"C:/path/to/another/checkpoints_folder",
],
"embeddings": [
"C:/path/to/your/embeddings_folder",
"C:/path/to/another/embeddings_folder",
],
}
class FakeSettingsService:
def __init__(self):
self.libraries = {
"default": {
"folder_paths": placeholder_paths,
"default_lora_root": "",
"default_checkpoint_root": "",
"default_embedding_root": "",
}
}
self.rename_calls = []
self.delete_calls = []
self.upsert_calls = []
def get_libraries(self):
return self.libraries
def rename_library(self, old_name: str, new_name: str):
self.rename_calls.append((old_name, new_name))
self.libraries[new_name] = self.libraries.pop(old_name)
def delete_library(self, name: str):
self.delete_calls.append(name)
self.libraries.pop(name, None)
def upsert_library(self, name: str, **payload):
self.upsert_calls.append((name, payload))
self.libraries[name] = {**payload}
fake_settings = FakeSettingsService()
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
monkeypatch.setattr(
config_module,
"load_settings_template",
lambda: {"folder_paths": placeholder_paths},
)
config_instance = config_module.Config()
assert isinstance(config_instance, config_module.Config)
assert fake_settings.rename_calls == [("default", "comfyui")]
assert not fake_settings.delete_calls
assert len(fake_settings.upsert_calls) == 1
assert "default" not in fake_settings.libraries
assert set(fake_settings.libraries.keys()) == {"comfyui"}
name, payload = fake_settings.upsert_calls[0]
assert name == "comfyui"
expected_folder_paths = {
key: [path.replace("\\", "/") for path in paths]
for key, paths in folder_paths.items()
}
assert payload["folder_paths"] == expected_folder_paths
assert payload["default_lora_root"] == folder_paths["loras"][0].replace("\\", "/")
assert (
payload["default_checkpoint_root"]
== folder_paths["checkpoints"][0].replace("\\", "/")
)
assert (
payload["default_embedding_root"]
== folder_paths["embeddings"][0].replace("\\", "/")
)
assert payload["metadata"] == {"display_name": "ComfyUI", "source": "comfyui"}
assert payload["activate"] is True