diff --git a/py/lora_utils.py b/py/lora_utils.py index 83091db..da60605 100644 --- a/py/lora_utils.py +++ b/py/lora_utils.py @@ -82,23 +82,46 @@ def detect_lora_manager(custom_path: str = "") -> Optional[str]: if p.is_dir() and _looks_like_lora_manager(p): return str(p.resolve()) - # 2. Auto-detect via custom_nodes (case-insensitive scan) + # 2. Auto-detect in every custom_nodes folder (case-insensitive scan): + # ComfyUI's own first, then the extra ones it registered (#144). + folders = [] root = find_comfyui_root() if root: - custom_nodes = root / "custom_nodes" - if custom_nodes.is_dir(): - for entry in custom_nodes.iterdir(): - if ( - entry.is_dir() - and "lora" in entry.name.lower() - and "manager" in entry.name.lower() - and _looks_like_lora_manager(entry) - ): - return str(entry.resolve()) + folders.append(root / "custom_nodes") + folders.extend(custom_nodes_directories()) + seen = set() + for custom_nodes in folders: + key = os.path.normcase(os.path.abspath(str(custom_nodes))) + if key in seen: + continue + seen.add(key) + found = _lora_manager_in(Path(custom_nodes)) + if found: + return found return None +def _lora_manager_in(custom_nodes: Path) -> Optional[str]: + """The LoraManager install directly under ``custom_nodes``, or None.""" + try: + entries = sorted(custom_nodes.iterdir()) if custom_nodes.is_dir() else [] + except OSError: + return None + for entry in entries: + try: + if ( + entry.is_dir() + and "lora" in entry.name.lower() + and "manager" in entry.name.lower() + and _looks_like_lora_manager(entry) + ): + return str(entry.resolve()) + except OSError: + continue + return None + + def _looks_like_lora_manager(path: Path) -> bool: """Heuristic: does this directory look like a LoraManager install?""" # Must have __init__.py (ComfyUI extension) or README.md diff --git a/tests/test_lora_utils.py b/tests/test_lora_utils.py index b273643..aa8eae1 100644 --- a/tests/test_lora_utils.py +++ b/tests/test_lora_utils.py @@ -817,9 +817,66 @@ class TestDetectLoraManager(unittest.TestCase): self.assertTrue(lora_utils._looks_like_lora_manager(lm)) def test_nothing_found(self): - with patch.object(lora_utils, "find_comfyui_root", return_value=None): + with ( + patch.object(lora_utils, "find_comfyui_root", return_value=None), + patch.object(lora_utils, "custom_nodes_directories", return_value=[]), + ): self.assertIsNone(lora_utils.detect_lora_manager()) + def test_auto_detects_in_an_extra_custom_nodes_folder(self): + # #144: ComfyUI's main custom_nodes has no LoraManager; an extra + # custom_nodes path registered through folder_paths (extra model + # paths, --base-directory) has it. + root = _make_comfy_root(self.tmp) + extra = self.tmp / "shared_nodes" + extra.mkdir() + lm = _make_lora_manager(extra, "ComfyUI-Lora-Manager") + fake = types.SimpleNamespace( + base_path=str(root), + get_folder_paths=lambda name: ( + [str(root / "custom_nodes"), str(extra)] + if name == "custom_nodes" + else [] + ), + ) + with ( + patch.dict(sys.modules, {"folder_paths": fake}), + patch.object(lora_utils, "find_comfyui_root", return_value=root), + ): + self.assertEqual(lora_utils.detect_lora_manager(), str(lm.resolve())) + + def test_main_custom_nodes_wins_over_an_extra_folder(self): + root = _make_comfy_root(self.tmp) + main = _make_lora_manager(root / "custom_nodes") + extra = self.tmp / "shared_nodes" + extra.mkdir() + _make_lora_manager(extra) + with ( + patch.object(lora_utils, "find_comfyui_root", return_value=root), + patch.object( + lora_utils, + "custom_nodes_directories", + return_value=[(root / "custom_nodes").resolve(), extra.resolve()], + ), + ): + self.assertEqual(lora_utils.detect_lora_manager(), str(main.resolve())) + + def test_an_unreadable_extra_folder_is_skipped(self): + root = _make_comfy_root(self.tmp) + missing = self.tmp / "gone" + extra = self.tmp / "shared_nodes" + extra.mkdir() + lm = _make_lora_manager(extra) + with ( + patch.object(lora_utils, "find_comfyui_root", return_value=root), + patch.object( + lora_utils, + "custom_nodes_directories", + return_value=[missing, extra.resolve()], + ), + ): + self.assertEqual(lora_utils.detect_lora_manager(), str(lm.resolve())) + class TestFindLoraDirectories(unittest.TestCase):