comfy-env 0.0.8__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 ADDED
@@ -0,0 +1,161 @@
1
+ """
2
+ comfy-env: Environment management for ComfyUI custom nodes.
3
+
4
+ This package provides:
5
+ - CUDA wheel resolution and in-place installation (Type 2 nodes)
6
+ - Process isolation with separate venvs (Type 1 nodes)
7
+
8
+ ## Quick Start - In-Place Installation
9
+
10
+ from comfy_env import install
11
+
12
+ # Auto-discover config and install CUDA wheels
13
+ install()
14
+
15
+ # Or with explicit config
16
+ install(config="comfyui_env.toml")
17
+
18
+ ## Quick Start - Process Isolation
19
+
20
+ from comfy_env.workers import get_worker, TorchMPWorker
21
+
22
+ # Same-venv isolation (zero-copy tensors)
23
+ worker = TorchMPWorker()
24
+ result = worker.call(my_gpu_function, image=tensor)
25
+
26
+ # Cross-venv isolation
27
+ from comfy_env.workers import PersistentVenvWorker
28
+ worker = PersistentVenvWorker(python="/path/to/venv/bin/python")
29
+ result = worker.call_module("my_module", "my_func", image=tensor)
30
+
31
+ ## CLI
32
+
33
+ comfy-env install # Install from config
34
+ comfy-env info # Show environment info
35
+ comfy-env resolve pkg==1.0 # Show resolved wheel URL
36
+ comfy-env doctor # Verify installation
37
+
38
+ ## Legacy APIs (still supported)
39
+
40
+ The @isolated decorator and WorkerBridge are still available.
41
+ """
42
+
43
+ __version__ = "0.0.8"
44
+
45
+ from .env.config import IsolatedEnv, EnvManagerConfig, LocalConfig, NodeReq
46
+ from .env.config_file import (
47
+ load_env_from_file,
48
+ discover_env_config,
49
+ load_config,
50
+ discover_config,
51
+ CONFIG_FILE_NAMES,
52
+ )
53
+ from .env.manager import IsolatedEnvManager
54
+ from .env.detection import detect_cuda_version, detect_gpu_info, get_gpu_summary
55
+ from .env.security import (
56
+ normalize_env_name,
57
+ validate_dependency,
58
+ validate_dependencies,
59
+ validate_path_within_root,
60
+ validate_wheel_url,
61
+ )
62
+ from .ipc.bridge import WorkerBridge
63
+ from .ipc.worker import BaseWorker, register
64
+ from .decorator import isolated, shutdown_all_processes
65
+
66
+ # New in-place installation API
67
+ from .install import install, verify_installation
68
+ from .resolver import RuntimeEnv, WheelResolver
69
+ from .errors import (
70
+ EnvManagerError,
71
+ ConfigError,
72
+ WheelNotFoundError,
73
+ DependencyError,
74
+ CUDANotFoundError,
75
+ InstallError,
76
+ )
77
+
78
+ # New workers module (recommended API)
79
+ from .workers import (
80
+ Worker,
81
+ TorchMPWorker,
82
+ VenvWorker,
83
+ WorkerPool,
84
+ get_worker,
85
+ register_worker,
86
+ shutdown_workers,
87
+ )
88
+
89
+ # TorchBridge is optional (requires PyTorch)
90
+ try:
91
+ from .ipc.torch_bridge import TorchBridge, TorchWorker
92
+ _TORCH_AVAILABLE = True
93
+ except ImportError:
94
+ _TORCH_AVAILABLE = False
95
+
96
+ # PersistentVenvWorker requires the ipc.transport module
97
+ try:
98
+ from .workers.venv import PersistentVenvWorker
99
+ _PERSISTENT_AVAILABLE = True
100
+ except ImportError:
101
+ _PERSISTENT_AVAILABLE = False
102
+
103
+ __all__ = [
104
+ # NEW: In-place installation API
105
+ "install",
106
+ "verify_installation",
107
+ "RuntimeEnv",
108
+ "WheelResolver",
109
+ # Errors
110
+ "EnvManagerError",
111
+ "ConfigError",
112
+ "WheelNotFoundError",
113
+ "DependencyError",
114
+ "CUDANotFoundError",
115
+ "InstallError",
116
+ # Workers API (recommended for isolation)
117
+ "Worker",
118
+ "TorchMPWorker",
119
+ "VenvWorker",
120
+ "WorkerPool",
121
+ "get_worker",
122
+ "register_worker",
123
+ "shutdown_workers",
124
+ # Environment & Config
125
+ "IsolatedEnv",
126
+ "EnvManagerConfig",
127
+ "LocalConfig",
128
+ "NodeReq",
129
+ "IsolatedEnvManager",
130
+ # Config file loading
131
+ "load_env_from_file",
132
+ "discover_env_config",
133
+ "load_config",
134
+ "discover_config",
135
+ "CONFIG_FILE_NAMES",
136
+ # Detection
137
+ "detect_cuda_version",
138
+ "detect_gpu_info",
139
+ "get_gpu_summary",
140
+ # Security validation
141
+ "normalize_env_name",
142
+ "validate_dependency",
143
+ "validate_dependencies",
144
+ "validate_path_within_root",
145
+ "validate_wheel_url",
146
+ # Legacy IPC (subprocess-based)
147
+ "WorkerBridge",
148
+ "BaseWorker",
149
+ "register",
150
+ # Legacy Decorator API
151
+ "isolated",
152
+ "shutdown_all_processes",
153
+ ]
154
+
155
+ # Add torch-based IPC if available
156
+ if _TORCH_AVAILABLE:
157
+ __all__ += ["TorchBridge", "TorchWorker"]
158
+
159
+ # Add PersistentVenvWorker if available
160
+ if _PERSISTENT_AVAILABLE:
161
+ __all__ += ["PersistentVenvWorker"]
comfy_env/cli.py ADDED
@@ -0,0 +1,388 @@
1
+ """
2
+ CLI for comfy-env.
3
+
4
+ Provides the `comfy-env` command with subcommands:
5
+ - install: Install dependencies from config
6
+ - info: Show runtime environment information
7
+ - resolve: Show resolved wheel URLs
8
+ - doctor: Verify installation
9
+ - list-packages: Show all packages in the built-in registry
10
+
11
+ Usage:
12
+ comfy-env install
13
+ comfy-env install --isolated
14
+ comfy-env install --dry-run
15
+
16
+ comfy-env info
17
+
18
+ comfy-env resolve nvdiffrast==0.4.0
19
+ comfy-env resolve --all
20
+
21
+ comfy-env doctor
22
+
23
+ comfy-env list-packages
24
+ """
25
+
26
+ import argparse
27
+ import sys
28
+ from pathlib import Path
29
+ from typing import List, Optional
30
+
31
+ from . import __version__
32
+
33
+
34
+ def main(args: Optional[List[str]] = None) -> int:
35
+ """Main entry point for comfy-env CLI."""
36
+ parser = argparse.ArgumentParser(
37
+ prog="comfy-env",
38
+ description="Environment management for ComfyUI custom nodes",
39
+ )
40
+ parser.add_argument(
41
+ "--version", action="version", version=f"comfy-env {__version__}"
42
+ )
43
+
44
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
45
+
46
+ # install command
47
+ install_parser = subparsers.add_parser(
48
+ "install",
49
+ help="Install dependencies from config",
50
+ description="Install CUDA wheels and dependencies from comfyui_env.toml",
51
+ )
52
+ install_parser.add_argument(
53
+ "--config", "-c",
54
+ type=str,
55
+ help="Path to config file (default: auto-discover)",
56
+ )
57
+ install_parser.add_argument(
58
+ "--isolated",
59
+ action="store_true",
60
+ help="Create isolated venv instead of installing in-place",
61
+ )
62
+ install_parser.add_argument(
63
+ "--dry-run",
64
+ action="store_true",
65
+ help="Show what would be installed without installing",
66
+ )
67
+ install_parser.add_argument(
68
+ "--verify",
69
+ action="store_true",
70
+ help="Verify wheel URLs exist before installing",
71
+ )
72
+ install_parser.add_argument(
73
+ "--dir", "-d",
74
+ type=str,
75
+ help="Directory containing the config (default: current directory)",
76
+ )
77
+
78
+ # info command
79
+ info_parser = subparsers.add_parser(
80
+ "info",
81
+ help="Show runtime environment information",
82
+ description="Display detected Python, CUDA, PyTorch, and GPU information",
83
+ )
84
+ info_parser.add_argument(
85
+ "--json",
86
+ action="store_true",
87
+ help="Output as JSON",
88
+ )
89
+
90
+ # resolve command
91
+ resolve_parser = subparsers.add_parser(
92
+ "resolve",
93
+ help="Resolve wheel URLs for packages",
94
+ description="Show resolved wheel URLs without installing",
95
+ )
96
+ resolve_parser.add_argument(
97
+ "packages",
98
+ nargs="*",
99
+ help="Package specs (e.g., nvdiffrast==0.4.0)",
100
+ )
101
+ resolve_parser.add_argument(
102
+ "--all", "-a",
103
+ action="store_true",
104
+ help="Resolve all packages from config",
105
+ )
106
+ resolve_parser.add_argument(
107
+ "--config", "-c",
108
+ type=str,
109
+ help="Path to config file",
110
+ )
111
+ resolve_parser.add_argument(
112
+ "--verify",
113
+ action="store_true",
114
+ help="Verify URLs exist (HTTP HEAD check)",
115
+ )
116
+
117
+ # doctor command
118
+ doctor_parser = subparsers.add_parser(
119
+ "doctor",
120
+ help="Verify installation and diagnose issues",
121
+ description="Check if packages are properly installed and importable",
122
+ )
123
+ doctor_parser.add_argument(
124
+ "--package", "-p",
125
+ type=str,
126
+ help="Check specific package",
127
+ )
128
+ doctor_parser.add_argument(
129
+ "--config", "-c",
130
+ type=str,
131
+ help="Path to config file",
132
+ )
133
+
134
+ # list-packages command
135
+ list_parser = subparsers.add_parser(
136
+ "list-packages",
137
+ help="Show all packages in the built-in registry",
138
+ description="List CUDA packages that comfy-env knows how to install",
139
+ )
140
+ list_parser.add_argument(
141
+ "--json",
142
+ action="store_true",
143
+ help="Output as JSON",
144
+ )
145
+
146
+ parsed = parser.parse_args(args)
147
+
148
+ if parsed.command is None:
149
+ parser.print_help()
150
+ return 0
151
+
152
+ try:
153
+ if parsed.command == "install":
154
+ return cmd_install(parsed)
155
+ elif parsed.command == "info":
156
+ return cmd_info(parsed)
157
+ elif parsed.command == "resolve":
158
+ return cmd_resolve(parsed)
159
+ elif parsed.command == "doctor":
160
+ return cmd_doctor(parsed)
161
+ elif parsed.command == "list-packages":
162
+ return cmd_list_packages(parsed)
163
+ else:
164
+ parser.print_help()
165
+ return 1
166
+ except KeyboardInterrupt:
167
+ print("\nInterrupted")
168
+ return 130
169
+ except Exception as e:
170
+ print(f"Error: {e}", file=sys.stderr)
171
+ return 1
172
+
173
+
174
+ def cmd_install(args) -> int:
175
+ """Handle install command."""
176
+ from .install import install
177
+
178
+ mode = "isolated" if args.isolated else "inplace"
179
+ node_dir = Path(args.dir) if args.dir else Path.cwd()
180
+
181
+ try:
182
+ install(
183
+ config=args.config,
184
+ mode=mode,
185
+ node_dir=node_dir,
186
+ dry_run=args.dry_run,
187
+ verify_wheels=args.verify,
188
+ )
189
+ return 0
190
+ except FileNotFoundError as e:
191
+ print(f"Error: {e}", file=sys.stderr)
192
+ return 1
193
+ except Exception as e:
194
+ print(f"Installation failed: {e}", file=sys.stderr)
195
+ return 1
196
+
197
+
198
+ def cmd_info(args) -> int:
199
+ """Handle info command."""
200
+ from .resolver import RuntimeEnv
201
+
202
+ env = RuntimeEnv.detect()
203
+
204
+ if args.json:
205
+ import json
206
+ print(json.dumps(env.as_dict(), indent=2))
207
+ return 0
208
+
209
+ print("Runtime Environment")
210
+ print("=" * 40)
211
+ print(f" OS: {env.os_name}")
212
+ print(f" Platform: {env.platform_tag}")
213
+ print(f" Python: {env.python_version}")
214
+
215
+ if env.cuda_version:
216
+ print(f" CUDA: {env.cuda_version}")
217
+ else:
218
+ print(" CUDA: Not detected")
219
+
220
+ if env.torch_version:
221
+ print(f" PyTorch: {env.torch_version}")
222
+ else:
223
+ print(" PyTorch: Not installed")
224
+
225
+ if env.gpu_name:
226
+ print(f" GPU: {env.gpu_name}")
227
+ if env.gpu_compute:
228
+ print(f" Compute: {env.gpu_compute}")
229
+
230
+ print()
231
+ return 0
232
+
233
+
234
+ def cmd_resolve(args) -> int:
235
+ """Handle resolve command."""
236
+ from .resolver import RuntimeEnv, WheelResolver, parse_wheel_requirement
237
+ from .env.config_file import discover_env_config, load_env_from_file
238
+
239
+ env = RuntimeEnv.detect()
240
+ resolver = WheelResolver()
241
+
242
+ packages = []
243
+
244
+ # Get packages from args or config
245
+ if args.all or (not args.packages and args.config):
246
+ # Load from config
247
+ if args.config:
248
+ config = load_env_from_file(Path(args.config))
249
+ else:
250
+ config = discover_env_config(Path.cwd())
251
+
252
+ if config and config.no_deps_requirements:
253
+ packages = config.no_deps_requirements
254
+ else:
255
+ print("No CUDA packages found in config", file=sys.stderr)
256
+ return 1
257
+ elif args.packages:
258
+ packages = args.packages
259
+ else:
260
+ print("Specify packages or use --all with a config file", file=sys.stderr)
261
+ return 1
262
+
263
+ print(f"Resolving wheels for: {env}")
264
+ print("=" * 60)
265
+
266
+ all_ok = True
267
+ for pkg_spec in packages:
268
+ package, version = parse_wheel_requirement(pkg_spec)
269
+ if version is None:
270
+ print(f" {package}: No version specified, skipping")
271
+ continue
272
+
273
+ try:
274
+ url = resolver.resolve(package, version, env, verify=args.verify)
275
+ status = "OK" if args.verify else "resolved"
276
+ print(f" {package}=={version}: {status}")
277
+ print(f" {url}")
278
+ except Exception as e:
279
+ print(f" {package}=={version}: FAILED")
280
+ print(f" {e}")
281
+ all_ok = False
282
+
283
+ return 0 if all_ok else 1
284
+
285
+
286
+ def cmd_doctor(args) -> int:
287
+ """Handle doctor command."""
288
+ from .install import verify_installation
289
+ from .env.config_file import discover_env_config, load_env_from_file
290
+
291
+ print("Running diagnostics...")
292
+ print("=" * 40)
293
+
294
+ # Check environment
295
+ print("\n1. Environment")
296
+ cmd_info(argparse.Namespace(json=False))
297
+
298
+ # Check packages
299
+ print("2. Package Verification")
300
+
301
+ packages = []
302
+ if args.package:
303
+ packages = [args.package]
304
+ elif args.config:
305
+ config = load_env_from_file(Path(args.config))
306
+ if config:
307
+ packages = (config.requirements or []) + (config.no_deps_requirements or [])
308
+ else:
309
+ config = discover_env_config(Path.cwd())
310
+ if config:
311
+ packages = (config.requirements or []) + (config.no_deps_requirements or [])
312
+
313
+ if packages:
314
+ # Extract package names from specs
315
+ pkg_names = []
316
+ for pkg in packages:
317
+ name = pkg.split("==")[0].split(">=")[0].split("[")[0]
318
+ pkg_names.append(name)
319
+
320
+ all_ok = verify_installation(pkg_names)
321
+ if all_ok:
322
+ print("\nAll packages verified!")
323
+ return 0
324
+ else:
325
+ print("\nSome packages failed verification.")
326
+ return 1
327
+ else:
328
+ print(" No packages to verify (no config found)")
329
+ return 0
330
+
331
+
332
+ def cmd_list_packages(args) -> int:
333
+ """Handle list-packages command."""
334
+ from .registry import PACKAGE_REGISTRY, list_packages
335
+
336
+ if args.json:
337
+ import json
338
+ result = {}
339
+ for name, config in PACKAGE_REGISTRY.items():
340
+ result[name] = {
341
+ "method": config["method"],
342
+ "description": config.get("description", ""),
343
+ }
344
+ if "index_url" in config:
345
+ result[name]["index_url"] = config["index_url"]
346
+ if "package_template" in config:
347
+ result[name]["package_template"] = config["package_template"]
348
+ print(json.dumps(result, indent=2))
349
+ return 0
350
+
351
+ print("Built-in CUDA Package Registry")
352
+ print("=" * 60)
353
+ print()
354
+ print("These packages can be installed without specifying wheel_sources.")
355
+ print("Just add them to your comfyui_env.toml:")
356
+ print()
357
+ print(" [cuda]")
358
+ print(" torch-scatter = \"2.1.2\"")
359
+ print(" torch-cluster = \"1.6.3\"")
360
+ print()
361
+ print("-" * 60)
362
+
363
+ # Group packages by method
364
+ by_method = {}
365
+ for name, config in PACKAGE_REGISTRY.items():
366
+ method = config["method"]
367
+ if method not in by_method:
368
+ by_method[method] = []
369
+ by_method[method].append((name, config))
370
+
371
+ method_labels = {
372
+ "index": "PEP 503 Index (pip --extra-index-url)",
373
+ "github_index": "GitHub Pages (pip --find-links)",
374
+ "pypi_variant": "PyPI with CUDA variant names",
375
+ }
376
+
377
+ for method, packages in by_method.items():
378
+ print(f"\n{method_labels.get(method, method)}:")
379
+ for name, config in sorted(packages):
380
+ desc = config.get("description", "")
381
+ print(f" {name:20} - {desc}")
382
+
383
+ print()
384
+ return 0
385
+
386
+
387
+ if __name__ == "__main__":
388
+ sys.exit(main())