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.
Files changed (55) hide show
  1. comfy_env/__init__.py +70 -122
  2. comfy_env/cli.py +78 -7
  3. comfy_env/config/__init__.py +19 -0
  4. comfy_env/config/parser.py +151 -0
  5. comfy_env/config/types.py +64 -0
  6. comfy_env/install.py +83 -361
  7. comfy_env/isolation/__init__.py +9 -0
  8. comfy_env/isolation/wrap.py +351 -0
  9. comfy_env/nodes.py +2 -2
  10. comfy_env/pixi/__init__.py +48 -0
  11. comfy_env/pixi/core.py +356 -0
  12. comfy_env/{resolver.py → pixi/resolver.py} +1 -14
  13. comfy_env/prestartup.py +60 -0
  14. comfy_env/templates/comfy-env-instructions.txt +30 -87
  15. comfy_env/templates/comfy-env.toml +68 -136
  16. comfy_env/workers/__init__.py +21 -32
  17. comfy_env/workers/base.py +1 -1
  18. comfy_env/workers/{torch_mp.py → mp.py} +47 -14
  19. comfy_env/workers/{venv.py → subprocess.py} +405 -441
  20. {comfy_env-0.0.64.dist-info → comfy_env-0.0.66.dist-info}/METADATA +2 -1
  21. comfy_env-0.0.66.dist-info/RECORD +34 -0
  22. comfy_env/decorator.py +0 -700
  23. comfy_env/env/__init__.py +0 -47
  24. comfy_env/env/config.py +0 -201
  25. comfy_env/env/config_file.py +0 -740
  26. comfy_env/env/manager.py +0 -636
  27. comfy_env/env/security.py +0 -267
  28. comfy_env/ipc/__init__.py +0 -55
  29. comfy_env/ipc/bridge.py +0 -476
  30. comfy_env/ipc/protocol.py +0 -265
  31. comfy_env/ipc/tensor.py +0 -371
  32. comfy_env/ipc/torch_bridge.py +0 -401
  33. comfy_env/ipc/transport.py +0 -318
  34. comfy_env/ipc/worker.py +0 -221
  35. comfy_env/isolation.py +0 -310
  36. comfy_env/pixi.py +0 -760
  37. comfy_env/stub_imports.py +0 -270
  38. comfy_env/stubs/__init__.py +0 -1
  39. comfy_env/stubs/comfy/__init__.py +0 -6
  40. comfy_env/stubs/comfy/model_management.py +0 -58
  41. comfy_env/stubs/comfy/utils.py +0 -29
  42. comfy_env/stubs/folder_paths.py +0 -71
  43. comfy_env/workers/pool.py +0 -241
  44. comfy_env-0.0.64.dist-info/RECORD +0 -48
  45. /comfy_env/{env/cuda_gpu_detection.py → pixi/cuda_detection.py} +0 -0
  46. /comfy_env/{env → pixi}/platform/__init__.py +0 -0
  47. /comfy_env/{env → pixi}/platform/base.py +0 -0
  48. /comfy_env/{env → pixi}/platform/darwin.py +0 -0
  49. /comfy_env/{env → pixi}/platform/linux.py +0 -0
  50. /comfy_env/{env → pixi}/platform/windows.py +0 -0
  51. /comfy_env/{registry.py → pixi/registry.py} +0 -0
  52. /comfy_env/{wheel_sources.yml → pixi/wheel_sources.yml} +0 -0
  53. {comfy_env-0.0.64.dist-info → comfy_env-0.0.66.dist-info}/WHEEL +0 -0
  54. {comfy_env-0.0.64.dist-info → comfy_env-0.0.66.dist-info}/entry_points.txt +0 -0
  55. {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")