comfy-env 0.1.15__py3-none-any.whl → 0.1.16__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 (50) hide show
  1. comfy_env/__init__.py +116 -41
  2. comfy_env/cli.py +89 -317
  3. comfy_env/config/__init__.py +18 -6
  4. comfy_env/config/parser.py +22 -76
  5. comfy_env/config/types.py +37 -0
  6. comfy_env/detection/__init__.py +77 -0
  7. comfy_env/detection/cuda.py +61 -0
  8. comfy_env/detection/gpu.py +230 -0
  9. comfy_env/detection/platform.py +70 -0
  10. comfy_env/detection/runtime.py +103 -0
  11. comfy_env/environment/__init__.py +53 -0
  12. comfy_env/environment/cache.py +141 -0
  13. comfy_env/environment/libomp.py +41 -0
  14. comfy_env/environment/paths.py +38 -0
  15. comfy_env/environment/setup.py +88 -0
  16. comfy_env/install.py +127 -329
  17. comfy_env/isolation/__init__.py +32 -2
  18. comfy_env/isolation/tensor_utils.py +83 -0
  19. comfy_env/isolation/workers/__init__.py +16 -0
  20. comfy_env/{workers → isolation/workers}/mp.py +1 -1
  21. comfy_env/{workers → isolation/workers}/subprocess.py +1 -1
  22. comfy_env/isolation/wrap.py +128 -509
  23. comfy_env/packages/__init__.py +60 -0
  24. comfy_env/packages/apt.py +36 -0
  25. comfy_env/packages/cuda_wheels.py +97 -0
  26. comfy_env/packages/node_dependencies.py +77 -0
  27. comfy_env/packages/pixi.py +85 -0
  28. comfy_env/packages/toml_generator.py +88 -0
  29. comfy_env-0.1.16.dist-info/METADATA +279 -0
  30. comfy_env-0.1.16.dist-info/RECORD +36 -0
  31. comfy_env/cache.py +0 -203
  32. comfy_env/nodes.py +0 -187
  33. comfy_env/pixi/__init__.py +0 -48
  34. comfy_env/pixi/core.py +0 -587
  35. comfy_env/pixi/cuda_detection.py +0 -303
  36. comfy_env/pixi/platform/__init__.py +0 -21
  37. comfy_env/pixi/platform/base.py +0 -96
  38. comfy_env/pixi/platform/darwin.py +0 -53
  39. comfy_env/pixi/platform/linux.py +0 -68
  40. comfy_env/pixi/platform/windows.py +0 -284
  41. comfy_env/pixi/resolver.py +0 -198
  42. comfy_env/prestartup.py +0 -208
  43. comfy_env/workers/__init__.py +0 -38
  44. comfy_env/workers/tensor_utils.py +0 -188
  45. comfy_env-0.1.15.dist-info/METADATA +0 -291
  46. comfy_env-0.1.15.dist-info/RECORD +0 -31
  47. /comfy_env/{workers → isolation/workers}/base.py +0 -0
  48. {comfy_env-0.1.15.dist-info → comfy_env-0.1.16.dist-info}/WHEEL +0 -0
  49. {comfy_env-0.1.15.dist-info → comfy_env-0.1.16.dist-info}/entry_points.txt +0 -0
  50. {comfy_env-0.1.15.dist-info → comfy_env-0.1.16.dist-info}/licenses/LICENSE +0 -0
comfy_env/cli.py CHANGED
@@ -1,23 +1,4 @@
1
- """
2
- CLI for comfy-env.
3
-
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
7
- - install: Install dependencies from config
8
- - info: Show runtime environment information
9
- - doctor: Verify installation
10
-
11
- Usage:
12
- comfy-env init ---> creates template comfy-env.toml
13
- comfy-env generate nodes/cgal/comfy-env.toml ---> nodes/cgal/pixi.toml
14
- comfy-env install ---> installs from comfy
15
- comfy-env install --dry-run
16
-
17
- comfy-env info
18
-
19
- comfy-env doctor
20
- """
1
+ """CLI for comfy-env."""
21
2
 
