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.
- {comfy_env-0.0.64 → comfy_env-0.0.66}/PKG-INFO +2 -1
- {comfy_env-0.0.64 → comfy_env-0.0.66}/pyproject.toml +3 -2
- comfy_env-0.0.66/src/comfy_env/__init__.py +110 -0
- {comfy_env-0.0.64 → comfy_env-0.0.66}/src/comfy_env/cli.py +78 -7
- comfy_env-0.0.66/src/comfy_env/config/__init__.py +19 -0
- comfy_env-0.0.66/src/comfy_env/config/parser.py +151 -0
- comfy_env-0.0.66/src/comfy_env/config/types.py +64 -0
- comfy_env-0.0.66/src/comfy_env/install.py +171 -0
- comfy_env-0.0.66/src/comfy_env/isolation/__init__.py +9 -0
- comfy_env-0.0.66/src/comfy_env/isolation/wrap.py +351 -0
- {comfy_env-0.0.64 → comfy_env-0.0.66}/src/comfy_env/nodes.py +2 -2
- comfy_env-0.0.66/src/comfy_env/pixi/__init__.py +48 -0
- comfy_env-0.0.66/src/comfy_env/pixi/core.py +356 -0
- {comfy_env-0.0.64/src/comfy_env → comfy_env-0.0.66/src/comfy_env/pixi}/resolver.py +1 -14
- comfy_env-0.0.66/src/comfy_env/prestartup.py +60 -0
- comfy_env-0.0.66/src/comfy_env/templates/comfy-env-instructions.txt +46 -0
- comfy_env-0.0.66/src/comfy_env/templates/comfy-env.toml +118 -0
- comfy_env-0.0.66/src/comfy_env/workers/__init__.py +38 -0
- {comfy_env-0.0.64 → comfy_env-0.0.66}/src/comfy_env/workers/base.py +1 -1
- 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
- comfy_env-0.0.64/src/comfy_env/workers/venv.py → comfy_env-0.0.66/src/comfy_env/workers/subprocess.py +405 -441
- comfy_env-0.0.64/src/comfy_env/__init__.py +0 -162
- comfy_env-0.0.64/src/comfy_env/decorator.py +0 -700
- comfy_env-0.0.64/src/comfy_env/env/__init__.py +0 -47
- comfy_env-0.0.64/src/comfy_env/env/config.py +0 -201
- comfy_env-0.0.64/src/comfy_env/env/config_file.py +0 -740
- comfy_env-0.0.64/src/comfy_env/env/manager.py +0 -636
- comfy_env-0.0.64/src/comfy_env/env/security.py +0 -267
- comfy_env-0.0.64/src/comfy_env/install.py +0 -449
- comfy_env-0.0.64/src/comfy_env/ipc/__init__.py +0 -55
- comfy_env-0.0.64/src/comfy_env/ipc/bridge.py +0 -476
- comfy_env-0.0.64/src/comfy_env/ipc/protocol.py +0 -265
- comfy_env-0.0.64/src/comfy_env/ipc/tensor.py +0 -371
- comfy_env-0.0.64/src/comfy_env/ipc/torch_bridge.py +0 -401
- comfy_env-0.0.64/src/comfy_env/ipc/transport.py +0 -318
- comfy_env-0.0.64/src/comfy_env/ipc/worker.py +0 -221
- comfy_env-0.0.64/src/comfy_env/isolation.py +0 -310
- comfy_env-0.0.64/src/comfy_env/pixi.py +0 -760
- comfy_env-0.0.64/src/comfy_env/stub_imports.py +0 -270
- comfy_env-0.0.64/src/comfy_env/stubs/__init__.py +0 -1
- comfy_env-0.0.64/src/comfy_env/stubs/comfy/__init__.py +0 -6
- comfy_env-0.0.64/src/comfy_env/stubs/comfy/model_management.py +0 -58
- comfy_env-0.0.64/src/comfy_env/stubs/comfy/utils.py +0 -29
- comfy_env-0.0.64/src/comfy_env/stubs/folder_paths.py +0 -71
- comfy_env-0.0.64/src/comfy_env/templates/comfy-env-instructions.txt +0 -103
- comfy_env-0.0.64/src/comfy_env/templates/comfy-env.toml +0 -186
- comfy_env-0.0.64/src/comfy_env/workers/__init__.py +0 -49
- comfy_env-0.0.64/src/comfy_env/workers/pool.py +0 -241
- {comfy_env-0.0.64 → comfy_env-0.0.66}/.github/workflows/publish.yml +0 -0
- {comfy_env-0.0.64 → comfy_env-0.0.66}/.gitignore +0 -0
- {comfy_env-0.0.64 → comfy_env-0.0.66}/LICENSE +0 -0
- {comfy_env-0.0.64 → comfy_env-0.0.66}/README.md +0 -0
- {comfy_env-0.0.64 → comfy_env-0.0.66}/src/comfy_env/errors.py +0 -0
- /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
- {comfy_env-0.0.64/src/comfy_env/env → comfy_env-0.0.66/src/comfy_env/pixi}/platform/__init__.py +0 -0
- {comfy_env-0.0.64/src/comfy_env/env → comfy_env-0.0.66/src/comfy_env/pixi}/platform/base.py +0 -0
- {comfy_env-0.0.64/src/comfy_env/env → comfy_env-0.0.66/src/comfy_env/pixi}/platform/darwin.py +0 -0
- {comfy_env-0.0.64/src/comfy_env/env → comfy_env-0.0.66/src/comfy_env/pixi}/platform/linux.py +0 -0
- {comfy_env-0.0.64/src/comfy_env/env → comfy_env-0.0.66/src/comfy_env/pixi}/platform/windows.py +0 -0
- {comfy_env-0.0.64/src/comfy_env → comfy_env-0.0.66/src/comfy_env/pixi}/registry.py +0 -0
- {comfy_env-0.0.64/src/comfy_env → comfy_env-0.0.66/src/comfy_env/pixi}/wheel_sources.yml +0 -0
- {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.
|
|
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.
|
|
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
|
|
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 .
|
|
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 .
|
|
268
|
-
from .
|
|
269
|
-
from .
|
|
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 .
|
|
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 .
|
|
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
|