comfy-env 0.0.64__tar.gz → 0.0.66__tar.gz

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 (62) hide show
  1. {comfy_env-0.0.64 → comfy_env-0.0.66}/PKG-INFO +2 -1
  2. {comfy_env-0.0.64 → comfy_env-0.0.66}/pyproject.toml +3 -2
  3. comfy_env-0.0.66/src/comfy_env/__init__.py +110 -0
  4. {comfy_env-0.0.64 → comfy_env-0.0.66}/src/comfy_env/cli.py +78 -7
  5. comfy_env-0.0.66/src/comfy_env/config/__init__.py +19 -0
  6. comfy_env-0.0.66/src/comfy_env/config/parser.py +151 -0
  7. comfy_env-0.0.66/src/comfy_env/config/types.py +64 -0
  8. comfy_env-0.0.66/src/comfy_env/install.py +171 -0
  9. comfy_env-0.0.66/src/comfy_env/isolation/__init__.py +9 -0
  10. comfy_env-0.0.66/src/comfy_env/isolation/wrap.py +351 -0
  11. {comfy_env-0.0.64 → comfy_env-0.0.66}/src/comfy_env/nodes.py +2 -2
  12. comfy_env-0.0.66/src/comfy_env/pixi/__init__.py +48 -0
  13. comfy_env-0.0.66/src/comfy_env/pixi/core.py +356 -0
  14. {comfy_env-0.0.64/src/comfy_env → comfy_env-0.0.66/src/comfy_env/pixi}/resolver.py +1 -14
  15. comfy_env-0.0.66/src/comfy_env/prestartup.py +60 -0
  16. comfy_env-0.0.66/src/comfy_env/templates/comfy-env-instructions.txt +46 -0
  17. comfy_env-0.0.66/src/comfy_env/templates/comfy-env.toml +118 -0
  18. comfy_env-0.0.66/src/comfy_env/workers/__init__.py +38 -0
  19. {comfy_env-0.0.64 → comfy_env-0.0.66}/src/comfy_env/workers/base.py +1 -1
  20. comfy_env-0.0.64/src/comfy_env/workers/torch_mp.py → comfy_env-0.0.66/src/comfy_env/workers/mp.py +47 -14
  21. comfy_env-0.0.64/src/comfy_env/workers/venv.py → comfy_env-0.0.66/src/comfy_env/workers/subprocess.py +405 -441
  22. comfy_env-0.0.64/src/comfy_env/__init__.py +0 -162
  23. comfy_env-0.0.64/src/comfy_env/decorator.py +0 -700
  24. comfy_env-0.0.64/src/comfy_env/env/__init__.py +0 -47
  25. comfy_env-0.0.64/src/comfy_env/env/config.py +0 -201
  26. comfy_env-0.0.64/src/comfy_env/env/config_file.py +0 -740
  27. comfy_env-0.0.64/src/comfy_env/env/manager.py +0 -636
  28. comfy_env-0.0.64/src/comfy_env/env/security.py +0 -267
  29. comfy_env-0.0.64/src/comfy_env/install.py +0 -449
  30. comfy_env-0.0.64/src/comfy_env/ipc/__init__.py +0 -55
  31. comfy_env-0.0.64/src/comfy_env/ipc/bridge.py +0 -476
  32. comfy_env-0.0.64/src/comfy_env/ipc/protocol.py +0 -265
  33. comfy_env-0.0.64/src/comfy_env/ipc/tensor.py +0 -371
  34. comfy_env-0.0.64/src/comfy_env/ipc/torch_bridge.py +0 -401
  35. comfy_env-0.0.64/src/comfy_env/ipc/transport.py +0 -318
  36. comfy_env-0.0.64/src/comfy_env/ipc/worker.py +0 -221
  37. comfy_env-0.0.64/src/comfy_env/isolation.py +0 -310
  38. comfy_env-0.0.64/src/comfy_env/pixi.py +0 -760
  39. comfy_env-0.0.64/src/comfy_env/stub_imports.py +0 -270
  40. comfy_env-0.0.64/src/comfy_env/stubs/__init__.py +0 -1
  41. comfy_env-0.0.64/src/comfy_env/stubs/comfy/__init__.py +0 -6
  42. comfy_env-0.0.64/src/comfy_env/stubs/comfy/model_management.py +0 -58
  43. comfy_env-0.0.64/src/comfy_env/stubs/comfy/utils.py +0 -29
  44. comfy_env-0.0.64/src/comfy_env/stubs/folder_paths.py +0 -71
  45. comfy_env-0.0.64/src/comfy_env/templates/comfy-env-instructions.txt +0 -103
  46. comfy_env-0.0.64/src/comfy_env/templates/comfy-env.toml +0 -186
  47. comfy_env-0.0.64/src/comfy_env/workers/__init__.py +0 -49
  48. comfy_env-0.0.64/src/comfy_env/workers/pool.py +0 -241
  49. {comfy_env-0.0.64 → comfy_env-0.0.66}/.github/workflows/publish.yml +0 -0
  50. {comfy_env-0.0.64 → comfy_env-0.0.66}/.gitignore +0 -0
  51. {comfy_env-0.0.64 → comfy_env-0.0.66}/LICENSE +0 -0
  52. {comfy_env-0.0.64 → comfy_env-0.0.66}/README.md +0 -0
  53. {comfy_env-0.0.64 → comfy_env-0.0.66}/src/comfy_env/errors.py +0 -0
  54. /comfy_env-0.0.64/src/comfy_env/env/cuda_gpu_detection.py → /comfy_env-0.0.66/src/comfy_env/pixi/cuda_detection.py +0 -0
  55. {comfy_env-0.0.64/src/comfy_env/env → comfy_env-0.0.66/src/comfy_env/pixi}/platform/__init__.py +0 -0
  56. {comfy_env-0.0.64/src/comfy_env/env → comfy_env-0.0.66/src/comfy_env/pixi}/platform/base.py +0 -0
  57. {comfy_env-0.0.64/src/comfy_env/env → comfy_env-0.0.66/src/comfy_env/pixi}/platform/darwin.py +0 -0
  58. {comfy_env-0.0.64/src/comfy_env/env → comfy_env-0.0.66/src/comfy_env/pixi}/platform/linux.py +0 -0
  59. {comfy_env-0.0.64/src/comfy_env/env → comfy_env-0.0.66/src/comfy_env/pixi}/platform/windows.py +0 -0
  60. {comfy_env-0.0.64/src/comfy_env → comfy_env-0.0.66/src/comfy_env/pixi}/registry.py +0 -0
  61. {comfy_env-0.0.64/src/comfy_env → comfy_env-0.0.66/src/comfy_env/pixi}/wheel_sources.yml +0 -0
  62. {comfy_env-0.0.64 → comfy_env-0.0.66}/src/comfy_env/workers/tensor_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comfy-env