22
3
  import argparse
23
4
  import sys
@@ -28,136 +9,51 @@ from . import __version__
28
9
 
29
10
 
30
11
  def main(args: Optional[List[str]] = None) -> int:
31
- """Main entry point for comfy-env CLI."""
32
- parser = argparse.ArgumentParser(
33
- prog="comfy-env",
34
- description="Environment management for ComfyUI custom nodes",
35
- )
36
- parser.add_argument(
37
- "--version", action="version", version=f"comfy-env {__version__}"
38
- )
39
-
40
- subparsers = parser.add_subparsers(dest="command", help="Available commands")
41
-
42
- # init command
43
- init_parser = subparsers.add_parser(
44
- "init",
45
- help="Create a default comfy-env.toml config file",
46
- description="Initialize a new comfy-env.toml configuration file in the current directory",
47
- )
48
- init_parser.add_argument(
49
- "--force", "-f",
50
- action="store_true",
51
- help="Overwrite existing config file",
52
- )
53
-
54
- # generate command
55
- generate_parser = subparsers.add_parser(
56
- "generate",
57
- help="Generate pixi.toml from comfy-env.toml",
58
- description="Parse comfy-env.toml and generate a pixi.toml in the same directory",
59
- )
60
- generate_parser.add_argument(
61
- "config",
62
- type=str,
63
- help="Path to comfy-env.toml",
64
- )
65
- generate_parser.add_argument(
66
- "--force", "-f",
67
- action="store_true",
68
- help="Overwrite existing pixi.toml",
69
- )
70
-
71
- # install command
72
- install_parser = subparsers.add_parser(
73
- "install",
74
- help="Install dependencies from config",
75
- description="Install CUDA wheels and dependencies from comfy-env.toml",
76
- )
77
- install_parser.add_argument(
78
- "--config", "-c",
79
- type=str,
80
- help="Path to config file (default: auto-discover)",
81
- )
82
- install_parser.add_argument(
83
- "--dry-run",
84
- action="store_true",
85
- help="Show what would be installed without installing",
86
- )
87
- install_parser.add_argument(
88
- "--dir", "-d",
89
- type=str,
90
- help="Directory containing the config (default: current directory)",
91
- )
92
-
93
- # info command
94
- info_parser = subparsers.add_parser(
95
- "info",
96
- help="Show runtime environment information",
97
- description="Display detected Python, CUDA, PyTorch, and GPU information",
98
- )
99
- info_parser.add_argument(
100
- "--json",
101
- action="store_true",
102
- help="Output as JSON",
103
- )
104
-
105
- # doctor command
106
- doctor_parser = subparsers.add_parser(
107
- "doctor",
108
- help="Verify installation and diagnose issues",
109
- description="Check if packages are properly installed and importable",
110
- )
111
- doctor_parser.add_argument(
112
- "--package", "-p",
113
- type=str,
114
- help="Check specific package",
115
- )
116
- doctor_parser.add_argument(
117
- "--config", "-c",
118
- type=str,
119
- help="Path to config file",
120
- )
121
-
122
- # apt-install command
123
- apt_parser = subparsers.add_parser(
124
- "apt-install",
125
- help="Install system packages from [apt] section (Linux only)",
126
- description="Read [apt] packages from comfy-env.toml and install via apt-get",
127
- )
128
- apt_parser.add_argument(
129
- "--config", "-c",
130
- type=str,
131
- help="Path to comfy-env.toml (default: auto-discover)",
132
- )
133
- apt_parser.add_argument(
134
- "--dry-run",
135
- action="store_true",
136
- help="Show what would be installed without installing",
137
- )
12
+ parser = argparse.ArgumentParser(prog="comfy-env", description="Environment management for ComfyUI")
13
+ parser.add_argument("--version", action="version", version=f"comfy-env {__version__}")
14
+ sub = parser.add_subparsers(dest="command", help="Commands")
15
+
16
+ # init
17
+ p = sub.add_parser("init", help="Create comfy-env.toml")
18
+ p.add_argument("--force", "-f", action="store_true", help="Overwrite existing")
19
+
20
+ # generate
21
+ p = sub.add_parser("generate", help="Generate pixi.toml from comfy-env.toml")
22
+ p.add_argument("config", type=str, help="Path to comfy-env.toml")
23
+ p.add_argument("--force", "-f", action="store_true", help="Overwrite existing")
24
+
25
+ # install
26
+ p = sub.add_parser("install", help="Install dependencies")
27
+ p.add_argument("--config", "-c", type=str, help="Config path")
28
+ p.add_argument("--dry-run", action="store_true", help="Preview only")
29
+ p.add_argument("--dir", "-d", type=str, help="Node directory")
30
+
31
+ # info
32
+ p = sub.add_parser("info", help="Show runtime info")
33
+ p.add_argument("--json", action="store_true", help="JSON output")
34
+
35
+ # doctor
36
+ p = sub.add_parser("doctor", help="Verify installation")
37
+ p.add_argument("--package", "-p", type=str, help="Check specific package")
38
+ p.add_argument("--config", "-c", type=str, help="Config path")
39
+
40
+ # apt-install
41
+ p = sub.add_parser("apt-install", help="Install apt packages (Linux)")
42
+ p.add_argument("--config", "-c", type=str, help="Config path")
43
+ p.add_argument("--dry-run", action="store_true", help="Preview only")
138
44
 
