|
| 1 | +import logging |
1 | 2 | import shutil
|
2 | 3 | import sys
|
| 4 | +import traceback |
3 | 5 | from importlib.util import module_from_spec, spec_from_file_location
|
4 | 6 | from pathlib import Path
|
5 | 7 |
|
6 | 8 |
|
7 |
| -def load_custom_nodes(custom_nodes_path: Path): |
| 9 | +def load_custom_nodes(custom_nodes_path: Path, logger: logging.Logger): |
8 | 10 | """
|
9 | 11 | Loads all custom nodes from the custom_nodes_path directory.
|
10 | 12 |
|
11 |
| - This function copies a custom __init__.py file to the custom_nodes_path directory, effectively turning it into a |
12 |
| - python module. |
| 13 | + If custom_nodes_path does not exist, it creates it. |
13 | 14 |
|
14 |
| - The custom __init__.py file itself imports all the custom node packs as python modules from the custom_nodes_path |
15 |
| - directory. |
| 15 | + It also copies the custom_nodes/README.md file to the custom_nodes_path directory. Because this file may change, |
| 16 | + it is _always_ copied to the custom_nodes_path directory. |
16 | 17 |
|
17 |
| - Then,the custom __init__.py file is programmatically imported using importlib. As it executes, it imports all the |
18 |
| - custom node packs as python modules. |
| 18 | + Then, it crawls the custom_nodes_path directory and imports all top-level directories as python modules. |
| 19 | +
|
| 20 | + If the directory does not contain an __init__.py file or starts with an `_` or `.`, it is skipped. |
19 | 21 | """
|
| 22 | + |
| 23 | + # create the custom nodes directory if it does not exist |
20 | 24 | custom_nodes_path.mkdir(parents=True, exist_ok=True)
|
21 | 25 |
|
22 |
| - custom_nodes_init_path = str(custom_nodes_path / "__init__.py") |
23 |
| - custom_nodes_readme_path = str(custom_nodes_path / "README.md") |
| 26 | + # Copy the README file to the custom nodes directory |
| 27 | + source_custom_nodes_readme_path = Path(__file__).parent / "custom_nodes/README.md" |
| 28 | + target_custom_nodes_readme_path = Path(custom_nodes_path) / "README.md" |
24 | 29 |
|
25 |
| - # copy our custom nodes __init__.py to the custom nodes directory |
26 |
| - shutil.copy(Path(__file__).parent / "custom_nodes/init.py", custom_nodes_init_path) |
27 |
| - shutil.copy(Path(__file__).parent / "custom_nodes/README.md", custom_nodes_readme_path) |
| 30 | + # copy our custom nodes README to the custom nodes directory |
| 31 | + shutil.copy(source_custom_nodes_readme_path, target_custom_nodes_readme_path) |
28 | 32 |
|
29 |
| - # set the same permissions as the destination directory, in case our source is read-only, |
30 |
| - # so that the files are user-writable |
31 |
| - for p in custom_nodes_path.glob("**/*"): |
32 |
| - p.chmod(custom_nodes_path.stat().st_mode) |
| 33 | + loaded_packs: list[str] = [] |
| 34 | + failed_packs: list[str] = [] |
33 | 35 |
|
34 | 36 | # Import custom nodes, see https://docs.python.org/3/library/importlib.html#importing-programmatically
|
35 |
| - spec = spec_from_file_location("custom_nodes", custom_nodes_init_path) |
36 |
| - if spec is None or spec.loader is None: |
37 |
| - raise RuntimeError(f"Could not load custom nodes from {custom_nodes_init_path}") |
38 |
| - module = module_from_spec(spec) |
39 |
| - sys.modules[spec.name] = module |
40 |
| - spec.loader.exec_module(module) |
| 37 | + for d in custom_nodes_path.iterdir(): |
| 38 | + # skip files |
| 39 | + if not d.is_dir(): |
| 40 | + continue |
| 41 | + |
| 42 | + # skip hidden directories |
| 43 | + if d.name.startswith("_") or d.name.startswith("."): |
| 44 | + continue |
| 45 | + |
| 46 | + # skip directories without an `__init__.py` |
| 47 | + init = d / "__init__.py" |
| 48 | + if not init.exists(): |
| 49 | + continue |
| 50 | + |
| 51 | + module_name = init.parent.stem |
| 52 | + |
| 53 | + # skip if already imported |
| 54 | + if module_name in globals(): |
| 55 | + continue |
| 56 | + |
| 57 | + # load the module |
| 58 | + spec = spec_from_file_location(module_name, init.absolute()) |
| 59 | + |
| 60 | + if spec is None or spec.loader is None: |
| 61 | + logger.warning(f"Could not load {init}") |
| 62 | + continue |
| 63 | + |
| 64 | + logger.info(f"Loading node pack {module_name}") |
| 65 | + |
| 66 | + try: |
| 67 | + module = module_from_spec(spec) |
| 68 | + sys.modules[spec.name] = module |
| 69 | + spec.loader.exec_module(module) |
| 70 | + |
| 71 | + loaded_packs.append(module_name) |
| 72 | + except Exception: |
| 73 | + failed_packs.append(module_name) |
| 74 | + full_error = traceback.format_exc() |
| 75 | + logger.error(f"Failed to load node pack {module_name} (may have partially loaded):\n{full_error}") |
| 76 | + |
| 77 | + del init, module_name |
| 78 | + |
| 79 | + loaded_count = len(loaded_packs) |
| 80 | + if loaded_count > 0: |
| 81 | + logger.info( |
| 82 | + f"Loaded {loaded_count} node pack{'s' if loaded_count != 1 else ''} from {custom_nodes_path}: {', '.join(loaded_packs)}" |
| 83 | + ) |
0 commit comments