3
- Version: 0.0.64
3
+ Version: 0.0.66
4
4
  Summary: Environment management for ComfyUI custom nodes - CUDA wheel resolution and process isolation
5
5
  Project-URL: Homepage, https://github.com/PozzettiAndrea/comfy-env
6
6
  Project-URL: Repository, https://github.com/PozzettiAndrea/comfy-env
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Requires-Python: >=3.10
20
20
  Requires-Dist: pyyaml>=6.0
21
+ Requires-Dist: tomli-w>=1.0.0
21
22
  Requires-Dist: tomli>=2.0.0; python_version < '3.11'
22
23
  Requires-Dist: uv>=0.4.0
23
24
  Provides-Extra: dev
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "comfy-env"
3
- version = "0.0.64"
3
+ version = "0.0.66"
4
4
  description = "Environment management for ComfyUI custom nodes - CUDA wheel resolution and process isolation"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -20,6 +20,7 @@ classifiers = [
20
20
  ]
21
21
  dependencies = [
22
22
  "tomli>=2.0.0; python_version < '3.11'", # TOML parsing (built-in tomllib for 3.11+)
23
+ "tomli-w>=1.0.0", # TOML writing (no stdlib equivalent)
23
24
  "uv>=0.4.0", # Fast Python package installer and venv creator
24
25
  "pyyaml>=6.0", # YAML parsing for wheel_sources.yml
25
26
  ]