139
45
  parsed = parser.parse_args(args)
140
-
141
- if parsed.command is None:
46
+ if not parsed.command:
142
47
  parser.print_help()
143
48
  return 0
144
49
 
50
+ commands = {
51
+ "init": cmd_init, "generate": cmd_generate, "install": cmd_install,
52
+ "info": cmd_info, "doctor": cmd_doctor, "apt-install": cmd_apt_install,
53
+ }
54
+
145
55
  try:
146
- if parsed.command == "init":
147
- return cmd_init(parsed)
148
- elif parsed.command == "generate":
149
- return cmd_generate(parsed)
150
- elif parsed.command == "install":
151
- return cmd_install(parsed)
152
- elif parsed.command == "info":
153
- return cmd_info(parsed)
154
- elif parsed.command == "doctor":
155
- return cmd_doctor(parsed)
156
- elif parsed.command == "apt-install":
157
- return cmd_apt_install(parsed)
158
- else:
159
- parser.print_help()
160
- return 1
56
+ return commands.get(parsed.command, lambda _: 1)(parsed)
161
57
  except KeyboardInterrupt:
162
58
  print("\nInterrupted")
163
59
  return 130
@@ -167,113 +63,64 @@ def main(args: Optional[List[str]] = None) -> int:
167
63
 
168
64
 
169
65
  DEFAULT_CONFIG = """\
170
- # comfy-env.toml - Environment configuration for ComfyUI custom nodes
171
- # Documentation: https://github.com/PozzettiAndrea/comfy-env
172
-
173
- [system]
174
- # System packages required (apt on Linux, brew on macOS)
175
- linux = []
66
+ # comfy-env.toml
67
+ [cuda]
68
+ packages = []
176
69
 
177
- [environment]
178
- python = "3.11"
179
- cuda_version = "auto"
180
- pytorch_version = "auto"
181
-
182
- [environment.cuda]
183
- # CUDA packages from https://pozzettiandrea.github.io/cuda-wheels/
184
- # Example: nvdiffrast = "0.4.0"
185
-
186
- [environment.packages]
187
- requirements = []
70
+ [pypi-dependencies]
71
+ # example = "*"
188
72
  """
189
73
 
190
74
 
191
75
  def cmd_init(args) -> int:
192
- """Handle init command."""
193
76
  config_path = Path.cwd() / "comfy-env.toml"
194
-
195
77
  if config_path.exists() and not args.force:
196
- print(f"Config file already exists: {config_path}", file=sys.stderr)
197
- print("Use --force to overwrite", file=sys.stderr)
78
+ print(f"Already exists: {config_path}\nUse --force to overwrite", file=sys.stderr)
198
79
  return 1
