mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-20 18:51:26 -03:00
fix(other-models): stop warning about legacy folder keys that alias
Enabling Other Models logged two warnings on a stock ComfyUI install:
Detected the same folder '.../clip' under multiple other-model categories
('.../clip' is already mapped). Keeping the first category; please fix
your path configuration.
Nothing was wrong with the configuration. ComfyUI's folder_paths rewrites
legacy names before every access (map_legacy: clip -> text_encoders,
unet -> diffusion_models) and registers both legacy directories under the
canonical key, so get_folder_paths("clip") returns exactly the same list as
get_folder_paths("text_encoders"). Both keys are in the enabled allow-list,
so the second pass hit the overlap guard for every text-encoder folder and
printed advice the user cannot act on. The path list itself was correct
(deduped), only the message was wrong.
- Config._collapse_legacy_folder_keys() drops a key when the host exposes
map_legacy and resolves it to another queried key. That is provably
lossless: an empty canonical list implies an empty alias list. The
standalone MockFolderPaths has no map_legacy and its keys are independent
settings.json entries, so every key is still queried there.
- _prepare_other_paths() now tracks the claiming sub_type alongside the
business path and downgrades a same-sub_type duplicate to debug, keeping
the warning for a genuine cross-category collision (and naming the other
category in the message).
Regression tests cover the aliased-key layout (no warning, no redundant
query, both folders still managed) and the same-sub_type duplicate, and the
opt-in test is parametrized over controlnet and clip_vision.
This commit is contained in:
@@ -116,6 +116,50 @@ class TestPrepareOtherPaths:
|
||||
]
|
||||
assert len(warnings) == 1
|
||||
|
||||
def test_same_sub_type_duplicate_is_debug_not_warning(self, tmp_path, caplog):
|
||||
"""A sub_type spanning two folder keys legitimately sees a folder twice.
|
||||
|
||||
``clip`` and ``text_encoders`` both map to ``text_encoder``, so a folder
|
||||
reachable through both is expected and must not tell the user to fix a
|
||||
configuration they cannot fix.
|
||||
"""
|
||||
text_encoders_dir = tmp_path / "text_encoders"
|
||||
legacy_clip_dir = tmp_path / "clip"
|
||||
text_encoders_dir.mkdir()
|
||||
legacy_clip_dir.mkdir()
|
||||
|
||||
config = _make_config()
|
||||
with caplog.at_level(logging.DEBUG, logger=config_module.logger.name):
|
||||
unique, sub_type_map, per_key = config._prepare_other_paths(
|
||||
{
|
||||
"text_encoders": [str(text_encoders_dir)],
|
||||
"clip": [str(legacy_clip_dir), str(text_encoders_dir)],
|
||||
}
|
||||
)
|
||||
|
||||
assert set(unique) == {
|
||||
_normalize(str(text_encoders_dir)),
|
||||
_normalize(str(legacy_clip_dir)),
|
||||
}
|
||||
assert sub_type_map[_normalize(str(legacy_clip_dir))] == "text_encoder"
|
||||
assert per_key["clip"] == [_normalize(str(legacy_clip_dir))]
|
||||
|
||||
warnings = [
|
||||
record.message
|
||||
for record in caplog.records
|
||||
if record.levelname == "WARNING"
|
||||
and "multiple other-model categories" in record.message
|
||||
]
|
||||
assert warnings == []
|
||||
|
||||
debug_messages = [
|
||||
record.message
|
||||
for record in caplog.records
|
||||
if record.levelname == "DEBUG"
|
||||
and "Ignoring duplicate folder" in record.message
|
||||
]
|
||||
assert len(debug_messages) == 1
|
||||
|
||||
def test_cross_scanner_overlap_warns_but_keeps_path(self, tmp_path, caplog):
|
||||
"""An other root overlapping a checkpoint root warns but stays managed."""
|
||||
shared = tmp_path / "shared_models"
|
||||
@@ -175,7 +219,7 @@ class TestInitOtherPaths:
|
||||
config_module.folder_paths, "get_folder_paths", get_folder_paths
|
||||
)
|
||||
|
||||
def test_default_enabled_keys_exclude_controlnet(self, monkeypatch, tmp_path):
|
||||
def test_default_enabled_keys_exclude_opt_in_types(self, monkeypatch, tmp_path):
|
||||
dirs = {}
|
||||
for key in (
|
||||
"vae",
|
||||
@@ -194,25 +238,79 @@ class TestInitOtherPaths:
|
||||
config = _make_config()
|
||||
roots = config._init_other_paths()
|
||||
|
||||
assert _normalize(dirs["controlnet"]) not in roots
|
||||
assert _normalize(dirs["controlnet"]) not in config.other_root_subtypes
|
||||
for key in ("vae", "upscale_models", "text_encoders", "clip", "clip_vision"):
|
||||
# clip_vision and controlnet are workflow-driven categories and stay
|
||||
# opt-in; only VAE / upscaler / text encoder are managed by default.
|
||||
for key in ("clip_vision", "controlnet"):
|
||||
assert _normalize(dirs[key]) not in roots
|
||||
assert _normalize(dirs[key]) not in config.other_root_subtypes
|
||||
# This stub has no map_legacy (standalone-shaped), so the legacy clip
|
||||
# key is queried on its own and its folder lands under text_encoder.
|
||||
for key in ("vae", "upscale_models", "text_encoders", "clip"):
|
||||
assert _normalize(dirs[key]) in roots
|
||||
|
||||
def test_controlnet_opt_in_via_setting(self, monkeypatch, tmp_path):
|
||||
controlnet_dir = tmp_path / "controlnet"
|
||||
controlnet_dir.mkdir()
|
||||
def test_legacy_key_is_not_queried_when_host_aliases_it(
|
||||
self, monkeypatch, tmp_path, caplog
|
||||
):
|
||||
"""ComfyUI resolves clip -> text_encoders, so only the canonical key is
|
||||
queried: its folder list already contains the legacy directory."""
|
||||
canonical_dir = tmp_path / "text_encoders"
|
||||
legacy_dir = tmp_path / "clip"
|
||||
canonical_dir.mkdir()
|
||||
legacy_dir.mkdir()
|
||||
|
||||
self._stub_folder_paths(monkeypatch, {"controlnet": str(controlnet_dir)})
|
||||
get_settings_manager().set("enabled_other_sub_types", ["controlnet"])
|
||||
queried = []
|
||||
|
||||
def get_folder_paths(key):
|
||||
queried.append(key)
|
||||
if key == "text_encoders":
|
||||
# Mirrors ComfyUI folder_paths: both directories are registered
|
||||
# under the canonical key.
|
||||
return [str(canonical_dir), str(legacy_dir)]
|
||||
return []
|
||||
|
||||
monkeypatch.setattr(
|
||||
config_module.folder_paths, "get_folder_paths", get_folder_paths
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
config_module.folder_paths,
|
||||
"map_legacy",
|
||||
lambda key: {"clip": "text_encoders"}.get(key, key),
|
||||
raising=False,
|
||||
)
|
||||
|
||||
config = _make_config()
|
||||
with caplog.at_level(logging.DEBUG, logger=config_module.logger.name):
|
||||
roots = config._init_other_paths()
|
||||
|
||||
assert "clip" not in queried
|
||||
assert _normalize(str(canonical_dir)) in roots
|
||||
assert _normalize(str(legacy_dir)) in roots
|
||||
assert (
|
||||
config.other_root_subtypes[_normalize(str(legacy_dir))]
|
||||
== "text_encoder"
|
||||
)
|
||||
# The reported bug: this layout used to log "please fix your path
|
||||
# configuration" twice for aliased keys the user cannot separate.
|
||||
assert [
|
||||
record.message
|
||||
for record in caplog.records
|
||||
if record.levelname == "WARNING"
|
||||
] == []
|
||||
|
||||
@pytest.mark.parametrize("opt_in_key", ["controlnet", "clip_vision"])
|
||||
def test_opt_in_sub_type_via_setting(self, monkeypatch, tmp_path, opt_in_key):
|
||||
opt_in_dir = tmp_path / opt_in_key
|
||||
opt_in_dir.mkdir()
|
||||
|
||||
self._stub_folder_paths(monkeypatch, {opt_in_key: str(opt_in_dir)})
|
||||
get_settings_manager().set("enabled_other_sub_types", [opt_in_key])
|
||||
|
||||
config = _make_config()
|
||||
roots = config._init_other_paths()
|
||||
|
||||
assert _normalize(str(controlnet_dir)) in roots
|
||||
assert _normalize(str(opt_in_dir)) in roots
|
||||
assert (
|
||||
config.other_root_subtypes[_normalize(str(controlnet_dir))]
|
||||
== "controlnet"
|
||||
config.other_root_subtypes[_normalize(str(opt_in_dir))] == opt_in_key
|
||||
)
|
||||
|
||||
def test_disabled_sub_type_is_not_scanned(self, monkeypatch, tmp_path):
|
||||
|
||||
Reference in New Issue
Block a user