@@ -43,7 +44,7 @@ build-backend = "hatchling.build"
43
44
  packages = ["src/comfy_env"]
44
45
 
45
46
  [tool.hatch.build.targets.wheel.force-include]
46
- "src/comfy_env/wheel_sources.yml" = "comfy_env/wheel_sources.yml"
47
+ "src/comfy_env/pixi/wheel_sources.yml" = "comfy_env/pixi/wheel_sources.yml"
47
48
 
48
49
  [tool.ruff]
49
50
  line-length = 100
@@ -0,0 +1,110 @@
1
+ """
2
+ comfy-env: Environment management for ComfyUI custom nodes.
3
+
4
+ All dependencies go through pixi for unified management.
5
+
6
+ Main APIs:
7
+ - install(): Install dependencies from comfy-env.toml
8
+ - wrap_isolated_nodes(): Wrap nodes for subprocess isolation
9
+ """
10
+
11
+ from importlib.metadata import version, PackageNotFoundError
12
+
13
+ try:
14
+ __version__ = version("comfy-env")
15
+ except PackageNotFoundError:
16
+ __version__ = "0.0.0-dev"
17
+
18
+ # Config types and parsing
19
+ from .config import (
20
+ ComfyEnvConfig,
21
+ NodeReq,
22
+ load_config,
23
+ discover_config,
24
+ CONFIG_FILE_NAME,
25
+ )
26
+
27
+ # Pixi integration
28
+ from .pixi import (
29
+ ensure_pixi,
30
+ get_pixi_path,
31
+ get_pixi_python,
32
+ pixi_run,
33
+ pixi_install,
34
+ CUDA_WHEELS_INDEX,
35
+ PACKAGE_REGISTRY,
36
+ detect_cuda_version,
37
+ detect_cuda_environment,
38
+ get_recommended_cuda_version,
39
+ GPUInfo,
40
+ CUDAEnvironment,
41
+ )
42
+
43
+ # Workers
44
+ from .workers import (
45
+ Worker,
46
+ WorkerError,
47
+ MPWorker,
48
+ SubprocessWorker,
49
+ )
50
+
51
+ # Isolation
52
+ from .isolation import wrap_isolated_nodes
53
+
54
+ # Install API
55
+ from .install import install, verify_installation
56
+
57
+ # Prestartup helpers
58
+ from .prestartup import setup_env
59
+
60
+ # Errors
61
+ from .errors import (
62
+ EnvManagerError,
63
+ ConfigError,
64
+ WheelNotFoundError,
65
+ DependencyError,
66
+ CUDANotFoundError,
67
+ InstallError,
68
+ )
69
+
70
+ __all__ = [
71
+ # Install API
72
+ "install",
73
+ "verify_installation",
74
+ # Prestartup
75
+ "setup_env",
76
+ # Isolation
77
+ "wrap_isolated_nodes",
78
+ # Config
79
+ "ComfyEnvConfig",
80
+ "NodeReq",
81
+ "load_config",
82
+ "discover_config",
83
+ "CONFIG_FILE_NAME",
84
+ # Pixi
85
+ "ensure_pixi",
86
+ "get_pixi_path",
87
+ "get_pixi_python",
88
+ "pixi_run",
89
+ "pixi_install",
90
+ "CUDA_WHEELS_INDEX",
91
+ "PACKAGE_REGISTRY",
92
+ # CUDA detection
93
+ "detect_cuda_version",
94
+ "detect_cuda_environment",
95
+ "get_recommended_cuda_version",
96
+ "GPUInfo",
97
+ "CUDAEnvironment",
98
+ # Workers
99
+ "Worker",
100
+ "WorkerError",
101
+ "MPWorker",
102
+ "SubprocessWorker",
103
+ # Errors
104
+ "EnvManagerError",
105
+ "ConfigError",
106
+ "WheelNotFoundError",
107
+ "DependencyError",
108
+ "CUDANotFoundError",
109
+ "InstallError",
110
+ ]
@@ -2,6 +2,8 @@
2
2
  CLI for comfy-env.