199
-
200
80
  config_path.write_text(DEFAULT_CONFIG)
201
81
  print(f"Created {config_path}")
202
82
  return 0
203
83
 
204
84
 
205
85
  def cmd_generate(args) -> int:
206
- """Handle generate command - create pixi.toml from comfy-env.toml."""
207
- from .config.parser import load_config
208
- from .pixi import create_pixi_toml
86
+ from .config import load_config
87
+ from .packages.toml_generator import write_pixi_toml
209
88
 
210
89
  config_path = Path(args.config).resolve()
211
-
212
90
  if not config_path.exists():
213
- print(f"Config file not found: {config_path}", file=sys.stderr)
91
+ print(f"Not found: {config_path}", file=sys.stderr)
214
92
  return 1
215
93
 
216
- if config_path.name != "comfy-env.toml":
217
- print(f"Warning: Expected comfy-env.toml, got {config_path.name}", file=sys.stderr)
218
-
219
94
  node_dir = config_path.parent
220
95
  pixi_path = node_dir / "pixi.toml"
221
-
222
96
  if pixi_path.exists() and not args.force:
223
- print(f"pixi.toml already exists: {pixi_path}", file=sys.stderr)
224
- print("Use --force to overwrite", file=sys.stderr)
97
+ print(f"Already exists: {pixi_path}\nUse --force to overwrite", file=sys.stderr)
225
98
  return 1
226
99
 
227
- # Load the config
228
100
  config = load_config(config_path)
229
- if not config or not config.envs:
230
- print(f"No environments found in {config_path}", file=sys.stderr)
101
+ if not config:
102
+ print(f"Failed to load: {config_path}", file=sys.stderr)
231
103
  return 1
232
104
 
233
- # Use the first environment
234
- env_name = next(iter(config.envs.keys()))
235
- env_config = config.envs[env_name]
236
-
237
105
  print(f"Generating pixi.toml from {config_path}")
238
- print(f" Environment: {env_name}")
239
- print(f" Python: {env_config.python}")
240
-
241
- # Generate pixi.toml
242
- result_path = create_pixi_toml(env_config, node_dir)
243
-
244
- print(f"Created {result_path}")
245
- print()
246
- print("Next steps:")
247
- print(f" cd {node_dir}")
248
- print(" pixi install")
106
+ write_pixi_toml(config, node_dir)
107
+ print(f"Created {pixi_path}\nNext: cd {node_dir} && pixi install")
249
108
  return 0
250
109
 
251
110
 
252
111
  def cmd_install(args) -> int:
253
- """Handle install command."""
254
112
  from .install import install
255
-
256
113
  node_dir = Path(args.dir) if args.dir else Path.cwd()
257
-
258
114
  try:
259
- install(
260
- config=args.config,
261
- node_dir=node_dir,
262
- dry_run=args.dry_run,
263
- )
115
+ install(config=args.config, node_dir=node_dir, dry_run=args.dry_run)
264
116
  return 0
265
- except FileNotFoundError as e:
266
- print(f"Error: {e}", file=sys.stderr)
267
- return 1
268
117
  except Exception as e:
269
- print(f"Installation failed: {e}", file=sys.stderr)
118
+ print(f"Failed: {e}", file=sys.stderr)
270
119
  return 1
271
120
 
272
121
 
273
122
  def cmd_info(args) -> int:
274
- """Handle info command."""
275
- from .pixi import RuntimeEnv
276
-
123
+ from .detection import RuntimeEnv
277
124
  env = RuntimeEnv.detect()
278
125
 
279
126
  if args.json:
@@ -281,149 +128,74 @@ def cmd_info(args) -> int:
281
128
  print(json.dumps(env.as_dict(), indent=2))
282
129
  return 0
283
130
 
