comfy-env 0.1.14__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 (51) hide show
  1. comfy_env/__init__.py +115 -62
  2. comfy_env/cli.py +89 -319
  3. comfy_env/config/__init__.py +18 -8
  4. comfy_env/config/parser.py +21 -122
  5. comfy_env/config/types.py +37 -70
  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 +163 -249
  17. comfy_env/isolation/__init__.py +33 -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 +2 -2
  22. comfy_env/isolation/wrap.py +149 -409
  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 -331
  32. comfy_env/errors.py +0 -293
  33. comfy_env/nodes.py +0 -187
  34. comfy_env/pixi/__init__.py +0 -48
  35. comfy_env/pixi/core.py +0 -588
  36. comfy_env/pixi/cuda_detection.py +0 -303
  37. comfy_env/pixi/platform/__init__.py +0 -21
  38. comfy_env/pixi/platform/base.py +0 -96
  39. comfy_env/pixi/platform/darwin.py +0 -53
  40. comfy_env/pixi/platform/linux.py +0 -68
  41. comfy_env/pixi/platform/windows.py +0 -284
  42. comfy_env/pixi/resolver.py +0 -198
  43. comfy_env/prestartup.py +0 -192
  44. comfy_env/workers/__init__.py +0 -38
  45. comfy_env/workers/tensor_utils.py +0 -188
  46. comfy_env-0.1.14.dist-info/METADATA +0 -291
  47. comfy_env-0.1.14.dist-info/RECORD +0 -33
  48. /comfy_env/{workers → isolation/workers}/base.py +0 -0
  49. {comfy_env-0.1.14.dist-info → comfy_env-0.1.16.dist-info}/WHEEL +0 -0
  50. {comfy_env-0.1.14.dist-info → comfy_env-0.1.16.dist-info}/entry_points.txt +0 -0
  51. {comfy_env-0.1.14.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,151 +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 discover_env_config, load_env_from_file
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_env_from_file(Path(args.config))
329
- if config:
330
- packages = (config.requirements or []) + (config.no_deps_requirements or [])
331
156
  else:
332
- config = discover_env_config(Path.cwd())
333
- if config:
334
- packages = (config.requirements or []) + (config.no_deps_requirements or [])
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
335
160
 
336
161
  if packages:
337
- pkg_names = []
338
- for pkg in packages:
339
- name = pkg.split("==")[0].split(">=")[0].split("[")[0]
340
- pkg_names.append(name)
341
-
342
- all_ok = verify_installation(pkg_names)
343
- if all_ok:
344
- print("\nAll packages verified!")
345
- return 0
346
- else:
347
- print("\nSome packages failed verification.")
348
- return 1
349
- else:
350
- print(" No packages to verify (no config found)")
351
- return 0
162
+ return 0 if verify_installation(packages) else 1
163
+ print(" No packages to verify")
164
+ return 0
352
165
 
353
166
 
354
167
  def cmd_apt_install(args) -> int:
355
- """Handle apt-install command - install system packages from [apt] section."""
356
- import os
357
- import shutil
358
- import subprocess
359
- import platform
360
-
168
+ import os, shutil, subprocess, platform
361
169
  if platform.system() != "Linux":
362
- print("apt-install is only supported on Linux", file=sys.stderr)
170
+ print("apt-install: Linux only", file=sys.stderr)
363
171
  return 1
364
172
 
365
- # Find config
366
- if args.config:
367
- config_path = Path(args.config).resolve()
368
- else:
369
- config_path = Path.cwd() / "comfy-env.toml"
370
-
173
+ config_path = Path(args.config).resolve() if args.config else Path.cwd() / "comfy-env.toml"
371
174
  if not config_path.exists():
372
- print(f"Config file not found: {config_path}", file=sys.stderr)
175
+ print(f"Not found: {config_path}", file=sys.stderr)
373
176
  return 1
374
177
 
375
- # Parse config to get apt packages
376
178
  from .config.parser import load_config
377
- config = load_config(config_path)
378
-
379
- if not config.apt_packages:
380
- 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")
381
182
  return 0
382
183
 
383
- packages = config.apt_packages
384
- print(f"Found {len(packages)} apt package(s) to install:")
385
- for pkg in packages:
386
- print(f" - {pkg}")
387
-
388
- # Determine if we need sudo
389
- is_root = os.geteuid() == 0
390
- has_sudo = shutil.which("sudo") is not None
391
- use_sudo = not is_root and has_sudo
392
-
184
+ print(f"Packages: {', '.join(packages)}")
185
+ use_sudo = os.geteuid() != 0 and shutil.which("sudo")
393
186
  prefix = ["sudo"] if use_sudo else []
394
187
 
395
188
  if args.dry_run:
396
- print("\n[Dry run] Would run:")
397
- prefix_str = "sudo " if use_sudo else ""
398
- 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)}")
399
190
  return 0
400
191
 
401
- if not is_root and not has_sudo:
402
- print("\nError: Need root privileges to install apt packages.", file=sys.stderr)
403
- print("Run manually with:", file=sys.stderr)
404
- 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)
405
194
  return 1
406
195
 
407
- # Run apt-get update
408
- print("\nUpdating package lists...")
409
- result = subprocess.run(
410
- prefix + ["apt-get", "update"],
411
- capture_output=False,
412
- )
413
- if result.returncode != 0:
414
- print("Warning: apt-get update failed, continuing anyway...")
415
-
416
- # Run apt-get install
417
- print(f"\nInstalling: {' '.join(packages)}")
418
- result = subprocess.run(
419
- prefix + ["apt-get", "install", "-y"] + packages,
420
- capture_output=False,
421
- )
422
-
423
- if result.returncode == 0:
424
- print("\nSystem packages installed successfully!")
425
- return 0
426
- else:
427
- print("\nFailed to install some packages", file=sys.stderr)
428
- 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
429
199
 
430
200
 
431
201
  if __name__ == "__main__":
@@ -1,19 +1,29 @@
1
1
  """
2
- Config parsing for comfy-env.
2
+ Config layer - Configuration parsing and types.
3
3
 
4
- This module handles parsing comfy-env.toml files and provides
5
- typed configuration objects.
4
+ Pure parsing, no side effects.
6
5
  """
7
6
 
8
- from .types import ComfyEnvConfig, NodeReq
9
- from .parser import load_config, discover_config, CONFIG_FILE_NAME
7
+ from .types import (
8
+ ComfyEnvConfig,
9
+ NodeDependency,
10
+ NodeReq, # Backwards compatibility alias
11
+ )
12
+ from .parser import (
13
+ CONFIG_FILE_NAME,
14
+ load_config,
15
+ discover_config,
16
+ parse_config,
17
+ )
10
18
 
11
19
  __all__ = [
12
20
  # Types
13
21
  "ComfyEnvConfig",
14
- "NodeReq",
15
- # Parser
22
+ "NodeDependency",
23
+ "NodeReq", # Backwards compatibility
24
+ # Parsing
25
+ "CONFIG_FILE_NAME",
16
26
  "load_config",
17
27
  "discover_config",
18
- "CONFIG_FILE_NAME",
28
+ "parse_config",
19
29
  ]