3
3
 
4
4
  Provides the `comfy-env` command with subcommands:
5
+ - init: Create a default comfy-env.toml
6
+ - generate: Generate pixi.toml from comfy-env.toml
5
7
  - install: Install dependencies from config
6
8
  - info: Show runtime environment information
7
9
  - resolve: Show resolved wheel URLs
@@ -9,7 +11,9 @@ Provides the `comfy-env` command with subcommands:
9
11
  - list-packages: Show all packages in the built-in registry
10
12
 
11
13
  Usage:
12
- comfy-env install
14
+ comfy-env init ---> creates template comfy-env.toml
15
+ comfy-env generate nodes/cgal/comfy-env.toml ---> nodes/cgal/pixi.toml
16
+ comfy-env install ---> installs from comfy
13
17
  comfy-env install --dry-run
14
18
 
15
19
  comfy-env info
@@ -54,6 +58,23 @@ def main(args: Optional[List[str]] = None) -> int:
54
58
  help="Overwrite existing config file",
55
59
  )
56
60
 
61
+ # generate command
62
+ generate_parser = subparsers.add_parser(
63
+ "generate",
64
+ help="Generate pixi.toml from comfy-env.toml",
65
+ description="Parse comfy-env.toml and generate a pixi.toml in the same directory",
66
+ )
67
+ generate_parser.add_argument(
68
+ "config",
69
+ type=str,
70
+ help="Path to comfy-env.toml",
71
+ )
72
+ generate_parser.add_argument(
73
+ "--force", "-f",
74
+ action="store_true",
75
+ help="Overwrite existing pixi.toml",
76
+ )
77
+
57
78
  # install command
