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/install.py CHANGED
@@ -1,137 +1,17 @@
1
1
  """
2
2
  Installation API for comfy-env.
3
3
 
4
- This module provides the main `install()` function that handles:
5
- - Named environments [envname] -> pixi (isolated Python environment)
6
- - Local packages [local] -> uv/pip (in-place to current Python)
7
-
8
4
  Example:
9
5
  from comfy_env import install
10
-
11
- # Auto-discovers config and installs
12
- install()
13
-
14
- # With explicit config path
15
- install(config="comfy-env.toml")
6
+ install() # Auto-discovers comfy-env.toml and installs
16
7
  """
17
8
 
18
9
  import inspect
19
- import shutil
20
- import subprocess
21
- import sys
22
10
  from pathlib import Path
23
- from typing import Callable, Dict, List, Optional, Set, Union
24
-
25
- from .env.config import IsolatedEnv, LocalConfig, NodeReq, SystemConfig
26
- from .env.config_file import load_config, discover_config
27
- from .errors import CUDANotFoundError, InstallError
28
- from .pixi import pixi_install
29
- from .registry import PACKAGE_REGISTRY, get_cuda_short2
30
- from .resolver import RuntimeEnv, parse_wheel_requirement
31
-
32
-
33
- def _install_system_packages(
34
- system_config: SystemConfig,
35
- log: Callable[[str], None],
36
- dry_run: bool = False,
37
- ) -> bool:
38
- """
39
- Install system-level packages (apt, brew, etc.).
40
-
41
- Args:
42
- system_config: SystemConfig with package lists per OS.
43
- log: Logging callback.
44
- dry_run: If True, show what would be installed without installing.
11
+ from typing import Callable, List, Optional, Set, Union
45
12
 
46
- Returns:
47
- True if installation succeeded or no packages needed.
48
- """
49
- platform = sys.platform
50
-
51
- if platform.startswith("linux"):
52
- packages = system_config.linux
53
- if not packages:
54
- return True
55
-
56
- log(f"Installing {len(packages)} system package(s) via apt...")
57
-
58
- if dry_run:
59
- log(f" Would install: {', '.join(packages)}")
60
- return True
61
-
62
- if not shutil.which("apt-get"):
63
- log(" Warning: apt-get not found. Cannot install system packages.")
64
- log(f" Please install manually: {', '.join(packages)}")
65
- return True
66
-
67
- sudo_available = shutil.which("sudo") is not None
68
-
69
- try:
70
- if sudo_available:
71
- log(" Running apt-get update...")
72
- subprocess.run(["sudo", "apt-get", "update"], capture_output=True, text=True)
73
-
74
- log(f" Installing: {', '.join(packages)}")
75
- install_result = subprocess.run(
76
- ["sudo", "apt-get", "install", "-y"] + packages,
77
- capture_output=True,
78
- text=True,
79
- )
80
-
81
- if install_result.returncode != 0:
82
- log(f" Warning: apt-get install failed: {install_result.stderr.strip()}")
83
- log(f" Please install manually: sudo apt-get install {' '.join(packages)}")
84
- else:
85
- log(" System packages installed successfully.")
86
- else:
87
- log(" Warning: sudo not available.")
88
- log(f" Please install manually: sudo apt-get install {' '.join(packages)}")
89
-
90
- except Exception as e:
91
- log(f" Warning: Failed to install system packages: {e}")
92
- log(f" Please install manually: sudo apt-get install {' '.join(packages)}")
93
-
94
- return True
95
-
96
- elif platform == "darwin":
97
- packages = system_config.darwin
98
- if packages:
99
- log(f"System packages for macOS: {', '.join(packages)}")
100
- log(f" Please install manually: brew install {' '.join(packages)}")
101
- return True
102
-
103
- elif platform == "win32":
104
- packages = system_config.windows
105
- if packages:
106
- log(f"System packages for Windows: {', '.join(packages)}")
107
- log(" Please install manually.")
108
- return True
109
-
110
- return True
111
-
112
-
113
- def _install_node_dependencies(
114
- node_reqs: List[NodeReq],
115
- node_dir: Path,
116
- log: Callable[[str], None],
117
- dry_run: bool = False,
118
- ) -> bool:
119
- """Install node dependencies (other ComfyUI custom nodes)."""
120
- from .nodes import install_node_deps
121
-
122
- custom_nodes_dir = node_dir.parent
123
- log(f"\nInstalling {len(node_reqs)} node dependencies...")
124
-
125
- if dry_run:
126
- for req in node_reqs:
127
- node_path = custom_nodes_dir / req.name
128
- status = "exists" if node_path.exists() else "would clone"
129
- log(f" {req.name}: {status}")
130
- return True
131
-
132
- visited: Set[str] = {node_dir.name}
133
- install_node_deps(node_reqs, custom_nodes_dir, log, visited)
134
- return True
13
+ from .config.types import ComfyEnvConfig, NodeReq
14
+ from .config.parser import load_config, discover_config
135
15
 