284
- print("Runtime Environment")
285
- print("=" * 40)
286
- print(f" OS: {env.os_name}")
287
- print(f" Platform: {env.platform_tag}")
288
- print(f" Python: {env.python_version}")
289
-
290
- if env.cuda_version:
291
- print(f" CUDA: {env.cuda_version}")
292
- else:
293
- print(" CUDA: Not detected")
294
-
295
- if env.torch_version:
296
- print(f" PyTorch: {env.torch_version}")
297
- else:
298
- print(" PyTorch: Not installed")
299
-
131
+ print("Runtime Environment\n" + "=" * 40)
132
+ print(f" OS: {env.os_name}")
133
+ print(f" Platform: {env.platform_tag}")
134
+ print(f" Python: {env.python_version}")
135
+ print(f" CUDA: {env.cuda_version or 'Not detected'}")
136
+ print(f" PyTorch: {env.torch_version or 'Not installed'}")
300
137
  if env.gpu_name:
301
- print(f" GPU: {env.gpu_name}")
302
- if env.gpu_compute:
303
- print(f" Compute: {env.gpu_compute}")
304
-
138
+ print(f" GPU: {env.gpu_name}")
139
+ if env.gpu_compute: print(f" Compute: {env.gpu_compute}")
305
140
  print()
306
141
  return 0
307
142
 
308
143
 
309
144
  def cmd_doctor(args) -> int:
310
- """Handle doctor command."""
311
145
  from .install import verify_installation
312
- from .config.parser import load_config, discover_config
146
+ from .config import load_config, discover_config
313
147
 
314
- print("Running diagnostics...")
315
- print("=" * 40)
316
-
317
- # Check environment
148
+ print("Diagnostics\n" + "=" * 40)
318
149
  print("\n1. Environment")
319
150
  cmd_info(argparse.Namespace(json=False))
320
151
 
321
- # Check packages
322
- print("2. Package Verification")
323
-
152
+ print("2. Packages")
324
153
  packages = []
325
154
  if args.package:
326
155
  packages = [args.package]
327
- elif args.config:
328
- config = load_config(Path(args.config))
329
- if config:
330
- # Get packages from pypi-dependencies
331
- pypi_deps = config.pixi_passthrough.get("pypi-dependencies", {})
332
- packages = list(pypi_deps.keys()) + config.cuda_packages
333
156
  else:
334
- config = discover_config(Path.cwd())
335
- if config:
336
- pypi_deps = config.pixi_passthrough.get("pypi-dependencies", {})
337
- packages = list(pypi_deps.keys()) + config.cuda_packages
157
+ cfg = load_config(Path(args.config)) if args.config else discover_config(Path.cwd())
158
+ if cfg:
159
+ packages = list(cfg.pixi_passthrough.get("pypi-dependencies", {}).keys()) + cfg.cuda_packages
338
160
 
339
161
  if packages:
340
- all_ok = verify_installation(packages)
341
- if all_ok:
342
- print("\nAll packages verified!")
343
- return 0
344
- else:
345
- print("\nSome packages failed verification.")
346
- return 1
347
- else:
348
- print(" No packages to verify (no config found)")
349
- return 0
162
+ return 0 if verify_installation(packages) else 1
163
+ print(" No packages to verify")
164
+ return 0
350
165
 
351
166
 
352
167
  def cmd_apt_install(args) -> int:
353
- """Handle apt-install command - install system packages from [apt] section."""
354
- import os
355
- import shutil
356
- import subprocess
357
- import platform
358
-
168
+ import os, shutil, subprocess, platform
359
169
  if platform.system() != "Linux":
360
- print("apt-install is only supported on Linux", file=sys.stderr)
170
+ print("apt-install: Linux only", file=sys.stderr)
361
171
  return 1
362
172
 
363
- # Find config
364
- if args.config:
365
- config_path = Path(args.config).resolve()
366
- else:
367
- config_path = Path.cwd() / "comfy-env.toml"
368
-
173
+ config_path = Path(args.config).resolve() if args.config else Path.cwd() / "comfy-env.toml"
369
174
  if not config_path.exists():
370
- print(f"Config file not found: {config_path}", file=sys.stderr)
175
+ print(f"Not found: {config_path}", file=sys.stderr)
371
176
  return 1
372
177
 