58
79
  install_parser = subparsers.add_parser(
59
80
  "install",
@@ -148,6 +169,8 @@ def main(args: Optional[List[str]] = None) -> int:
148
169
  try:
149
170
  if parsed.command == "init":
150
171
  return cmd_init(parsed)
172
+ elif parsed.command == "generate":
173
+ return cmd_generate(parsed)
151
174
  elif parsed.command == "install":
152
175
  return cmd_install(parsed)
153
176
  elif parsed.command == "info":
@@ -205,6 +228,53 @@ def cmd_init(args) -> int:
205
228
  return 0
206
229
 
207
230
 
231
+ def cmd_generate(args) -> int:
232
+ """Handle generate command - create pixi.toml from comfy-env.toml."""
233
+ from .config.parser import load_config
234
+ from .pixi import create_pixi_toml
235
+
236
+ config_path = Path(args.config).resolve()
237
+
238
+ if not config_path.exists():
239
+ print(f"Config file not found: {config_path}", file=sys.stderr)
240
+ return 1
241
+
242
+ if config_path.name != "comfy-env.toml":
243
+ print(f"Warning: Expected comfy-env.toml, got {config_path.name}", file=sys.stderr)
244
+
245
+ node_dir = config_path.parent
246
+ pixi_path = node_dir / "pixi.toml"
247
+
248
+ if pixi_path.exists() and not args.force:
249
+ print(f"pixi.toml already exists: {pixi_path}", file=sys.stderr)
250
+ print("Use --force to overwrite", file=sys.stderr)
251
+ return 1
252
+
253
+ # Load the config
254
+ config = load_config(config_path)
255
+ if not config or not config.envs:
256
+ print(f"No environments found in {config_path}", file=sys.stderr)
257
+ return 1
258
+
259
+ # Use the first environment
260
+ env_name = next(iter(config.envs.keys()))
261
+ env_config = config.envs[env_name]
262
+
263
+ print(f"Generating pixi.toml from {config_path}")
264
+ print(f" Environment: {env_name}")
265
+ print(f" Python: {env_config.python}")
266
+
267
+ # Generate pixi.toml
268
+ result_path = create_pixi_toml(env_config, node_dir)
269
+
270
+ print(f"Created {result_path}")
271
+ print()
272
+ print("Next steps:")
273
+ print(f" cd {node_dir}")
274
+ print(" pixi install")
275
+ return 0
276
+
277
+
208
278
  def cmd_install(args) -> int:
209
279
  """Handle install command."""
210
280
  from .install import install
@@ -228,7 +298,7 @@ def cmd_install(args) -> int:
228
298
 
229
299
  def cmd_info(args) -> int:
230
300
  """Handle info command."""
231
- from .resolver import RuntimeEnv
301
+ from .pixi import RuntimeEnv
232
302
 
233
303
  env = RuntimeEnv.detect()
234
304
 
@@ -264,9 +334,10 @@ def cmd_info(args) -> int:
264
334
 
265
335
  def cmd_resolve(args) -> int:
266
336
  """Handle resolve command."""
267
- from .resolver import RuntimeEnv, parse_wheel_requirement
268
- from .registry import PACKAGE_REGISTRY, get_cuda_short2
269
- from .env.config_file import discover_env_config, load_env_from_file
337
+ from .pixi import RuntimeEnv, parse_wheel_requirement
338
+ from .pixi import PACKAGE_REGISTRY
339
+ from .pixi.registry import get_cuda_short2
340
+ from .config.parser import discover_env_config, load_env_from_file
270
341
 
271
342
  env = RuntimeEnv.detect()
272
343
  packages = []
@@ -353,7 +424,7 @@ def _substitute_template(template: str, vars_dict: dict) -> str:
353
424
  def cmd_doctor(args) -> int:
354
425
  """Handle doctor command."""
355
426
  from .install import verify_installation
356
- from .env.config_file import discover_env_config, load_env_from_file
427
+ from .config.parser import discover_env_config, load_env_from_file
357
428
 
358
429
  print("Running diagnostics...")
359
430
  print("=" * 40)
@@ -397,7 +468,7 @@ def cmd_doctor(args) -> int:
397
468
 
398
469
  def cmd_list_packages(args) -> int:
399
470
  """Handle list-packages command."""
400
- from .registry import PACKAGE_REGISTRY
471
+ from .pixi import PACKAGE_REGISTRY
401
472
 
402
473
  if args.json:
403
474
  import json
@@ -0,0 +1,19 @@
1
+ """
2
+ Config parsing for comfy-env.
3
+
4
+ This module handles parsing comfy-env.toml files and provides
5
+ typed configuration objects.
6
+ """
7
+
8
+ from .types import ComfyEnvConfig, NodeReq
9
+ from .parser import load_config, discover_config, CONFIG_FILE_NAME
10
+
11
+ __all__ = [
12
+ # Types
13
+ "ComfyEnvConfig",
14
+ "NodeReq",
15
+ # Parser
16
+ "load_config",
17
+ "discover_config",
18
+ "CONFIG_FILE_NAME",
19
+ ]
@@ -0,0 +1,151 @@
1
+ """Load configuration from comfy-env.toml.
2
+
3
+ comfy-env.toml is a superset of pixi.toml. Custom sections we handle:
4
+ - python = "3.11" - Python version for isolated envs
5
+ - [cuda] packages = [...] - CUDA packages (triggers find-links + PyTorch detection)
6
+ - [node_reqs] - Other ComfyUI nodes to clone
7
+
8
+ Everything else passes through to pixi.toml directly.
9
+
10
+ Example config:
11
+
12
+ python = "3.11"
13
+
14
+ [cuda]
15
+ packages = ["cumesh"]
16
+
17
+ [dependencies]
18
+ mesalib = "*"
19
+ cgal = "*"
20
+
21
+ [pypi-dependencies]
22
+ numpy = ">=1.21.0,<2"
23
+ trimesh = { version = ">=4.0.0", extras = ["easy"] }
24
+
25
+ [target.linux-64.pypi-dependencies]
26
+ embreex = "*"
27
+
28
+ [node_reqs]
29
+ SomeNode = "owner/repo"
30
+ """
31
+
32
+ import copy
33
+ import sys
34
+ from pathlib import Path
35
+ from typing import Optional, Dict, Any, List
36
+
37
+ # Use built-in tomllib (Python 3.11+) or tomli fallback
38
+ if sys.version_info >= (3, 11):
39
+ import tomllib
40
+ else:
41
+ try:
42
+ import tomli as tomllib
43
+ except ImportError:
44
+ tomllib = None # type: ignore
45
+
46
+ from .types import ComfyEnvConfig, NodeReq
47
+
48
+
49
+ CONFIG_FILE_NAME = "comfy-env.toml"
50
+
51
+ # Sections we handle specially (not passed through to pixi.toml)
52
+ CUSTOM_SECTIONS = {"python", "cuda", "node_reqs"}
53
+
54
+
55
+ def load_config(path: Path) -> ComfyEnvConfig:
56
+ """
57
+ Load configuration from a TOML file.
58
+
59
+ Args:
60
+ path: Path to comfy-env.toml
61
+
62
+ Returns:
63
+ ComfyEnvConfig instance
64
+
65
+ Raises:
66
+ FileNotFoundError: If config file doesn't exist
67
+ ImportError: If tomli not installed (Python < 3.11)
68
+ """
69
+ if tomllib is None:
70
+ raise ImportError(
71
+ "TOML parsing requires tomli for Python < 3.11. "
72
+ "Install with: pip install tomli"
73
+ )
74
+
75
+ path = Path(path)
76
+ if not path.exists():
77
+ raise FileNotFoundError(f"Config file not found: {path}")
78
+
79
+ with open(path, "rb") as f:
80
+ data = tomllib.load(f)
81
+
82
+ return _parse_config(data)
83
+
84
+
85
+ def discover_config(node_dir: Path) -> Optional[ComfyEnvConfig]:
86
+ """
87
+ Find and load comfy-env.toml from a directory.
88
+
89
+ Args:
90
+ node_dir: Directory to search
91
+
92
+ Returns:
93
+ ComfyEnvConfig if found, None otherwise
94
+ """
95
+ if tomllib is None:
96
+ return None
97
+
98
+ config_path = Path(node_dir) / CONFIG_FILE_NAME
99
+ if config_path.exists():
100
+ return load_config(config_path)
101
+
102
+ return None
103
+
104
+
105
+ def _parse_config(data: Dict[str, Any]) -> ComfyEnvConfig:
106
+ """Parse TOML data into ComfyEnvConfig."""
107
+ # Make a copy so we can pop our custom sections
108
+ data = copy.deepcopy(data)
109
+
110
+ # Extract python version (top-level key)
111
+ python_version = data.pop("python", None)
112
+ if python_version is not None:
113
+ python_version = str(python_version)
114
+
115
+ # Extract [cuda] section
116
+ cuda_data = data.pop("cuda", {})
117
+ cuda_packages = _ensure_list(cuda_data.get("packages", []))
118
+
119
+ # Extract [node_reqs] section
120
+ node_reqs_data = data.pop("node_reqs", {})
121
+ node_reqs = _parse_node_reqs(node_reqs_data)
122
+
123
+ # Everything else passes through to pixi.toml
124
+ pixi_passthrough = data
125
+
126
+ return ComfyEnvConfig(
127
+ python=python_version,
128
+ cuda_packages=cuda_packages,
129
+ node_reqs=node_reqs,
130
+ pixi_passthrough=pixi_passthrough,
131
+ )
132
+
133
+
134
+ def _parse_node_reqs(data: Dict[str, Any]) -> List[NodeReq]:
135
+ """Parse [node_reqs] section."""
136
+ node_reqs = []
137
+ for name, value in data.items():
138
+ if isinstance(value, str):
139
+ node_reqs.append(NodeReq(name=name, repo=value))
140
+ elif isinstance(value, dict):
141
+ node_reqs.append(NodeReq(name=name, repo=value.get("repo", "")))
142
+ return node_reqs
143
+
144
+
145
+ def _ensure_list(value) -> List:
146
+ """Ensure value is a list."""
147
+ if isinstance(value, list):
148
+ return value
149
+ if value:
150
+ return [value]
151
+ return []
@@ -0,0 +1,64 @@
1
+ """Configuration types for comfy-env."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Dict, List, Optional
5
+
6
+
7
+ @dataclass
8
+ class NodeReq:
9
+ """A node dependency (another ComfyUI custom node)."""
10
+ name: str
11
+ repo: str # GitHub repo, e.g., "owner/repo"
12
+
13
+
14
+ @dataclass
15
+ class ComfyEnvConfig:
16
+ """
17
+ Configuration from comfy-env.toml.
18
+
19
+ comfy-env.toml is a superset of pixi.toml. Custom sections we handle:
20
+ - python = "3.11" - Python version for isolated envs
21
+ - [cuda] packages = [...] - CUDA packages (triggers find-links + PyTorch detection)
22
+ - [node_reqs] - Other ComfyUI nodes to clone
23
+
24
+ Everything else passes through to pixi.toml directly:
25
+ - [dependencies] - conda packages
26
+ - [pypi-dependencies] - pip packages
27
+ - [target.linux-64.pypi-dependencies] - platform-specific deps
28
+ - Any other pixi.toml syntax
29
+
30
+ Example config:
31
+ python = "3.11"
32
+
33
+ [cuda]
34
+ packages = ["cumesh"]
35
+
36
+ [dependencies]
37
+ mesalib = "*"
38
+ cgal = "*"
39
+
40
+ [pypi-dependencies]
41
+ numpy = ">=1.21.0,<2"
42
+ trimesh = { version = ">=4.0.0", extras = ["easy"] }
43
+
44
+ [target.linux-64.pypi-dependencies]
45
+ embreex = "*"
46
+
47
+ [node_reqs]
48
+ SomeNode = "owner/repo"
49
+ """
50
+ # python = "3.11" - Python version (for isolated envs)
51
+ python: Optional[str] = None
52
+
53
+ # [cuda] - CUDA packages (installed via find-links index)
54
+ cuda_packages: List[str] = field(default_factory=list)
55
+
56
+ # [node_reqs] - other ComfyUI nodes to clone
57
+ node_reqs: List[NodeReq] = field(default_factory=list)
58
+
59
+ # Everything else from comfy-env.toml passes through to pixi.toml
60
+ pixi_passthrough: Dict[str, Any] = field(default_factory=dict)
61
+
62
+ @property
63
+ def has_cuda(self) -> bool:
64
+ return bool(self.cuda_packages)
@@ -0,0 +1,171 @@
1
+ """
2
+ Installation API for comfy-env.
3
+
4
+ Example:
5
+ from comfy_env import install
6
+ install() # Auto-discovers comfy-env.toml and installs
7
+ """
8
+
9
+ import inspect
10
+ from pathlib import Path
11
+ from typing import Callable, List, Optional, Set, Union
12
+
13
+ from .config.types import ComfyEnvConfig, NodeReq
14
+ from .config.parser import load_config, discover_config
15
+
16
+
17
+ def install(
18
+ config: Optional[Union[str, Path]] = None,
19
+ node_dir: Optional[Path] = None,
20
+ log_callback: Optional[Callable[[str], None]] = None,
21
+ dry_run: bool = False,
22
+ ) -> bool:
23
+ """
24
+ Install dependencies from comfy-env.toml.
25
+
26
+ Args:
27
+ config: Optional path to comfy-env.toml. Auto-discovered if not provided.
28
+ node_dir: Optional node directory. Auto-discovered from caller if not provided.
29
+ log_callback: Optional callback for logging. Defaults to print.
30
+ dry_run: If True, show what would be installed without installing.
31
+
32
+ Returns:
33
+ True if installation succeeded.
34
+ """
35
+ # Auto-discover caller's directory if not provided
36
+ if node_dir is None:
37
+ frame = inspect.stack()[1]
38
+ caller_file = frame.filename
39
+ node_dir = Path(caller_file).parent.resolve()
40
+
41
+ log = log_callback or print
42
+
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:
53
+ raise FileNotFoundError(
54
+ f"No comfy-env.toml found in {node_dir}. "
55
+ "Create comfy-env.toml to define dependencies."
56
+ )
57
+
58
+ # Install node dependencies first
59
+ if cfg.node_reqs:
60
+ _install_node_dependencies(cfg.node_reqs, node_dir, log, dry_run)
61
+
62
+ # Install everything via pixi
63
+ _install_via_pixi(cfg, node_dir, log, dry_run)
64
+
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
70
+
71
+
72
+ def _install_node_dependencies(
73
+ node_reqs: List[NodeReq],
74
+ node_dir: Path,
75
+ log: Callable[[str], None],
76
+ dry_run: bool,
77
+ ) -> None:
78
+ """Install node dependencies (other ComfyUI custom nodes)."""
79
+ from .nodes import install_node_deps
80
+
81
+ custom_nodes_dir = node_dir.parent
82
+ log(f"\nInstalling {len(node_reqs)} node dependencies...")
83
+
84
+ if dry_run:
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
90
+
91
+ visited: Set[str] = {node_dir.name}
92
+ install_node_deps(node_reqs, custom_nodes_dir, log, visited)
93
+
94
+
95
+ def _install_via_pixi(
96
+ cfg: ComfyEnvConfig,
97
+ node_dir: Path,
98
+ log: Callable[[str], None],
99
+ dry_run: bool,
100
+ ) -> None:
101
+ """Install all packages via pixi."""
102
+ from .pixi import pixi_install
103
+
104
+ # Count what we're installing
105
+ cuda_count = len(cfg.cuda_packages)
106
+
107
+ # Count from passthrough (pixi-native format)
108
+ deps = cfg.pixi_passthrough.get("dependencies", {})
109
+ pypi_deps = cfg.pixi_passthrough.get("pypi-dependencies", {})
110
+
111
+ if cuda_count == 0 and not deps and not pypi_deps:
112
+ log("No packages to install")
113
+ return
114
+
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)}")
122
+
123
+ if dry_run:
124
+ log("\n(dry run - no changes made)")
125
+ return
126
+
127
+ pixi_install(cfg, node_dir, log)
128
+
129
+
130
+ def _install_isolated_subdirs(
131
+ node_dir: Path,
132
+ log: Callable[[str], None],
133
+ dry_run: bool,
134
+ ) -> None:
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)
143
+
144
+ sub_dir = config_file.parent
145
+ relative = sub_dir.relative_to(node_dir)
146
+
147
+ log(f"\n[isolated] Installing: {relative}")
148
+ sub_cfg = load_config(config_file)
149
+
150
+ if dry_run:
151
+ log(f" (dry run)")
152
+ continue
153
+
154
+ pixi_install(sub_cfg, sub_dir, log, create_env_link=True)
155
+
156
+
157
+ def verify_installation(
158
+ packages: List[str],
159
+ log: Callable[[str], None] = print,
160
+ ) -> bool:
161
+ """Verify that packages are importable."""
162
+ all_ok = True
163
+ for package in packages:
164
+ import_name = package.replace("-", "_").split("[")[0]
165
+ try:
166
+ __import__(import_name)
167
+ log(f" {package}: OK")
168
+ except ImportError as e:
169
+ log(f" {package}: FAILED ({e})")
170
+ all_ok = False
171
+ return all_ok