136
16
 
137
17
  def install(
@@ -143,10 +23,6 @@ def install(
143
23
  """
144
24
  Install dependencies from comfy-env.toml.
145
25
 
146
- Example:
147
- from comfy_env import install
148
- install()
149
-
150
26
  Args:
151
27
  config: Optional path to comfy-env.toml. Auto-discovered if not provided.
152
28
  node_dir: Optional node directory. Auto-discovered from caller if not provided.
@@ -164,270 +40,118 @@ def install(
164
40
 
165
41
  log = log_callback or print
166
42
 
167
- full_config = _load_full_config(config, node_dir)
168
- if full_config is None:
43
+ # Load config
44
+ if config is not None:
45
+ config_path = Path(config)
46
+ if not config_path.is_absolute():
47
+ config_path = node_dir / config_path
48
+ cfg = load_config(config_path)
49
+ else:
50
+ cfg = discover_config(node_dir)
51
+
52
+ if cfg is None:
169
53
  raise FileNotFoundError(
170
54
  f"No comfy-env.toml found in {node_dir}. "
171
55
  "Create comfy-env.toml to define dependencies."
172
56
  )
173
57
 
174
- if full_config.node_reqs:
175
- _install_node_dependencies(full_config.node_reqs, node_dir, log, dry_run)
176
-
177
- if full_config.has_system:
178
- _install_system_packages(full_config.system, log, dry_run)
179
-
180
- env_config = full_config.default_env
181
-
182
- # Get user wheel_sources overrides
183
- user_wheel_sources = full_config.wheel_sources if hasattr(full_config, 'wheel_sources') else {}
184
-
185
- if env_config:
186
- # Named environment -> always pixi
187
- log(f"Found environment: {env_config.name}")
188
- python_ver = env_config.python or "3.11" # Default to 3.11 if not specified
189
- if not env_config.python:
190
- log(f" No Python version specified, defaulting to {python_ver}")
191
- env_config = IsolatedEnv(
192
- name=env_config.name,
193
- python=python_ver,
194
- cuda=env_config.cuda,
195
- pytorch=env_config.pytorch,
196
- requirements=env_config.requirements,
197
- no_deps_requirements=env_config.no_deps_requirements,
198
- linux_requirements=env_config.linux_requirements,
199
- darwin_requirements=env_config.darwin_requirements,
200
- windows_requirements=env_config.windows_requirements,
201
- conda=env_config.conda,
202
- isolated=env_config.isolated,
203
- )
204
- log(f" Using pixi backend (Python {python_ver})")
205
- return pixi_install(env_config, node_dir, log, dry_run)
206
- elif full_config.has_local:
207
- # [local] section -> uv in-place install
208
- return _install_local(full_config.local, node_dir, log, dry_run, user_wheel_sources)
209
- else:
210
- log("No packages to install")
211
- return True
58
+ # Install node dependencies first
59
+ if cfg.node_reqs:
60
+ _install_node_dependencies(cfg.node_reqs, node_dir, log, dry_run)
212
61
 
62
+ # Install everything via pixi
63
+ _install_via_pixi(cfg, node_dir, log, dry_run)
213
64
 
214
- def _load_full_config(config: Optional[Union[str, Path]], node_dir: Path):
215
- """Load full EnvManagerConfig (includes tools)."""
216
- if config is not None:
217
- config_path = Path(config)
218
- if not config_path.is_absolute():
219
- config_path = node_dir / config_path
220
- return load_config(config_path, node_dir)
221
- return discover_config(node_dir)
65
+ # Auto-discover and install isolated subdirectory environments
66
+ _install_isolated_subdirs(node_dir, log, dry_run)
67
+
68
+ log("\nInstallation complete!")
69
+ return True
222
70
 
223
71
 
224
- def _install_local(
225
- local_config: LocalConfig,
72
+ def _install_node_dependencies(
73
+ node_reqs: List[NodeReq],
226
74
  node_dir: Path,
227
75
  log: Callable[[str], None],
228
76
  dry_run: bool,
229
- user_wheel_sources: Dict[str, str],
230
- ) -> bool:
231
- """Install local packages into current environment."""
232
- log("Installing local packages into host environment")
233
-
234
- if sys.platform == "win32":
235
- log("Installing MSVC runtime for Windows...")
236
- if not dry_run:
237
- _pip_install(["msvc-runtime"], no_deps=False, log=log)
238
-
239
- env = RuntimeEnv.detect()
240
- log(f"Detected environment: {env}")
241
-
242
- if not env.cuda_version and local_config.cuda_packages:
243
- raise CUDANotFoundError(package=", ".join(local_config.cuda_packages.keys()))
77
+ ) -> None:
78
+ """Install node dependencies (other ComfyUI custom nodes)."""
79
+ from .nodes import install_node_deps
244
80
 
245
- cuda_packages = []
246
- for pkg, ver in local_config.cuda_packages.items():
247
- if ver and ver != "*":
248
- cuda_packages.append(f"{pkg}=={ver}")
249
- else:
250
- cuda_packages.append(pkg)
81
+ custom_nodes_dir = node_dir.parent
82
+ log(f"\nInstalling {len(node_reqs)} node dependencies...")
251
83
 
252
84
  if dry_run:
253
- log("\nDry run - would install:")
254
- for pkg in cuda_packages:
255
- log(f" {pkg}")
256
- if local_config.requirements:
257
- log(" Regular packages:")
258
- for pkg in local_config.requirements:
259
- log(f" {pkg}")
260
- return True
261
-
262
- if cuda_packages:
263
- log(f"\nInstalling {len(cuda_packages)} CUDA packages...")
264
- for req in cuda_packages:
265
- package, version = parse_wheel_requirement(req)
266
- _install_cuda_package(package, version, env, user_wheel_sources, log)
267
-
268
- if local_config.requirements:
269
- log(f"\nInstalling {len(local_config.requirements)} regular packages...")
270
- _pip_install(local_config.requirements, no_deps=False, log=log)
271
-
272
- log("\nLocal installation complete!")
273
- return True
85
+ for req in node_reqs:
86
+ node_path = custom_nodes_dir / req.name
87
+ status = "exists" if node_path.exists() else "would clone"
88
+ log(f" {req.name}: {status}")
89
+ return
274
90
 
91
+ visited: Set[str] = {node_dir.name}
92
+ install_node_deps(node_reqs, custom_nodes_dir, log, visited)
275
93
 
276
- def _resolve_wheel_url(
277
- package: str,
278
- version: Optional[str],
279
- env: RuntimeEnv,
280
- user_wheel_sources: Dict[str, str],
281
- ) -> str:
282
- """
283
- Resolve wheel URL for a CUDA package.
284
94
 
285
- Resolution order:
286
- 1. User's [wheel_sources] in comfy-env.toml (highest priority)
287
- 2. Built-in wheel_sources.yml registry
288
- 3. Error if not found
289
- """
290
- pkg_lower = package.lower()
291
- vars_dict = _build_template_vars(env, version)
292
-
293
- # 1. Check user overrides first
294
- if pkg_lower in user_wheel_sources:
295
- template = user_wheel_sources[pkg_lower]
296
- return _substitute_template(template, vars_dict)
297
-
298
- # 2. Check built-in registry
299
- if pkg_lower in PACKAGE_REGISTRY:
300
- config = PACKAGE_REGISTRY[pkg_lower]
301
-
302
- # wheel_template: direct URL
303
- if "wheel_template" in config:
304
- template = config["wheel_template"]
305
-
306
- # Only require version if template uses {version}
307
- if "{version}" in template:
308
- effective_version = version or config.get("default_version")
309
- if not effective_version:
310
- raise InstallError(f"Package {package} requires version (no default in registry)")
311
- vars_dict["version"] = effective_version
312
-
313
- return _substitute_template(template, vars_dict)
314
-
315
- # find_links: pip resolves wheel from index (e.g., miropsota's detectron2)
316
- if "find_links" in config:
317
- find_links_url = config["find_links"]
318
- effective_version = version or config.get("default_version")
319
- if effective_version:
320
- return f"find_links:{find_links_url}:{package}=={effective_version}"
321
- else:
322
- return f"find_links:{find_links_url}:{package}"
323
-
324
- # package_name: PyPI variant (e.g., spconv-cu124)
325
- if "package_name" in config:
326
- pkg_name = _substitute_template(config["package_name"], vars_dict)
327
- return f"pypi:{pkg_name}" # Special marker for PyPI install
328
-
329
- raise InstallError(
330
- f"Package {package} not found in registry or user wheel_sources.\n"
331
- f"Add it to [wheel_sources] in your comfy-env.toml:\n\n"
332
- f"[wheel_sources]\n"
333
- f'{package} = "https://example.com/{package}-{{version}}+cu{{cuda_short}}-{{py_tag}}-{{platform}}.whl"'
334
- )
335
-
336
-
337
- def _install_cuda_package(
338
- package: str,
339
- version: Optional[str],
340
- env: RuntimeEnv,
341
- user_wheel_sources: Dict[str, str],
95
+ def _install_via_pixi(
96
+ cfg: ComfyEnvConfig,
97
+ node_dir: Path,
342
98
  log: Callable[[str], None],
99
+ dry_run: bool,
343
100
  ) -> None:
344
- """
345
- Install a single CUDA package.
346
-
347
- Uses wheel_template for direct URL or package_name for PyPI variants.
348
- """
349
- url_or_marker = _resolve_wheel_url(package, version, env, user_wheel_sources)
350
-
351
- if url_or_marker.startswith("pypi:"):
352
- # PyPI variant package (e.g., spconv-cu124)
353
- pkg_name = url_or_marker[5:] # Strip "pypi:" prefix
354
- pkg_spec = f"{pkg_name}=={version}" if version else pkg_name
355
- log(f" Installing {package} as {pkg_spec} from PyPI...")
356
- _pip_install([pkg_spec], no_deps=False, log=log)
357
- elif url_or_marker.startswith("find_links:"):
358
- # find_links: pip resolves from index URL
359
- _, find_links_url, pkg_spec = url_or_marker.split(":", 2)
360
- log(f" Installing {pkg_spec} from {find_links_url}...")
361
- _pip_install([pkg_spec, "--find-links", find_links_url], no_deps=True, log=log)
362
- else:
363
- # Direct wheel URL
364
- log(f" Installing {package}...")
365
- log(f" URL: {url_or_marker}")
366
- _pip_install([url_or_marker], no_deps=True, log=log)
367
-
101
+ """Install all packages via pixi."""
102
+ from .pixi import pixi_install
368
103
 
369
- def _build_template_vars(env: RuntimeEnv, version: Optional[str] = None) -> Dict[str, str]:
370
- """Build template variables dict from RuntimeEnv."""
371
- vars_dict = env.as_dict()
104
+ # Count what we're installing
105
+ cuda_count = len(cfg.cuda_packages)
372
106
 
373
- if version:
374
- vars_dict["version"] = version
107
+ # Count from passthrough (pixi-native format)
108
+ deps = cfg.pixi_passthrough.get("dependencies", {})
109
+ pypi_deps = cfg.pixi_passthrough.get("pypi-dependencies", {})
375
110
 
376
- # Add cuda_short2 for spconv (e.g., "124" not "1240")
377
- if env.cuda_version:
378
- vars_dict["cuda_short2"] = get_cuda_short2(env.cuda_version)
111
+ if cuda_count == 0 and not deps and not pypi_deps:
112
+ log("No packages to install")
113
+ return
379
114
 
380
- return vars_dict
115
+ log(f"\nInstalling via pixi:")
116
+ if cuda_count:
117
+ log(f" CUDA packages: {', '.join(cfg.cuda_packages)}")
118
+ if deps:
119
+ log(f" Conda packages: {len(deps)}")
120
+ if pypi_deps:
121
+ log(f" PyPI packages: {len(pypi_deps)}")
381
122
 
123
+ if dry_run:
124
+ log("\n(dry run - no changes made)")
125
+ return
382
126
 
383
- def _substitute_template(template: str, vars_dict: Dict[str, str]) -> str:
384
- """Substitute {var} placeholders in template with values from vars_dict."""
385
- result = template
386
- for key, value in vars_dict.items():
387
- if value is not None:
388
- result = result.replace(f"{{{key}}}", str(value))
389
- return result
127
+ pixi_install(cfg, node_dir, log)
390
128
 
391
129
 
392
- def _pip_install(
393
- packages: List[str],
394
- no_deps: bool = False,
395
- log: Callable[[str], None] = print,
130
+ def _install_isolated_subdirs(
131
+ node_dir: Path,
132
+ log: Callable[[str], None],
133
+ dry_run: bool,
396
134
  ) -> None:
397
- """Install packages using pip (prefers uv if available)."""
398
- pip_cmd = _get_pip_command()
399
-
400
- args = pip_cmd + ["install"]
401
- # uv requires --system when not in a venv (CI environments)
402
- if _is_using_uv():
403
- args.append("--system")
404
- if no_deps:
405
- args.append("--no-deps")
406
- args.extend(packages)
407
-
408
- log(f"Running: {' '.join(args[:3])}... ({len(packages)} packages)")
409
-
410
- result = subprocess.run(args, capture_output=True, text=True)
411
-
412
- if result.returncode != 0:
413
- raise InstallError(
414
- f"Failed to install packages",
415
- exit_code=result.returncode,
416
- stderr=result.stderr,
417
- )
135
+ """Find and install comfy-env.toml in subdirectories."""
136
+ from .pixi import pixi_install
137
+ from .config.parser import CONFIG_FILE_NAME
138
+
139
+ # Find all comfy-env.toml files in subdirectories (not root)
140
+ for config_file in node_dir.rglob(CONFIG_FILE_NAME):
141
+ if config_file.parent == node_dir:
142
+ continue # Skip root (already installed)
418
143
 
144
+ sub_dir = config_file.parent
145
+ relative = sub_dir.relative_to(node_dir)
419
146
 
420
- def _get_pip_command() -> List[str]:
421
- """Get the pip command to use (prefers uv if available)."""
422
- uv_path = shutil.which("uv")
423
- if uv_path:
424
- return [uv_path, "pip"]
425
- return [sys.executable, "-m", "pip"]
147
+ log(f"\n[isolated] Installing: {relative}")
148
+ sub_cfg = load_config(config_file)
426
149
 
150
+ if dry_run:
151
+ log(f" (dry run)")
152
+ continue
427
153
 
428
- def _is_using_uv() -> bool:
429
- """Check if we're using uv for pip commands."""
430
- return shutil.which("uv") is not None
154
+ pixi_install(sub_cfg, sub_dir, log, create_env_link=True)
431
155
 
432
156
 
433
157
  def verify_installation(
@@ -445,5 +169,3 @@ def verify_installation(
445
169
  log(f" {package}: FAILED ({e})")
446
170
  all_ok = False
447
171
  return all_ok
448
-
449
-
@@ -0,0 +1,9 @@
1
+ """
2
+ Isolation module for wrapping ComfyUI nodes to run in isolated environments.
3
+ """
4
+
5
+ from .wrap import wrap_isolated_nodes
6
+
7
+ __all__ = [
8
+ "wrap_isolated_nodes",
9
+ ]