373
- # Parse config to get apt packages
374
178
  from .config.parser import load_config
375
- config = load_config(config_path)
376
-
377
- if not config.apt_packages:
378
- print("No [apt] packages specified in config")
179
+ packages = load_config(config_path).apt_packages
180
+ if not packages:
181
+ print("No apt packages in config")
379
182
  return 0
380
183
 
381
- packages = config.apt_packages
382
- print(f"Found {len(packages)} apt package(s) to install:")
383
- for pkg in packages:
384
- print(f" - {pkg}")
385
-
386
- # Determine if we need sudo
387
- is_root = os.geteuid() == 0
388
- has_sudo = shutil.which("sudo") is not None
389
- use_sudo = not is_root and has_sudo
390
-
184
+ print(f"Packages: {', '.join(packages)}")
185
+ use_sudo = os.geteuid() != 0 and shutil.which("sudo")
391
186
  prefix = ["sudo"] if use_sudo else []
392
187
 
393
188
  if args.dry_run:
394
- print("\n[Dry run] Would run:")
395
- prefix_str = "sudo " if use_sudo else ""
396
- print(f" {prefix_str}apt-get update && {prefix_str}apt-get install -y {' '.join(packages)}")
189
+ print(f"Would run: {'sudo ' if use_sudo else ''}apt-get install -y {' '.join(packages)}")
397
190
  return 0
398
191
 
399
- if not is_root and not has_sudo:
400
- print("\nError: Need root privileges to install apt packages.", file=sys.stderr)
401
- print("Run manually with:", file=sys.stderr)
402
- print(f" sudo apt-get update && sudo apt-get install -y {' '.join(packages)}", file=sys.stderr)
192
+ if os.geteuid() != 0 and not shutil.which("sudo"):
193
+ print("Need root. Run: sudo apt-get install -y " + " ".join(packages), file=sys.stderr)
403
194
  return 1
404
195
 
405
- # Run apt-get update
406
- print("\nUpdating package lists...")
407
- result = subprocess.run(
408
- prefix + ["apt-get", "update"],
409
- capture_output=False,
410
- )
411
- if result.returncode != 0:
412
- print("Warning: apt-get update failed, continuing anyway...")
413
-
414
- # Run apt-get install
415
- print(f"\nInstalling: {' '.join(packages)}")
416
- result = subprocess.run(
417
- prefix + ["apt-get", "install", "-y"] + packages,
418
- capture_output=False,
419
- )
420
-
421
- if result.returncode == 0:
422
- print("\nSystem packages installed successfully!")
423
- return 0
424
- else:
425
- print("\nFailed to install some packages", file=sys.stderr)
426
- return result.returncode
196
+ subprocess.run(prefix + ["apt-get", "update"], capture_output=False)
197
+ result = subprocess.run(prefix + ["apt-get", "install", "-y"] + packages, capture_output=False)
198
+ return result.returncode
427
199
 
428
200
 
429
201
  if __name__ == "__main__":
@@ -1,17 +1,29 @@
1
- """Config parsing for comfy-env."""
1
+ """
2
+ Config layer - Configuration parsing and types.
2
3
 
3
- from .parser import (
4
+ Pure parsing, no side effects.
5
+ """
6
+
7
+ from .types import (
4
8
  ComfyEnvConfig,
5
- NodeReq,
9
+ NodeDependency,
10
+ NodeReq, # Backwards compatibility alias
11
+ )
12
+ from .parser import (
13
+ CONFIG_FILE_NAME,
6
14
  load_config,
7
15
  discover_config,
8
- CONFIG_FILE_NAME,
16
+ parse_config,
9
17
  )
10
18
 
11
19
  __all__ = [
20
+ # Types
12
21
  "ComfyEnvConfig",
13
- "NodeReq",
22
+ "NodeDependency",
23
+ "NodeReq", # Backwards compatibility
24
+ # Parsing
25
+ "CONFIG_FILE_NAME",
14
26
  "load_config",
15
27
  "discover_config",
16
- "CONFIG_FILE_NAME",
28
+ "parse_config",
17
29
  ]