mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-28 08:21:27 -03:00
feat(settings): add explicit settings dir override for sandboxed runs
Add LORA_MANAGER_SETTINGS_DIR env var and standalone --settings-path to pin the settings location (settings.json, cache/, wildcards/, backups/, logs/, stats/) to an arbitrary directory. The override takes precedence over portable mode and the platform user config dir, and skips legacy migration, so sandboxed dev/E2E runs no longer need to write settings.json in the repo root or collide with the real instance. standalone.py pre-scans argv for --settings-path at import time because the settings location is resolved before main() parses arguments. SettingsManager portable-switch migration is a no-op while the directory is pinned. Update the lora-manager-e2e skill (prefer --settings-path sandboxing; start_server.py passes it through) and the lora-manager-runtime-context skill (document precedence; inspect script honors the override).
This commit is contained in:
@@ -293,6 +293,36 @@ def test_switching_back_to_user_config_moves_subdirectories(tmp_path, monkeypatc
|
||||
) == "project_wildcard"
|
||||
|
||||
|
||||
def test_portable_switch_ignored_when_settings_dir_pinned(tmp_path, monkeypatch):
|
||||
"""An explicit settings dir (--settings-path) must never trigger the
|
||||
portable-mode directory migration between project root and user config."""
|
||||
project_root, user_dir, user_settings = _setup_storage_paths(tmp_path, monkeypatch)
|
||||
_populate_settings_dir(user_dir)
|
||||
|
||||
custom_dir = tmp_path / "custom_settings"
|
||||
custom_dir.mkdir()
|
||||
monkeypatch.setattr(
|
||||
"py.services.settings_manager.ensure_settings_file",
|
||||
lambda logger=None: str(custom_dir / "settings.json"),
|
||||
)
|
||||
settings_paths.set_settings_dir_override(str(custom_dir))
|
||||
try:
|
||||
manager = SettingsManager()
|
||||
manager.settings_file = str(custom_dir / "settings.json")
|
||||
|
||||
manager.set("use_portable_settings", True)
|
||||
|
||||
# Settings file stays pinned; no directories are migrated anywhere and
|
||||
# the settings file is not mirrored to the user config dir.
|
||||
assert manager.settings_file == str(custom_dir / "settings.json")
|
||||
assert not (project_root / "cache").exists()
|
||||
assert not (project_root / "backups").exists()
|
||||
assert not (project_root / "settings.json").exists()
|
||||
assert not user_settings.exists()
|
||||
finally:
|
||||
settings_paths.set_settings_dir_override(None)
|
||||
|
||||
|
||||
def test_download_path_template_parses_json_string(manager):
|
||||
templates = {"lora": "{author}", "checkpoint": "{author}", "embedding": "{author}"}
|
||||
manager.settings["download_path_templates"] = json.dumps(templates)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import importlib
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -112,3 +114,44 @@ def test_validate_settings_logs_warnings(tmp_path, monkeypatch, caplog):
|
||||
|
||||
messages = [record.message for record in caplog.records]
|
||||
assert any("Standalone mode is using fallback configuration values." in message for message in messages)
|
||||
|
||||
|
||||
@pytest.mark.no_settings_dir_isolation
|
||||
def test_explicit_settings_dir_env_used_by_manager(tmp_path, monkeypatch):
|
||||
"""LORA_MANAGER_SETTINGS_DIR pins the settings file for the manager."""
|
||||
custom_dir = tmp_path / "custom"
|
||||
monkeypatch.setenv("LORA_MANAGER_SETTINGS_DIR", str(custom_dir))
|
||||
reset_settings_manager()
|
||||
|
||||
manager = get_settings_manager()
|
||||
|
||||
assert settings_paths.is_settings_dir_pinned()
|
||||
assert Path(manager.settings_file) == custom_dir / "settings.json"
|
||||
assert settings_paths.get_settings_dir() == str(custom_dir)
|
||||
|
||||
|
||||
def test_apply_settings_dir_from_argv():
|
||||
"""standalone's argv pre-scan publishes --settings-path into the env."""
|
||||
import standalone
|
||||
|
||||
# The helper writes to os.environ directly; manage the variable manually so
|
||||
# monkeypatch's undo stack cannot restore a stale value after the test.
|
||||
previous = os.environ.pop("LORA_MANAGER_SETTINGS_DIR", None)
|
||||
try:
|
||||
standalone._apply_settings_dir_from_argv(
|
||||
["--port", "8199", "--settings-path", "/tmp/xyz-e2e-settings"]
|
||||
)
|
||||
assert os.environ["LORA_MANAGER_SETTINGS_DIR"] == "/tmp/xyz-e2e-settings"
|
||||
|
||||
os.environ.pop("LORA_MANAGER_SETTINGS_DIR", None)
|
||||
standalone._apply_settings_dir_from_argv(["--settings-path=/tmp/abc-e2e"])
|
||||
assert os.environ["LORA_MANAGER_SETTINGS_DIR"] == "/tmp/abc-e2e"
|
||||
|
||||
os.environ.pop("LORA_MANAGER_SETTINGS_DIR", None)
|
||||
standalone._apply_settings_dir_from_argv(["--port", "8199"])
|
||||
assert "LORA_MANAGER_SETTINGS_DIR" not in os.environ
|
||||
finally:
|
||||
if previous is None:
|
||||
os.environ.pop("LORA_MANAGER_SETTINGS_DIR", None)
|
||||
else:
|
||||
os.environ["LORA_MANAGER_SETTINGS_DIR"] = previous
|
||||
|
||||
@@ -6,7 +6,26 @@ import os
|
||||
|
||||
import pytest
|
||||
|
||||
from py.utils.settings_paths import _should_use_portable_settings
|
||||
from py.utils.settings_paths import (
|
||||
SETTINGS_DIR_ENV,
|
||||
_should_use_portable_settings,
|
||||
ensure_settings_file,
|
||||
get_settings_dir,
|
||||
get_settings_dir_override,
|
||||
is_settings_dir_pinned,
|
||||
set_settings_dir_override,
|
||||
)
|
||||
|
||||
|
||||
def _redirect_paths(tmp_path, monkeypatch):
|
||||
"""Pin project root and user config dir resolution to temp paths."""
|
||||
monkeypatch.setattr(
|
||||
"py.utils.settings_paths.get_project_root", lambda: str(tmp_path / "repo")
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"py.utils.settings_paths.user_config_dir",
|
||||
lambda *args, **kwargs: str(tmp_path / "user_config"),
|
||||
)
|
||||
|
||||
|
||||
class TestShouldUsePortableSettings:
|
||||
@@ -54,3 +73,69 @@ class TestShouldUsePortableSettings:
|
||||
mp.setenv("LORA_MANAGER_PORTABLE", "1")
|
||||
result = _should_use_portable_settings(str(missing), logging.getLogger())
|
||||
assert result is True
|
||||
|
||||
|
||||
class TestExplicitSettingsDirOverride:
|
||||
"""Tests for the LORA_MANAGER_SETTINGS_DIR / --settings-path override."""
|
||||
|
||||
def test_env_override_wins_over_portable_and_user_config(self, tmp_path, monkeypatch):
|
||||
_redirect_paths(tmp_path, monkeypatch)
|
||||
repo = tmp_path / "repo"
|
||||
repo.mkdir()
|
||||
(repo / "settings.json").write_text(
|
||||
json.dumps({"use_portable_settings": True}), encoding="utf-8"
|
||||
)
|
||||
custom_dir = tmp_path / "custom" / "e2e"
|
||||
|
||||
monkeypatch.setenv(SETTINGS_DIR_ENV, str(custom_dir))
|
||||
monkeypatch.delenv("LORA_MANAGER_PORTABLE", raising=False)
|
||||
|
||||
assert is_settings_dir_pinned()
|
||||
target = get_settings_dir(create=True)
|
||||
assert target == str(custom_dir.resolve())
|
||||
assert target == os.path.abspath(str(custom_dir))
|
||||
assert custom_dir.is_dir()
|
||||
|
||||
# The override is independent of the effective use_portable flag.
|
||||
with pytest.MonkeyPatch.context() as mp:
|
||||
mp.setenv("LORA_MANAGER_PORTABLE", "1")
|
||||
assert get_settings_dir(create=False) == os.path.abspath(str(custom_dir))
|
||||
|
||||
def test_env_override_normalizes_tilde(self, monkeypatch):
|
||||
monkeypatch.setenv(SETTINGS_DIR_ENV, "~/lm-e2e-settings")
|
||||
override = get_settings_dir_override()
|
||||
assert override == os.path.abspath(os.path.expanduser("~/lm-e2e-settings"))
|
||||
|
||||
def test_ensure_settings_file_pins_path_and_skips_migration(self, tmp_path, monkeypatch):
|
||||
_redirect_paths(tmp_path, monkeypatch)
|
||||
repo = tmp_path / "repo"
|
||||
repo.mkdir()
|
||||
legacy = repo / "settings.json"
|
||||
legacy.write_text(json.dumps({"language": "ja"}), encoding="utf-8")
|
||||
custom_dir = tmp_path / "custom"
|
||||
|
||||
monkeypatch.setenv(SETTINGS_DIR_ENV, str(custom_dir))
|
||||
|
||||
settings_file = ensure_settings_file()
|
||||
assert settings_file == os.path.join(os.path.abspath(str(custom_dir)), "settings.json")
|
||||
assert os.path.isdir(str(custom_dir))
|
||||
# The legacy (project-root) file must NOT be migrated into the custom dir.
|
||||
assert legacy.exists()
|
||||
assert not (custom_dir / "settings.json").exists()
|
||||
assert not (tmp_path / "user_config" / "settings.json").exists()
|
||||
|
||||
def test_programmatic_override_and_clear(self, tmp_path, monkeypatch):
|
||||
_redirect_paths(tmp_path, monkeypatch)
|
||||
monkeypatch.delenv(SETTINGS_DIR_ENV, raising=False)
|
||||
|
||||
custom_dir = tmp_path / "prog"
|
||||
previous = set_settings_dir_override(str(custom_dir))
|
||||
assert previous is None
|
||||
assert is_settings_dir_pinned()
|
||||
assert get_settings_dir(create=False) == os.path.abspath(str(custom_dir))
|
||||
|
||||
previous = set_settings_dir_override(None)
|
||||
assert previous == os.path.abspath(str(custom_dir))
|
||||
assert not is_settings_dir_pinned()
|
||||
# Falls back to the (redirected) platform user config dir.
|
||||
assert get_settings_dir(create=False) == str(tmp_path / "user_config")
|
||||
|
||||
Reference in New Issue
Block a user