comfy-env 0.0.64__py3-none-any.whl → 0.0.66__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 +70 -122
- comfy_env/cli.py +78 -7
- comfy_env/config/__init__.py +19 -0
- comfy_env/config/parser.py +151 -0
- comfy_env/config/types.py +64 -0
- comfy_env/install.py +83 -361
- comfy_env/isolation/__init__.py +9 -0
- comfy_env/isolation/wrap.py +351 -0
- comfy_env/nodes.py +2 -2
- comfy_env/pixi/__init__.py +48 -0
- comfy_env/pixi/core.py +356 -0
- comfy_env/{resolver.py → pixi/resolver.py} +1 -14
- comfy_env/prestartup.py +60 -0
- comfy_env/templates/comfy-env-instructions.txt +30 -87
- comfy_env/templates/comfy-env.toml +68 -136
- comfy_env/workers/__init__.py +21 -32
- comfy_env/workers/base.py +1 -1
- comfy_env/workers/{torch_mp.py → mp.py} +47 -14
- comfy_env/workers/{venv.py → subprocess.py} +405 -441
- {comfy_env-0.0.64.dist-info → comfy_env-0.0.66.dist-info}/METADATA +2 -1
- comfy_env-0.0.66.dist-info/RECORD +34 -0
- comfy_env/decorator.py +0 -700
- comfy_env/env/__init__.py +0 -47
- comfy_env/env/config.py +0 -201
- comfy_env/env/config_file.py +0 -740
- comfy_env/env/manager.py +0 -636
- comfy_env/env/security.py +0 -267
- comfy_env/ipc/__init__.py +0 -55
- comfy_env/ipc/bridge.py +0 -476
- comfy_env/ipc/protocol.py +0 -265
- comfy_env/ipc/tensor.py +0 -371
- comfy_env/ipc/torch_bridge.py +0 -401
- comfy_env/ipc/transport.py +0 -318
- comfy_env/ipc/worker.py +0 -221
- comfy_env/isolation.py +0 -310
- comfy_env/pixi.py +0 -760
- comfy_env/stub_imports.py +0 -270
- comfy_env/stubs/__init__.py +0 -1
- comfy_env/stubs/comfy/__init__.py +0 -6
- comfy_env/stubs/comfy/model_management.py +0 -58
- comfy_env/stubs/comfy/utils.py +0 -29
- comfy_env/stubs/folder_paths.py +0 -71
- comfy_env/workers/pool.py +0 -241
- comfy_env-0.0.64.dist-info/RECORD +0 -48
- /comfy_env/{env/cuda_gpu_detection.py → pixi/cuda_detection.py} +0 -0
- /comfy_env/{env → pixi}/platform/__init__.py +0 -0
- /comfy_env/{env → pixi}/platform/base.py +0 -0
- /comfy_env/{env → pixi}/platform/darwin.py +0 -0
- /comfy_env/{env → pixi}/platform/linux.py +0 -0
- /comfy_env/{env → pixi}/platform/windows.py +0 -0
- /comfy_env/{registry.py → pixi/registry.py} +0 -0
- /comfy_env/{wheel_sources.yml → pixi/wheel_sources.yml} +0 -0
- {comfy_env-0.0.64.dist-info → comfy_env-0.0.66.dist-info}/WHEEL +0 -0
- {comfy_env-0.0.64.dist-info → comfy_env-0.0.66.dist-info}/entry_points.txt +0 -0
- {comfy_env-0.0.64.dist-info → comfy_env-0.0.66.dist-info}/licenses/LICENSE +0 -0
comfy_env/isolation.py
DELETED
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Process isolation for ComfyUI node packs.
|
|
3
|
-
|
|
4
|
-
This module provides enable_isolation() which wraps all 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 .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
|
10
|
-
from comfy_env import enable_isolation
|
|
11
|
-
|
|
12
|
-
enable_isolation(NODE_CLASS_MAPPINGS) # That's it!
|
|
13
|
-
|
|
14
|
-
This requires `isolated = true` in comfy-env.toml:
|
|
15
|
-
|
|
16
|
-
[myenv]
|
|
17
|
-
python = "3.11"
|
|
18
|
-
isolated = true
|
|
19
|
-
|
|
20
|
-
[myenv.packages]
|
|
21
|
-
requirements = ["my-package"]
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
import atexit
|
|
25
|
-
import inspect
|
|
26
|
-
import os
|
|
27
|
-
import sys
|
|
28
|
-
import threading
|
|
29
|
-
from functools import wraps
|
|
30
|
-
from pathlib import Path
|
|
31
|
-
from typing import Any, Dict, Optional
|
|
32
|
-
|
|
33
|
-
# Debug logging (set COMFY_ENV_DEBUG=1 to enable)
|
|
34
|
-
_DEBUG = os.environ.get("COMFY_ENV_DEBUG", "").lower() in ("1", "true", "yes")
|
|
35
|
-
|
|
36
|
-
# Global worker cache (one per isolated environment)
|
|
37
|
-
_workers: Dict[str, Any] = {}
|
|
38
|
-
_workers_lock = threading.Lock()
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _get_worker(
|
|
42
|
-
env_name: str,
|
|
43
|
-
python_path: Path,
|
|
44
|
-
working_dir: Path,
|
|
45
|
-
sys_path: list[str],
|
|
46
|
-
):
|
|
47
|
-
"""Get or create a persistent worker for the isolated environment."""
|
|
48
|
-
from .workers.venv import PersistentVenvWorker
|
|
49
|
-
|
|
50
|
-
cache_key = str(python_path)
|
|
51
|
-
|
|
52
|
-
with _workers_lock:
|
|
53
|
-
if cache_key in _workers:
|
|
54
|
-
worker = _workers[cache_key]
|
|
55
|
-
if worker.is_alive():
|
|
56
|
-
return worker
|
|
57
|
-
# Worker died, will recreate
|
|
58
|
-
|
|
59
|
-
print(f"[comfy-env] Starting isolated worker: {env_name}")
|
|
60
|
-
print(f"[comfy-env] Python: {python_path}")
|
|
61
|
-
|
|
62
|
-
worker = PersistentVenvWorker(
|
|
63
|
-
python=str(python_path),
|
|
64
|
-
working_dir=working_dir,
|
|
65
|
-
sys_path=sys_path,
|
|
66
|
-
name=env_name,
|
|
67
|
-
)
|
|
68
|
-
_workers[cache_key] = worker
|
|
69
|
-
return worker
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def _shutdown_workers():
|
|
73
|
-
"""Shutdown all cached workers. Called at exit."""
|
|
74
|
-
with _workers_lock:
|
|
75
|
-
for name, worker in _workers.items():
|
|
76
|
-
try:
|
|
77
|
-
worker.shutdown()
|
|
78
|
-
except Exception:
|
|
79
|
-
pass
|
|
80
|
-
_workers.clear()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
atexit.register(_shutdown_workers)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _find_python_path(node_dir: Path, env_name: str) -> Optional[Path]:
|
|
87
|
-
"""
|
|
88
|
-
Find the Python executable for the isolated environment.
|
|
89
|
-
|
|
90
|
-
Priority:
|
|
91
|
-
1. .pixi/envs/default/bin/python (pixi/conda environment)
|
|
92
|
-
2. _env_{name}/bin/python (uv venv)
|
|
93
|
-
3. _env_{name}/Scripts/python.exe (Windows uv venv)
|
|
94
|
-
"""
|
|
95
|
-
# Check pixi environment first
|
|
96
|
-
if sys.platform == "win32":
|
|
97
|
-
pixi_python = node_dir / ".pixi" / "envs" / "default" / "python.exe"
|
|
98
|
-
else:
|
|
99
|
-
pixi_python = node_dir / ".pixi" / "envs" / "default" / "bin" / "python"
|
|
100
|
-
|
|
101
|
-
if pixi_python.exists():
|
|
102
|
-
return pixi_python
|
|
103
|
-
|
|
104
|
-
# Check _env_* directory (uv venv)
|
|
105
|
-
env_dir = node_dir / f"_env_{env_name}"
|
|
106
|
-
if sys.platform == "win32":
|
|
107
|
-
env_python = env_dir / "Scripts" / "python.exe"
|
|
108
|
-
else:
|
|
109
|
-
env_python = env_dir / "bin" / "python"
|
|
110
|
-
|
|
111
|
-
if env_python.exists():
|
|
112
|
-
return env_python
|
|
113
|
-
|
|
114
|
-
return None
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def _wrap_node_class(
|
|
118
|
-
cls: type,
|
|
119
|
-
env_name: str,
|
|
120
|
-
python_path: Path,
|
|
121
|
-
working_dir: Path,
|
|
122
|
-
sys_path: list[str],
|
|
123
|
-
) -> type:
|
|
124
|
-
"""
|
|
125
|
-
Wrap a node class so its FUNCTION method runs in the isolated environment.
|
|
126
|
-
|
|
127
|
-
Args:
|
|
128
|
-
cls: The node class to wrap
|
|
129
|
-
env_name: Name of the isolated environment
|
|
130
|
-
python_path: Path to the isolated Python executable
|
|
131
|
-
working_dir: Working directory for the worker
|
|
132
|
-
sys_path: Additional paths to add to sys.path in the worker
|
|
133
|
-
|
|
134
|
-
Returns:
|
|
135
|
-
The wrapped class (modified in place)
|
|
136
|
-
"""
|
|
137
|
-
func_name = getattr(cls, "FUNCTION", None)
|
|
138
|
-
if not func_name:
|
|
139
|
-
return cls # Not a valid ComfyUI node class
|
|
140
|
-
|
|
141
|
-
original_method = getattr(cls, func_name, None)
|
|
142
|
-
if original_method is None:
|
|
143
|
-
return cls
|
|
144
|
-
|
|
145
|
-
# Get source file for the class
|
|
146
|
-
try:
|
|
147
|
-
source_file = Path(inspect.getfile(cls)).resolve()
|
|
148
|
-
except (TypeError, OSError):
|
|
149
|
-
# Can't get source file, skip wrapping
|
|
150
|
-
return cls
|
|
151
|
-
|
|
152
|
-
# Compute relative module path from working_dir
|
|
153
|
-
# e.g., /path/to/nodes/io/load_mesh.py -> nodes.io.load_mesh
|
|
154
|
-
try:
|
|
155
|
-
relative_path = source_file.relative_to(working_dir)
|
|
156
|
-
# Convert path to module: nodes/io/load_mesh.py -> nodes.io.load_mesh
|
|
157
|
-
module_name = str(relative_path.with_suffix("")).replace("/", ".").replace("\\", ".")
|
|
158
|
-
except ValueError:
|
|
159
|
-
# File not under working_dir, use stem as fallback
|
|
160
|
-
module_name = source_file.stem
|
|
161
|
-
|
|
162
|
-
@wraps(original_method)
|
|
163
|
-
def proxy(self, **kwargs):
|
|
164
|
-
if _DEBUG:
|
|
165
|
-
print(f"[comfy-env] PROXY CALLED: {cls.__name__}.{func_name}", flush=True)
|
|
166
|
-
print(f"[comfy-env] kwargs keys: {list(kwargs.keys())}", flush=True)
|
|
167
|
-
|
|
168
|
-
worker = _get_worker(env_name, python_path, working_dir, sys_path)
|
|
169
|
-
if _DEBUG:
|
|
170
|
-
print(f"[comfy-env] worker alive: {worker.is_alive()}", flush=True)
|
|
171
|
-
|
|
172
|
-
# Clone tensors for IPC if needed
|
|
173
|
-
try:
|
|
174
|
-
from .decorator import _clone_tensor_if_needed
|
|
175
|
-
|
|
176
|
-
kwargs = {k: _clone_tensor_if_needed(v) for k, v in kwargs.items()}
|
|
177
|
-
except ImportError:
|
|
178
|
-
pass # No torch available, skip cloning
|
|
179
|
-
|
|
180
|
-
if _DEBUG:
|
|
181
|
-
print(f"[comfy-env] calling worker.call_method...", flush=True)
|
|
182
|
-
result = worker.call_method(
|
|
183
|
-
module_name=module_name,
|
|
184
|
-
class_name=cls.__name__,
|
|
185
|
-
method_name=func_name,
|
|
186
|
-
self_state=self.__dict__.copy() if hasattr(self, "__dict__") else None,
|
|
187
|
-
kwargs=kwargs,
|
|
188
|
-
timeout=600.0,
|
|
189
|
-
)
|
|
190
|
-
if _DEBUG:
|
|
191
|
-
print(f"[comfy-env] call_method returned", flush=True)
|
|
192
|
-
|
|
193
|
-
# Clone result tensors
|
|
194
|
-
try:
|
|
195
|
-
from .decorator import _clone_tensor_if_needed
|
|
196
|
-
|
|
197
|
-
result = _clone_tensor_if_needed(result)
|
|
198
|
-
except ImportError:
|
|
199
|
-
pass
|
|
200
|
-
|
|
201
|
-
return result
|
|
202
|
-
|
|
203
|
-
# Replace the method
|
|
204
|
-
setattr(cls, func_name, proxy)
|
|
205
|
-
|
|
206
|
-
# Mark as isolated for debugging
|
|
207
|
-
cls._comfy_env_isolated = True
|
|
208
|
-
cls._comfy_env_name = env_name
|
|
209
|
-
|
|
210
|
-
return cls
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def enable_isolation(node_class_mappings: Dict[str, type]) -> None:
|
|
214
|
-
"""
|
|
215
|
-
Enable process isolation for all nodes in a node pack.
|
|
216
|
-
|
|
217
|
-
Call this AFTER importing NODE_CLASS_MAPPINGS. It wraps all node classes
|
|
218
|
-
so their FUNCTION methods run in the isolated Python environment specified
|
|
219
|
-
in comfy-env.toml.
|
|
220
|
-
|
|
221
|
-
Requires `isolated = true` in comfy-env.toml:
|
|
222
|
-
|
|
223
|
-
[myenv]
|
|
224
|
-
python = "3.11"
|
|
225
|
-
isolated = true
|
|
226
|
-
|
|
227
|
-
Args:
|
|
228
|
-
node_class_mappings: The NODE_CLASS_MAPPINGS dict from the node pack.
|
|
229
|
-
|
|
230
|
-
Example:
|
|
231
|
-
from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
|
232
|
-
from comfy_env import enable_isolation
|
|
233
|
-
|
|
234
|
-
enable_isolation(NODE_CLASS_MAPPINGS)
|
|
235
|
-
|
|
236
|
-
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
|
237
|
-
"""
|
|
238
|
-
# Skip if running inside worker subprocess
|
|
239
|
-
if os.environ.get("COMFYUI_ISOLATION_WORKER") == "1":
|
|
240
|
-
return
|
|
241
|
-
|
|
242
|
-
# Find the calling module's directory (node pack root)
|
|
243
|
-
frame = inspect.currentframe()
|
|
244
|
-
if frame is None:
|
|
245
|
-
print("[comfy-env] Warning: Could not get current frame")
|
|
246
|
-
return
|
|
247
|
-
|
|
248
|
-
caller_frame = frame.f_back
|
|
249
|
-
if caller_frame is None:
|
|
250
|
-
print("[comfy-env] Warning: Could not get caller frame")
|
|
251
|
-
return
|
|
252
|
-
|
|
253
|
-
caller_file = caller_frame.f_globals.get("__file__")
|
|
254
|
-
if not caller_file:
|
|
255
|
-
print("[comfy-env] Warning: Could not determine caller location")
|
|
256
|
-
return
|
|
257
|
-
|
|
258
|
-
node_dir = Path(caller_file).resolve().parent
|
|
259
|
-
|
|
260
|
-
# Load config
|
|
261
|
-
from .env.config_file import discover_config
|
|
262
|
-
|
|
263
|
-
config = discover_config(node_dir)
|
|
264
|
-
if not config:
|
|
265
|
-
print(f"[comfy-env] No comfy-env.toml found in {node_dir}")
|
|
266
|
-
return
|
|
267
|
-
|
|
268
|
-
# Find isolated environment
|
|
269
|
-
isolated_env = None
|
|
270
|
-
env_name = None
|
|
271
|
-
|
|
272
|
-
for name, env in config.envs.items():
|
|
273
|
-
if getattr(env, "isolated", False):
|
|
274
|
-
isolated_env = env
|
|
275
|
-
env_name = name
|
|
276
|
-
break
|
|
277
|
-
|
|
278
|
-
if not isolated_env or not env_name:
|
|
279
|
-
# No isolated env configured, silently return
|
|
280
|
-
return
|
|
281
|
-
|
|
282
|
-
# Find Python executable
|
|
283
|
-
python_path = _find_python_path(node_dir, env_name)
|
|
284
|
-
|
|
285
|
-
if not python_path:
|
|
286
|
-
print(f"[comfy-env] Warning: Isolated environment not found for '{env_name}'")
|
|
287
|
-
print(f"[comfy-env] Expected: .pixi/envs/default/bin/python or _env_{env_name}/bin/python")
|
|
288
|
-
print(f"[comfy-env] Run 'comfy-env install' to create the environment")
|
|
289
|
-
return
|
|
290
|
-
|
|
291
|
-
# Build sys.path for the worker
|
|
292
|
-
sys_path = [str(node_dir)]
|
|
293
|
-
|
|
294
|
-
# Add nodes directory if it exists
|
|
295
|
-
nodes_dir = node_dir / "nodes"
|
|
296
|
-
if nodes_dir.exists():
|
|
297
|
-
sys_path.append(str(nodes_dir))
|
|
298
|
-
|
|
299
|
-
print(f"[comfy-env] Enabling isolation for {len(node_class_mappings)} nodes")
|
|
300
|
-
print(f"[comfy-env] Environment: {env_name}")
|
|
301
|
-
print(f"[comfy-env] Python: {python_path}")
|
|
302
|
-
|
|
303
|
-
# Wrap all node classes
|
|
304
|
-
wrapped_count = 0
|
|
305
|
-
for node_name, node_cls in node_class_mappings.items():
|
|
306
|
-
if hasattr(node_cls, "FUNCTION"):
|
|
307
|
-
_wrap_node_class(node_cls, env_name, python_path, node_dir, sys_path)
|
|
308
|
-
wrapped_count += 1
|
|
309
|
-
|
|
310
|
-
print(f"[comfy-env] Wrapped {wrapped_count} node classes for isolation")
|