comfy-env 0.1.14__py3-none-any.whl → 0.1.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- comfy_env/__init__.py +115 -62
- comfy_env/cli.py +89 -319
- comfy_env/config/__init__.py +18 -8
- comfy_env/config/parser.py +21 -122
- comfy_env/config/types.py +37 -70
- comfy_env/detection/__init__.py +77 -0
- comfy_env/detection/cuda.py +61 -0
- comfy_env/detection/gpu.py +230 -0
- comfy_env/detection/platform.py +70 -0
- comfy_env/detection/runtime.py +103 -0
- comfy_env/environment/__init__.py +53 -0
- comfy_env/environment/cache.py +141 -0
- comfy_env/environment/libomp.py +41 -0
- comfy_env/environment/paths.py +38 -0
- comfy_env/environment/setup.py +88 -0
- comfy_env/install.py +163 -249
- comfy_env/isolation/__init__.py +33 -2
- comfy_env/isolation/tensor_utils.py +83 -0
- comfy_env/isolation/workers/__init__.py +16 -0
- comfy_env/{workers → isolation/workers}/mp.py +1 -1
- comfy_env/{workers → isolation/workers}/subprocess.py +2 -2
- comfy_env/isolation/wrap.py +149 -409
- comfy_env/packages/__init__.py +60 -0
- comfy_env/packages/apt.py +36 -0
- comfy_env/packages/cuda_wheels.py +97 -0
- comfy_env/packages/node_dependencies.py +77 -0
- comfy_env/packages/pixi.py +85 -0
- comfy_env/packages/toml_generator.py +88 -0
- comfy_env-0.1.16.dist-info/METADATA +279 -0
- comfy_env-0.1.16.dist-info/RECORD +36 -0
- comfy_env/cache.py +0 -331
- comfy_env/errors.py +0 -293
- comfy_env/nodes.py +0 -187
- comfy_env/pixi/__init__.py +0 -48
- comfy_env/pixi/core.py +0 -588
- comfy_env/pixi/cuda_detection.py +0 -303
- comfy_env/pixi/platform/__init__.py +0 -21
- comfy_env/pixi/platform/base.py +0 -96
- comfy_env/pixi/platform/darwin.py +0 -53
- comfy_env/pixi/platform/linux.py +0 -68
- comfy_env/pixi/platform/windows.py +0 -284
- comfy_env/pixi/resolver.py +0 -198
- comfy_env/prestartup.py +0 -192
- comfy_env/workers/__init__.py +0 -38
- comfy_env/workers/tensor_utils.py +0 -188
- comfy_env-0.1.14.dist-info/METADATA +0 -291
- comfy_env-0.1.14.dist-info/RECORD +0 -33
- /comfy_env/{workers → isolation/workers}/base.py +0 -0
- {comfy_env-0.1.14.dist-info → comfy_env-0.1.16.dist-info}/WHEEL +0 -0
- {comfy_env-0.1.14.dist-info → comfy_env-0.1.16.dist-info}/entry_points.txt +0 -0
- {comfy_env-0.1.14.dist-info → comfy_env-0.1.16.dist-info}/licenses/LICENSE +0 -0
comfy_env/isolation/wrap.py
CHANGED
|
@@ -1,28 +1,7 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Process isolation for ComfyUI node packs.
|
|
3
|
-
|
|
4
|
-
This module provides wrap_isolated_nodes() which wraps node classes
|
|
5
|
-
to run their FUNCTION methods in an isolated Python environment.
|
|
6
|
-
|
|
7
|
-
Usage:
|
|
8
|
-
# In your node pack's __init__.py:
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from comfy_env import wrap_isolated_nodes
|
|
11
|
-
|
|
12
|
-
NODE_CLASS_MAPPINGS = {}
|
|
13
|
-
|
|
14
|
-
# Main nodes (no isolation)
|
|
15
|
-
from .nodes.main import NODE_CLASS_MAPPINGS as main_nodes
|
|
16
|
-
NODE_CLASS_MAPPINGS.update(main_nodes)
|
|
17
|
-
|
|
18
|
-
# Isolated nodes (has comfy-env.toml in that directory)
|
|
19
|
-
from .nodes.isolated import NODE_CLASS_MAPPINGS as isolated_nodes
|
|
20
|
-
NODE_CLASS_MAPPINGS.update(
|
|
21
|
-
wrap_isolated_nodes(isolated_nodes, Path(__file__).parent / "nodes/isolated")
|
|
22
|
-
)
|
|
23
|
-
"""
|
|
1
|
+
"""Process isolation for ComfyUI nodes - wraps FUNCTION methods to run in isolated env."""
|
|
24
2
|
|
|
25
3
|
import atexit
|
|
4
|
+
import glob
|
|
26
5
|
import inspect
|
|
27
6
|
import os
|
|
28
7
|
import sys
|
|
@@ -31,405 +10,194 @@ from functools import wraps
|
|
|
31
10
|
from pathlib import Path
|
|
32
11
|
from typing import Any, Dict, Optional
|
|
33
12
|
|
|
34
|
-
# Debug logging (set COMFY_ENV_DEBUG=1 to enable)
|
|
35
13
|
_DEBUG = os.environ.get("COMFY_ENV_DEBUG", "").lower() in ("1", "true", "yes")
|
|
14
|
+
_workers: Dict[str, Any] = {}
|
|
15
|
+
_workers_lock = threading.Lock()
|
|
36
16
|
|
|
37
17
|
|
|
38
|
-
def
|
|
39
|
-
"""
|
|
40
|
-
name = dir_name.lower().replace("-", "_").lstrip("comfyui_")
|
|
41
|
-
return f"_env_{name}"
|
|
18
|
+
def _is_enabled() -> bool:
|
|
19
|
+
return os.environ.get("USE_COMFY_ENV", "1").lower() not in ("0", "false", "no", "off")
|
|
42
20
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
21
|
+
|
|
22
|
+
def _env_name(dir_name: str) -> str:
|
|
23
|
+
return f"_env_{dir_name.lower().replace('-', '_').lstrip('comfyui_')}"
|
|
46
24
|
|
|
47
25
|
|
|
48
|
-
def
|
|
49
|
-
"""Get
|
|
26
|
+
def _get_env_paths(env_dir: Path) -> tuple[Optional[Path], Optional[Path]]:
|
|
27
|
+
"""Get (site_packages, lib_dir) from env."""
|
|
50
28
|
if sys.platform == "win32":
|
|
51
|
-
|
|
29
|
+
sp = env_dir / "Lib" / "site-packages"
|
|
30
|
+
lib = env_dir / "Library" / "bin"
|
|
52
31
|
else:
|
|
53
|
-
|
|
32
|
+
matches = glob.glob(str(env_dir / "lib/python*/site-packages"))
|
|
33
|
+
sp = Path(matches[0]) if matches else None
|
|
34
|
+
lib = env_dir / "lib"
|
|
35
|
+
return (sp, lib) if sp and sp.exists() else (None, None)
|
|
54
36
|
|
|
55
|
-
if not python_path.exists():
|
|
56
|
-
return None
|
|
57
37
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
38
|
+
def _find_env_dir(node_dir: Path) -> Optional[Path]:
|
|
39
|
+
"""Find env dir: marker -> _env_<name> -> .pixi -> .venv"""
|
|
40
|
+
marker = node_dir / ".comfy-env-marker.toml"
|
|
41
|
+
if marker.exists():
|
|
42
|
+
try:
|
|
43
|
+
import tomli
|
|
44
|
+
with open(marker, "rb") as f:
|
|
45
|
+
env_path = tomli.load(f).get("env", {}).get("path")
|
|
46
|
+
if env_path and Path(env_path).exists():
|
|
47
|
+
return Path(env_path)
|
|
48
|
+
except Exception: pass
|
|
49
|
+
|
|
50
|
+
for candidate in [node_dir / _env_name(node_dir.name),
|
|
51
|
+
node_dir / ".pixi/envs/default",
|
|
52
|
+
node_dir / ".venv"]:
|
|
53
|
+
if candidate.exists(): return candidate
|
|
68
54
|
return None
|
|
69
55
|
|
|
70
56
|
|
|
71
|
-
def
|
|
72
|
-
env_dir
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
|
|
57
|
+
def _get_python_version(env_dir: Path) -> Optional[str]:
|
|
58
|
+
python = env_dir / ("python.exe" if sys.platform == "win32" else "bin/python")
|
|
59
|
+
if not python.exists(): return None
|
|
60
|
+
try:
|
|
61
|
+
import subprocess
|
|
62
|
+
r = subprocess.run([str(python), "-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"],
|
|
63
|
+
capture_output=True, text=True, timeout=5)
|
|
64
|
+
return r.stdout.strip() if r.returncode == 0 else None
|
|
65
|
+
except Exception: return None
|
|
66
|
+
|
|
80
67
|
|
|
68
|
+
def _get_worker(env_dir: Path, working_dir: Path, sys_path: list[str],
|
|
69
|
+
lib_path: Optional[str] = None, env_vars: Optional[dict] = None):
|
|
70
|
+
cache_key = str(env_dir)
|
|
81
71
|
with _workers_lock:
|
|
82
|
-
if cache_key in _workers:
|
|
83
|
-
|
|
84
|
-
if worker.is_alive():
|
|
85
|
-
return worker
|
|
86
|
-
# Worker died, will recreate
|
|
87
|
-
|
|
88
|
-
# Check if Python versions match
|
|
89
|
-
host_version = f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
90
|
-
isolated_version = _get_isolated_python_version(env_dir)
|
|
91
|
-
|
|
92
|
-
if isolated_version and isolated_version != host_version:
|
|
93
|
-
# Different Python version - must use SubprocessWorker
|
|
94
|
-
from ..workers.subprocess import SubprocessWorker
|
|
95
|
-
|
|
96
|
-
if sys.platform == "win32":
|
|
97
|
-
python_path = env_dir / "python.exe"
|
|
98
|
-
else:
|
|
99
|
-
python_path = env_dir / "bin" / "python"
|
|
100
|
-
|
|
101
|
-
print(f"[comfy-env] Starting isolated worker (SubprocessWorker)")
|
|
102
|
-
print(f"[comfy-env] Python: {python_path} ({isolated_version} vs host {host_version})")
|
|
103
|
-
|
|
104
|
-
worker = SubprocessWorker(
|
|
105
|
-
python=str(python_path),
|
|
106
|
-
working_dir=working_dir,
|
|
107
|
-
sys_path=sys_path,
|
|
108
|
-
name=working_dir.name,
|
|
109
|
-
)
|
|
110
|
-
else:
|
|
111
|
-
# Same Python version - use MPWorker (faster)
|
|
112
|
-
from ..workers.mp import MPWorker
|
|
72
|
+
if cache_key in _workers and _workers[cache_key].is_alive():
|
|
73
|
+
return _workers[cache_key]
|
|
113
74
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if env_vars:
|
|
117
|
-
print(f"[comfy-env] env_vars: {', '.join(f'{k}={v}' for k, v in env_vars.items())}")
|
|
75
|
+
host_ver = f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
76
|
+
iso_ver = _get_python_version(env_dir)
|
|
118
77
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
78
|
+
if iso_ver and iso_ver != host_ver:
|
|
79
|
+
from .workers.subprocess import SubprocessWorker
|
|
80
|
+
python = env_dir / ("python.exe" if sys.platform == "win32" else "bin/python")
|
|
81
|
+
print(f"[comfy-env] SubprocessWorker: {python} ({iso_ver} vs {host_ver})")
|
|
82
|
+
worker = SubprocessWorker(python=str(python), working_dir=working_dir, sys_path=sys_path, name=working_dir.name)
|
|
83
|
+
else:
|
|
84
|
+
from .workers.mp import MPWorker
|
|
85
|
+
print(f"[comfy-env] MPWorker: {env_dir}")
|
|
86
|
+
worker = MPWorker(name=working_dir.name, sys_path=sys_path, lib_path=lib_path, env_vars=env_vars)
|
|
125
87
|
|
|
126
88
|
_workers[cache_key] = worker
|
|
127
89
|
return worker
|
|
128
90
|
|
|
129
91
|
|
|
92
|
+
@atexit.register
|
|
130
93
|
def _shutdown_workers():
|
|
131
|
-
"""Shutdown all cached workers. Called at exit."""
|
|
132
94
|
with _workers_lock:
|
|
133
|
-
for
|
|
134
|
-
try:
|
|
135
|
-
|
|
136
|
-
except Exception:
|
|
137
|
-
pass
|
|
95
|
+
for w in _workers.values():
|
|
96
|
+
try: w.shutdown()
|
|
97
|
+
except Exception: pass
|
|
138
98
|
_workers.clear()
|
|
139
99
|
|
|
140
100
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def _find_env_paths(node_dir: Path) -> tuple[Optional[Path], Optional[Path]]:
|
|
145
|
-
"""
|
|
146
|
-
Find site-packages and lib directories for the isolated environment.
|
|
147
|
-
|
|
148
|
-
Fallback order:
|
|
149
|
-
1. Marker file -> central cache
|
|
150
|
-
2. _env_<name> (local)
|
|
151
|
-
3. .pixi/envs/default (old pixi)
|
|
152
|
-
4. .venv
|
|
153
|
-
|
|
154
|
-
Returns:
|
|
155
|
-
(site_packages, lib_dir) - lib_dir is for LD_LIBRARY_PATH
|
|
156
|
-
"""
|
|
157
|
-
import glob
|
|
158
|
-
|
|
159
|
-
def _get_paths_from_env(env_dir: Path) -> tuple[Optional[Path], Optional[Path]]:
|
|
160
|
-
"""Extract site-packages and lib_dir from an env directory."""
|
|
161
|
-
if sys.platform == "win32":
|
|
162
|
-
site_packages = env_dir / "Lib" / "site-packages"
|
|
163
|
-
lib_dir = env_dir / "Library" / "bin"
|
|
164
|
-
else:
|
|
165
|
-
pattern = str(env_dir / "lib" / "python*" / "site-packages")
|
|
166
|
-
matches = glob.glob(pattern)
|
|
167
|
-
site_packages = Path(matches[0]) if matches else None
|
|
168
|
-
lib_dir = env_dir / "lib"
|
|
169
|
-
if site_packages and site_packages.exists():
|
|
170
|
-
return site_packages, lib_dir if lib_dir and lib_dir.exists() else None
|
|
171
|
-
return None, None
|
|
172
|
-
|
|
173
|
-
# 1. Check marker file -> central cache
|
|
174
|
-
marker_path = node_dir / ".comfy-env-marker.toml"
|
|
175
|
-
if marker_path.exists():
|
|
176
|
-
try:
|
|
177
|
-
if sys.version_info >= (3, 11):
|
|
178
|
-
import tomllib
|
|
179
|
-
else:
|
|
180
|
-
import tomli as tomllib
|
|
181
|
-
with open(marker_path, "rb") as f:
|
|
182
|
-
marker = tomllib.load(f)
|
|
183
|
-
env_path = marker.get("env", {}).get("path")
|
|
184
|
-
if env_path:
|
|
185
|
-
env_dir = Path(env_path)
|
|
186
|
-
if env_dir.exists():
|
|
187
|
-
result = _get_paths_from_env(env_dir)
|
|
188
|
-
if result[0]:
|
|
189
|
-
return result
|
|
190
|
-
except Exception:
|
|
191
|
-
pass # Fall through to other options
|
|
192
|
-
|
|
193
|
-
# 2. Check _env_<name> directory (local)
|
|
194
|
-
env_name = get_env_name(node_dir.name)
|
|
195
|
-
env_dir = node_dir / env_name
|
|
196
|
-
if env_dir.exists():
|
|
197
|
-
result = _get_paths_from_env(env_dir)
|
|
198
|
-
if result[0]:
|
|
199
|
-
return result
|
|
200
|
-
|
|
201
|
-
# 3. Fallback: Check old .pixi/envs/default (for backward compat)
|
|
202
|
-
pixi_env = node_dir / ".pixi" / "envs" / "default"
|
|
203
|
-
if pixi_env.exists():
|
|
204
|
-
result = _get_paths_from_env(pixi_env)
|
|
205
|
-
if result[0]:
|
|
206
|
-
return result
|
|
207
|
-
|
|
208
|
-
# 4. Check .venv directory
|
|
209
|
-
venv_dir = node_dir / ".venv"
|
|
210
|
-
if venv_dir.exists():
|
|
211
|
-
if sys.platform == "win32":
|
|
212
|
-
site_packages = venv_dir / "Lib" / "site-packages"
|
|
213
|
-
else:
|
|
214
|
-
pattern = str(venv_dir / "lib" / "python*" / "site-packages")
|
|
215
|
-
matches = glob.glob(pattern)
|
|
216
|
-
site_packages = Path(matches[0]) if matches else None
|
|
217
|
-
if site_packages and site_packages.exists():
|
|
218
|
-
return site_packages, None # venvs don't have separate lib
|
|
219
|
-
|
|
220
|
-
return None, None
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def _find_env_dir(node_dir: Path) -> Optional[Path]:
|
|
224
|
-
"""
|
|
225
|
-
Find the environment directory (for cache key).
|
|
226
|
-
|
|
227
|
-
Fallback order:
|
|
228
|
-
1. Marker file -> central cache
|
|
229
|
-
2. _env_<name> (local)
|
|
230
|
-
3. .pixi/envs/default (old pixi)
|
|
231
|
-
4. .venv
|
|
232
|
-
"""
|
|
233
|
-
# 1. Check marker file -> central cache
|
|
234
|
-
marker_path = node_dir / ".comfy-env-marker.toml"
|
|
235
|
-
if marker_path.exists():
|
|
236
|
-
try:
|
|
237
|
-
if sys.version_info >= (3, 11):
|
|
238
|
-
import tomllib
|
|
239
|
-
else:
|
|
240
|
-
import tomli as tomllib
|
|
241
|
-
with open(marker_path, "rb") as f:
|
|
242
|
-
marker = tomllib.load(f)
|
|
243
|
-
env_path = marker.get("env", {}).get("path")
|
|
244
|
-
if env_path:
|
|
245
|
-
env_dir = Path(env_path)
|
|
246
|
-
if env_dir.exists():
|
|
247
|
-
return env_dir
|
|
248
|
-
except Exception:
|
|
249
|
-
pass
|
|
250
|
-
|
|
251
|
-
# 2. Check _env_<name> first
|
|
252
|
-
env_name = get_env_name(node_dir.name)
|
|
253
|
-
env_dir = node_dir / env_name
|
|
254
|
-
if env_dir.exists():
|
|
255
|
-
return env_dir
|
|
256
|
-
|
|
257
|
-
# 3. Fallback to old .pixi path
|
|
258
|
-
pixi_env = node_dir / ".pixi" / "envs" / "default"
|
|
259
|
-
if pixi_env.exists():
|
|
260
|
-
return pixi_env
|
|
261
|
-
|
|
262
|
-
# 4. Check .venv
|
|
263
|
-
venv_dir = node_dir / ".venv"
|
|
264
|
-
if venv_dir.exists():
|
|
265
|
-
return venv_dir
|
|
266
|
-
|
|
267
|
-
return None
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
def _find_custom_node_root(nodes_dir: Path) -> Optional[Path]:
|
|
271
|
-
"""
|
|
272
|
-
Find the custom node root (direct child of custom_nodes/).
|
|
273
|
-
|
|
274
|
-
Uses folder_paths to find custom_nodes directories, then finds
|
|
275
|
-
which one is an ancestor of nodes_dir.
|
|
276
|
-
|
|
277
|
-
Example: /path/custom_nodes/ComfyUI-UniRig/nodes/nodes_gpu
|
|
278
|
-
-> returns /path/custom_nodes/ComfyUI-UniRig
|
|
279
|
-
"""
|
|
280
|
-
try:
|
|
281
|
-
import folder_paths
|
|
282
|
-
custom_nodes_dirs = folder_paths.get_folder_paths("custom_nodes")
|
|
283
|
-
except (ImportError, KeyError):
|
|
284
|
-
return None
|
|
285
|
-
|
|
286
|
-
for cn_dir in custom_nodes_dirs:
|
|
287
|
-
cn_path = Path(cn_dir)
|
|
288
|
-
try:
|
|
289
|
-
rel = nodes_dir.relative_to(cn_path)
|
|
290
|
-
if rel.parts:
|
|
291
|
-
return cn_path / rel.parts[0]
|
|
292
|
-
except ValueError:
|
|
293
|
-
continue
|
|
294
|
-
|
|
295
|
-
return None
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
def _wrap_node_class(
|
|
299
|
-
cls: type,
|
|
300
|
-
env_dir: Path,
|
|
301
|
-
working_dir: Path,
|
|
302
|
-
sys_path: list[str],
|
|
303
|
-
lib_path: Optional[str] = None,
|
|
304
|
-
env_vars: Optional[dict] = None,
|
|
305
|
-
) -> type:
|
|
306
|
-
"""
|
|
307
|
-
Wrap a node class so its FUNCTION method runs in the isolated environment.
|
|
308
|
-
|
|
309
|
-
Args:
|
|
310
|
-
cls: The node class to wrap
|
|
311
|
-
env_dir: Path to the isolated environment directory
|
|
312
|
-
working_dir: Working directory for the worker
|
|
313
|
-
sys_path: Additional paths to add to sys.path in the worker
|
|
314
|
-
lib_path: Path to add to LD_LIBRARY_PATH for conda libraries
|
|
315
|
-
|
|
316
|
-
Returns:
|
|
317
|
-
The wrapped class (modified in place)
|
|
318
|
-
"""
|
|
101
|
+
def _wrap_node_class(cls: type, env_dir: Path, working_dir: Path, sys_path: list[str],
|
|
102
|
+
lib_path: Optional[str] = None, env_vars: Optional[dict] = None) -> type:
|
|
319
103
|
func_name = getattr(cls, "FUNCTION", None)
|
|
320
|
-
if not func_name:
|
|
321
|
-
|
|
104
|
+
if not func_name: return cls
|
|
105
|
+
original = getattr(cls, func_name, None)
|
|
106
|
+
if not original: return cls
|
|
322
107
|
|
|
323
|
-
original_method = getattr(cls, func_name, None)
|
|
324
|
-
if original_method is None:
|
|
325
|
-
return cls
|
|
326
|
-
|
|
327
|
-
# Get source file for the class
|
|
328
108
|
try:
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
109
|
+
source = Path(inspect.getfile(cls)).resolve()
|
|
110
|
+
module_name = str(source.relative_to(working_dir).with_suffix("")).replace("/", ".").replace("\\", ".")
|
|
111
|
+
except (TypeError, OSError, ValueError):
|
|
112
|
+
module_name = source.stem if 'source' in dir() else cls.__module__
|
|
333
113
|
|
|
334
|
-
|
|
335
|
-
# e.g., /path/to/nodes/io/load_mesh.py -> nodes.io.load_mesh
|
|
336
|
-
try:
|
|
337
|
-
relative_path = source_file.relative_to(working_dir)
|
|
338
|
-
# Convert path to module: nodes/io/load_mesh.py -> nodes.io.load_mesh
|
|
339
|
-
module_name = str(relative_path.with_suffix("")).replace("/", ".").replace("\\", ".")
|
|
340
|
-
except ValueError:
|
|
341
|
-
# File not under working_dir, use stem as fallback
|
|
342
|
-
module_name = source_file.stem
|
|
343
|
-
|
|
344
|
-
@wraps(original_method)
|
|
114
|
+
@wraps(original)
|
|
345
115
|
def proxy(self, **kwargs):
|
|
346
|
-
if _DEBUG:
|
|
347
|
-
print(f"[comfy-env] PROXY CALLED: {cls.__name__}.{func_name}", flush=True)
|
|
348
|
-
print(f"[comfy-env] kwargs keys: {list(kwargs.keys())}", flush=True)
|
|
349
|
-
|
|
350
116
|
worker = _get_worker(env_dir, working_dir, sys_path, lib_path, env_vars)
|
|
351
|
-
if _DEBUG:
|
|
352
|
-
print(f"[comfy-env] worker alive: {worker.is_alive()}", flush=True)
|
|
353
|
-
|
|
354
|
-
# Clone tensors for IPC if needed
|
|
355
117
|
try:
|
|
356
|
-
from
|
|
357
|
-
|
|
118
|
+
from .tensor_utils import prepare_for_ipc_recursive
|
|
358
119
|
kwargs = {k: prepare_for_ipc_recursive(v) for k, v in kwargs.items()}
|
|
359
|
-
except ImportError:
|
|
360
|
-
pass # No torch available, skip cloning
|
|
120
|
+
except ImportError: pass
|
|
361
121
|
|
|
362
|
-
if _DEBUG:
|
|
363
|
-
print(f"[comfy-env] calling worker.call_method...", flush=True)
|
|
364
122
|
result = worker.call_method(
|
|
365
|
-
module_name=module_name,
|
|
366
|
-
class_name=cls.__name__,
|
|
367
|
-
method_name=func_name,
|
|
123
|
+
module_name=module_name, class_name=cls.__name__, method_name=func_name,
|
|
368
124
|
self_state=self.__dict__.copy() if hasattr(self, "__dict__") else None,
|
|
369
|
-
kwargs=kwargs,
|
|
370
|
-
timeout=600.0,
|
|
125
|
+
kwargs=kwargs, timeout=600.0,
|
|
371
126
|
)
|
|
372
|
-
if _DEBUG:
|
|
373
|
-
print(f"[comfy-env] call_method returned", flush=True)
|
|
374
127
|
|
|
375
|
-
# Clone result tensors
|
|
376
128
|
try:
|
|
377
|
-
from
|
|
378
|
-
|
|
129
|
+
from .tensor_utils import prepare_for_ipc_recursive
|
|
379
130
|
result = prepare_for_ipc_recursive(result)
|
|
380
|
-
except ImportError:
|
|
381
|
-
pass
|
|
382
|
-
|
|
131
|
+
except ImportError: pass
|
|
383
132
|
return result
|
|
384
133
|
|
|
385
|
-
# Replace the method
|
|
386
134
|
setattr(cls, func_name, proxy)
|
|
387
|
-
|
|
388
|
-
# Mark as isolated for debugging
|
|
389
135
|
cls._comfy_env_isolated = True
|
|
390
|
-
|
|
391
136
|
return cls
|
|
392
137
|
|
|
393
138
|
|
|
394
|
-
def
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
"""
|
|
399
|
-
Wrap nodes from a directory that has a comfy-env.toml.
|
|
139
|
+
def wrap_nodes() -> None:
|
|
140
|
+
"""Auto-wrap nodes for isolation. Call from __init__.py after NODE_CLASS_MAPPINGS."""
|
|
141
|
+
if not _is_enabled() or os.environ.get("COMFYUI_ISOLATION_WORKER") == "1":
|
|
142
|
+
return
|
|
400
143
|
|
|
401
|
-
|
|
402
|
-
|
|
144
|
+
frame = inspect.stack()[1]
|
|
145
|
+
caller_module = inspect.getmodule(frame.frame)
|
|
146
|
+
if not caller_module: return
|
|
403
147
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
nodes_dir: The directory containing comfy-env.toml and the node files.
|
|
148
|
+
mappings = getattr(caller_module, "NODE_CLASS_MAPPINGS", None)
|
|
149
|
+
if not mappings: return
|
|
407
150
|
|
|
408
|
-
|
|
409
|
-
|
|
151
|
+
pkg_dir = Path(frame.filename).resolve().parent
|
|
152
|
+
config_files = list(pkg_dir.rglob("comfy-env.toml"))
|
|
153
|
+
if not config_files: return
|
|
410
154
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
155
|
+
try:
|
|
156
|
+
import folder_paths
|
|
157
|
+
comfyui_base = folder_paths.base_path
|
|
158
|
+
except ImportError:
|
|
159
|
+
comfyui_base = None
|
|
415
160
|
|
|
416
|
-
|
|
161
|
+
envs = []
|
|
162
|
+
for cf in config_files:
|
|
163
|
+
env_dir = _find_env_dir(cf.parent)
|
|
164
|
+
sp, lib = _get_env_paths(env_dir) if env_dir else (None, None)
|
|
165
|
+
if not env_dir or not sp: continue
|
|
417
166
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
167
|
+
env_vars = {}
|
|
168
|
+
try:
|
|
169
|
+
import tomli
|
|
170
|
+
with open(cf, "rb") as f:
|
|
171
|
+
env_vars = {str(k): str(v) for k, v in tomli.load(f).get("env_vars", {}).items()}
|
|
172
|
+
except Exception: pass
|
|
173
|
+
if comfyui_base: env_vars["COMFYUI_BASE"] = str(comfyui_base)
|
|
421
174
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
)
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
175
|
+
envs.append({"dir": cf.parent, "env_dir": env_dir, "sp": sp, "lib": lib, "env_vars": env_vars})
|
|
176
|
+
|
|
177
|
+
wrapped = 0
|
|
178
|
+
for name, cls in mappings.items():
|
|
179
|
+
if not hasattr(cls, "FUNCTION"): continue
|
|
180
|
+
try:
|
|
181
|
+
src = Path(inspect.getfile(cls)).resolve()
|
|
182
|
+
except (TypeError, OSError): continue
|
|
183
|
+
|
|
184
|
+
for e in envs:
|
|
185
|
+
try:
|
|
186
|
+
src.relative_to(e["dir"])
|
|
187
|
+
_wrap_node_class(cls, e["env_dir"], e["dir"], [str(e["sp"]), str(e["dir"])],
|
|
188
|
+
str(e["lib"]) if e["lib"] else None, e["env_vars"])
|
|
189
|
+
wrapped += 1
|
|
190
|
+
break
|
|
191
|
+
except ValueError: continue
|
|
192
|
+
|
|
193
|
+
if wrapped: print(f"[comfy-env] Wrapped {wrapped} nodes")
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def wrap_isolated_nodes(node_class_mappings: Dict[str, type], nodes_dir: Path) -> Dict[str, type]:
|
|
197
|
+
"""Wrap nodes from a directory with comfy-env.toml for isolation."""
|
|
198
|
+
if not _is_enabled() or os.environ.get("COMFYUI_ISOLATION_WORKER") == "1":
|
|
430
199
|
return node_class_mappings
|
|
431
200
|
|
|
432
|
-
# Get ComfyUI base path from folder_paths (canonical source)
|
|
433
201
|
try:
|
|
434
202
|
import folder_paths
|
|
435
203
|
comfyui_base = folder_paths.base_path
|
|
@@ -437,59 +205,31 @@ def wrap_isolated_nodes(
|
|
|
437
205
|
comfyui_base = None
|
|
438
206
|
|
|
439
207
|
nodes_dir = Path(nodes_dir).resolve()
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if not config_file.exists():
|
|
444
|
-
print(f"[comfy-env] Warning: No comfy-env.toml in {nodes_dir}")
|
|
208
|
+
config = nodes_dir / "comfy-env.toml"
|
|
209
|
+
if not config.exists():
|
|
210
|
+
print(f"[comfy-env] No comfy-env.toml in {nodes_dir}")
|
|
445
211
|
return node_class_mappings
|
|
446
212
|
|
|
447
|
-
# Read env_vars from comfy-env.toml
|
|
448
213
|
env_vars = {}
|
|
449
214
|
try:
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
config = tomllib.load(f)
|
|
456
|
-
env_vars_data = config.get("env_vars", {})
|
|
457
|
-
env_vars = {str(k): str(v) for k, v in env_vars_data.items()}
|
|
458
|
-
except Exception:
|
|
459
|
-
pass # Ignore errors reading config
|
|
460
|
-
|
|
461
|
-
# Set COMFYUI_BASE for worker to find ComfyUI modules
|
|
462
|
-
if comfyui_base:
|
|
463
|
-
env_vars["COMFYUI_BASE"] = str(comfyui_base)
|
|
464
|
-
|
|
465
|
-
# Find environment directory and paths
|
|
466
|
-
env_dir = _find_env_dir(nodes_dir)
|
|
467
|
-
site_packages, lib_dir = _find_env_paths(nodes_dir)
|
|
215
|
+
import tomli
|
|
216
|
+
with open(config, "rb") as f:
|
|
217
|
+
env_vars = {str(k): str(v) for k, v in tomli.load(f).get("env_vars", {}).items()}
|
|
218
|
+
except Exception: pass
|
|
219
|
+
if comfyui_base: env_vars["COMFYUI_BASE"] = str(comfyui_base)
|
|
468
220
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
print(f"[comfy-env] Run 'comfy-env install' in {nodes_dir}")
|
|
221
|
+
env_dir = _find_env_dir(nodes_dir)
|
|
222
|
+
sp, lib = _get_env_paths(env_dir) if env_dir else (None, None)
|
|
223
|
+
if not env_dir or not sp:
|
|
224
|
+
print(f"[comfy-env] No env found. Run 'comfy-env install' in {nodes_dir}")
|
|
473
225
|
return node_class_mappings
|
|
474
226
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
# Relative imports would require importing parent package which may have host-only deps
|
|
478
|
-
sys_path = [str(site_packages), str(nodes_dir)]
|
|
479
|
-
|
|
480
|
-
# lib_dir for LD_LIBRARY_PATH (conda libraries)
|
|
481
|
-
lib_path = str(lib_dir) if lib_dir else None
|
|
227
|
+
sys_path = [str(sp), str(nodes_dir)]
|
|
228
|
+
lib_path = str(lib) if lib else None
|
|
482
229
|
|
|
483
230
|
print(f"[comfy-env] Wrapping {len(node_class_mappings)} nodes from {nodes_dir.name}")
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
if env_vars:
|
|
488
|
-
print(f"[comfy-env] env_vars: {', '.join(f'{k}={v}' for k, v in env_vars.items())}")
|
|
489
|
-
|
|
490
|
-
# Wrap all node classes
|
|
491
|
-
for node_name, node_cls in node_class_mappings.items():
|
|
492
|
-
if hasattr(node_cls, "FUNCTION"):
|
|
493
|
-
_wrap_node_class(node_cls, env_dir, nodes_dir, sys_path, lib_path, env_vars)
|
|
231
|
+
for cls in node_class_mappings.values():
|
|
232
|
+
if hasattr(cls, "FUNCTION"):
|
|
233
|
+
_wrap_node_class(cls, env_dir, nodes_dir, sys_path, lib_path, env_vars)
|
|
494
234
|
|
|
495
235
|
return node_class_mappings
|