comfy-env 0.0.71__py3-none-any.whl → 0.0.73__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/cli.py CHANGED
@@ -119,6 +119,23 @@ def main(args: Optional[List[str]] = None) -> int:
119
119
  help="Path to config file",
120
120
  )
121
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
+ )
138
+
122
139
  parsed = parser.parse_args(args)
123
140
 
124
141
  if parsed.command is None:
@@ -136,6 +153,8 @@ def main(args: Optional[List[str]] = None) -> int:
136
153
  return cmd_info(parsed)
137
154
  elif parsed.command == "doctor":
138
155
  return cmd_doctor(parsed)
156
+ elif parsed.command == "apt-install":
157
+ return cmd_apt_install(parsed)
139
158
  else:
140
159
  parser.print_help()
141
160
  return 1
@@ -332,5 +351,82 @@ def cmd_doctor(args) -> int:
332
351
  return 0
333
352
 
334
353
 
354
+ 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
+
361
+ if platform.system() != "Linux":
362
+ print("apt-install is only supported on Linux", file=sys.stderr)
363
+ return 1
364
+
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
+
371
+ if not config_path.exists():
372
+ print(f"Config file not found: {config_path}", file=sys.stderr)
373
+ return 1
374
+
375
+ # Parse config to get apt packages
376
+ 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")
381
+ return 0
382
+
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
+
393
+ prefix = ["sudo"] if use_sudo else []
394
+
395
+ 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)}")
399
+ return 0
400
+
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)
405
+ return 1
406
+
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
429
+
430
+
335
431
  if __name__ == "__main__":
336
432
  sys.exit(main())
@@ -49,7 +49,7 @@ from .types import ComfyEnvConfig, NodeReq
49
49
  CONFIG_FILE_NAME = "comfy-env.toml"
50
50
 
51
51
  # Sections we handle specially (not passed through to pixi.toml)
52
- CUSTOM_SECTIONS = {"python", "cuda", "node_reqs", "apt"}
52
+ CUSTOM_SECTIONS = {"python", "cuda", "node_reqs", "apt", "env_vars"}
53
53
 
54
54
 
55
55
  def load_config(path: Path) -> ComfyEnvConfig:
@@ -120,6 +120,10 @@ def _parse_config(data: Dict[str, Any]) -> ComfyEnvConfig:
120
120
  apt_data = data.pop("apt", {})
121
121
  apt_packages = _ensure_list(apt_data.get("packages", []))
122
122
 
123
+ # Extract [env_vars] section
124
+ env_vars_data = data.pop("env_vars", {})
125
+ env_vars = {str(k): str(v) for k, v in env_vars_data.items()}
126
+
123
127
  # Extract [node_reqs] section
124
128
  node_reqs_data = data.pop("node_reqs", {})
125
129
  node_reqs = _parse_node_reqs(node_reqs_data)
@@ -131,6 +135,7 @@ def _parse_config(data: Dict[str, Any]) -> ComfyEnvConfig:
131
135
  python=python_version,
132
136
  cuda_packages=cuda_packages,
133
137
  apt_packages=apt_packages,
138
+ env_vars=env_vars,
134
139
  node_reqs=node_reqs,
135
140
  pixi_passthrough=pixi_passthrough,
136
141
  )
comfy_env/config/types.py CHANGED
@@ -56,6 +56,9 @@ class ComfyEnvConfig:
56
56
  # [apt] - System packages to install via apt (Linux only)
57
57
  apt_packages: List[str] = field(default_factory=list)
58
58
 
59
+ # [env_vars] - Environment variables to set early (in prestartup)
60
+ env_vars: Dict[str, str] = field(default_factory=dict)
61
+
59
62
  # [node_reqs] - other ComfyUI nodes to clone
60
63
  node_reqs: List[NodeReq] = field(default_factory=list)
61
64
 
comfy_env/install.py CHANGED
@@ -55,7 +55,11 @@ def install(
55
55
  "Create comfy-env.toml to define dependencies."
56
56
  )
57
57
 
58
- # Install node dependencies first
58
+ # Install apt packages first (Linux only)
59
+ if cfg.apt_packages:
60
+ _install_apt_packages(cfg.apt_packages, log, dry_run)
61
+
62
+ # Install node dependencies
59
63
  if cfg.node_reqs:
60
64
  _install_node_dependencies(cfg.node_reqs, node_dir, log, dry_run)
61
65
 
@@ -69,6 +73,66 @@ def install(
69
73
  return True
70
74
 
71
75
 
76
+ def _install_apt_packages(
77
+ packages: List[str],
78
+ log: Callable[[str], None],
79
+ dry_run: bool,
80
+ ) -> None:
81
+ """Install apt packages (Linux only)."""
82
+ import os
83
+ import platform
84
+ import shutil
85
+ import subprocess
86
+
87
+ if platform.system() != "Linux":
88
+ log(f"[apt] Skipping apt packages (not Linux)")
89
+ return
90
+
91
+ log(f"\n[apt] Installing {len(packages)} system package(s):")
92
+ for pkg in packages:
93
+ log(f" - {pkg}")
94
+
95
+ if dry_run:
96
+ log(" (dry run - no changes made)")
97
+ return
98
+
99
+ # Determine if we need sudo
100
+ is_root = os.geteuid() == 0
101
+ has_sudo = shutil.which("sudo") is not None
102
+ use_sudo = not is_root and has_sudo
103
+ prefix = ["sudo"] if use_sudo else []
104
+
105
+ if not is_root and not has_sudo:
106
+ log(f"[apt] Warning: No root access. Install manually:")
107
+ log(f" sudo apt-get update && sudo apt-get install -y {' '.join(packages)}")
108
+ return
109
+
110
+ # Run apt-get update (suppress output, just show errors)
111
+ log("[apt] Updating package lists...")
112
+ result = subprocess.run(
113
+ prefix + ["apt-get", "update"],
114
+ capture_output=True,
115
+ text=True,
116
+ )
117
+ if result.returncode != 0:
118
+ log(f"[apt] Warning: apt-get update failed: {result.stderr.strip()}")
119
+
120
+ # Run apt-get install
121
+ log(f"[apt] Installing packages...")
122
+ result = subprocess.run(
123
+ prefix + ["apt-get", "install", "-y"] + packages,
124
+ capture_output=True,
125
+ text=True,
126
+ )
127
+
128
+ if result.returncode == 0:
129
+ log("[apt] System packages installed successfully!")
130
+ else:
131
+ log(f"[apt] Warning: Installation failed: {result.stderr.strip()}")
132
+ log(f"[apt] You may need to install manually:")
133
+ log(f" sudo apt-get install -y {' '.join(packages)}")
134
+
135
+
72
136
  def _install_node_dependencies(
73
137
  node_reqs: List[NodeReq],
74
138
  node_dir: Path,
comfy_env/prestartup.py CHANGED
@@ -8,7 +8,35 @@ import glob
8
8
  import os
9
9
  import sys
10
10
  from pathlib import Path
11
- from typing import Optional
11
+ from typing import Optional, Dict
12
+
13
+
14
+ def _load_env_vars(config_path: str) -> Dict[str, str]:
15
+ """
16
+ Load [env_vars] section from comfy-env.toml.
17
+
18
+ Uses tomllib (Python 3.11+) or tomli fallback.
19
+ Returns empty dict if file not found or parsing fails.
20
+ """
21
+ if not os.path.exists(config_path):
22
+ return {}
23
+
24
+ try:
25
+ if sys.version_info >= (3, 11):
26
+ import tomllib
27
+ else:
28
+ try:
29
+ import tomli as tomllib
30
+ except ImportError:
31
+ return {}
32
+
33
+ with open(config_path, "rb") as f:
34
+ data = tomllib.load(f)
35
+
36
+ env_vars_data = data.get("env_vars", {})
37
+ return {str(k): str(v) for k, v in env_vars_data.items()}
38
+ except Exception:
39
+ return {}
12
40
 
13
41
 
14
42
  def setup_env(node_dir: Optional[str] = None) -> None:
@@ -16,8 +44,9 @@ def setup_env(node_dir: Optional[str] = None) -> None:
16
44
  Set up environment for pixi conda libraries.
17
45
 
18
46
  Call this in prestartup_script.py before any native library imports.
19
- Sets LD_LIBRARY_PATH (Linux/Mac) or PATH (Windows) for conda libs,
20
- and adds pixi site-packages to sys.path.
47
+ - Applies [env_vars] from comfy-env.toml first (for OpenMP settings, etc.)
48
+ - Sets LD_LIBRARY_PATH (Linux/Mac) or PATH (Windows) for conda libs
49
+ - Adds pixi site-packages to sys.path
21
50
 
22
51
  Args:
23
52
  node_dir: Path to the custom node directory. Auto-detected if not provided.
@@ -33,6 +62,12 @@ def setup_env(node_dir: Optional[str] = None) -> None:
33
62
  frame = inspect.stack()[1]
34
63
  node_dir = str(Path(frame.filename).parent)
35
64
 
65
+ # Apply [env_vars] from comfy-env.toml FIRST (before any library loading)
66
+ config_path = os.path.join(node_dir, "comfy-env.toml")
67
+ env_vars = _load_env_vars(config_path)
68
+ for key, value in env_vars.items():
69
+ os.environ[key] = value
70
+
36
71
  pixi_env = os.path.join(node_dir, ".pixi", "envs", "default")
37
72
 
38
73
  if not os.path.exists(pixi_env):
@@ -3,7 +3,7 @@
3
3
  # =============================================================================
4
4
  #
5
5
  # comfy-env.toml is a SUPERSET of pixi.toml. All pixi.toml syntax works here.
6
- # We add custom sections: [cuda], [node_reqs], and top-level `python = "x.x"`.
6
+ # We add custom sections: [cuda], [node_reqs], [env_vars], and top-level `python = "x.x"`.
7
7
  #
8
8
  # GPU is auto-detected to select the right CUDA version.
9
9
  #
@@ -22,6 +22,21 @@
22
22
  # python = "3.11"
23
23
 
24
24
 
25
+ # =============================================================================
26
+ # ENVIRONMENT VARIABLES (custom section)
27
+ # =============================================================================
28
+ # Environment variables applied early via setup_env() in prestartup_script.py.
29
+ # Useful for OpenMP settings, library configs, etc. that must be set before imports.
30
+ #
31
+ # Requires prestartup_script.py in your node directory:
32
+ # from comfy_env import setup_env
33
+ # setup_env()
34
+
35
+ [env_vars]
36
+ # KMP_DUPLICATE_LIB_OK = "TRUE" # Allow duplicate OpenMP libraries (macOS fix)
37
+ # OMP_NUM_THREADS = "4" # Limit OpenMP threads
38
+
39
+
25
40
  # =============================================================================
26
41
  # CUDA PACKAGES (custom section)
27
42
  # =============================================================================
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comfy-env
3
- Version: 0.0.71
3
+ Version: 0.0.73
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
@@ -1,12 +1,12 @@
1
1
  comfy_env/__init__.py,sha256=s0RkyKsBlDiSI4ZSwivtWLvYhc8DS0CUEWgFLVJbtLA,2176
2
- comfy_env/cli.py,sha256=ky7jC8eArKbjgYw9Uy52MKajmtLb16wYblrbEOoiy2U,9599
2
+ comfy_env/cli.py,sha256=ty4HYlzollCUCS0o6Sha6eczPAsW_gHRVgvck3IfA2w,12723
3
3
  comfy_env/errors.py,sha256=q-C3vyrPa_kk_Ao8l17mIGfJiG2IR0hCFV0GFcNLmcI,9924
4
- comfy_env/install.py,sha256=Fkjk3ci9_mAabUFqyuXShV9-Ry2iTX-JWAx9fnr5q_Q,5098
4
+ comfy_env/install.py,sha256=t7C5lpF3NVDlAKiRR0TsgYlK6icn7NEtKOU3uGbHnZc,7072
5
5
  comfy_env/nodes.py,sha256=nBkG2pESeOt5kXSgTDIHAHaVdHK8-rEueR3BxXWn6TE,4354
6
- comfy_env/prestartup.py,sha256=_b9QIWsHzkQD5VYdiyn0beHMLoWx18aC4RKB8SlJiGE,2048
6
+ comfy_env/prestartup.py,sha256=rP0TtT9lxiU7Py27U79Ew7954VpHsYL6lfx4IpQY1Uc,3152
7
7
  comfy_env/config/__init__.py,sha256=4Guylkb-FV8QxhFwschzpzbr2eu8y-KNgNT3_JOm9jc,403
8
- comfy_env/config/parser.py,sha256=V3jjrCqkRDBAE-wmYjbwy6D6rt1Lg5rJk_hQMRbbdls,4106
9
- comfy_env/config/types.py,sha256=EqXj6fjZDZo0gmTMC-8jbShkONSociPVAmWztFbP_bE,2007
8
+ comfy_env/config/parser.py,sha256=dA1lX5ExBEfCqUJwe4V5i_jn2NJ69bMq3c3ji3lMSV8,4295
9
+ comfy_env/config/types.py,sha256=Sb8HO34xsSZu5YAc2K4M7Gb3QNevJlngf12hHiwuU0w,2140
10
10
  comfy_env/isolation/__init__.py,sha256=vw9a4mpJ2CFjy-PLe_A3zQ6umBQklgqWNxwn9beNw3g,175
11
11
  comfy_env/isolation/wrap.py,sha256=9bXxK9h4FMrFL9k4EAdILWxkjXYytVCJV68MuwwufK8,11485
12
12
  comfy_env/pixi/__init__.py,sha256=BUrq7AQf3WDm0cHWh72B2xZbURNnDu2dCuELWiQCUiM,997
@@ -19,14 +19,14 @@ comfy_env/pixi/platform/darwin.py,sha256=HK3VkLT6DfesAnIXwx2IaUFHTBclF0xTQnC7azW
19
19
  comfy_env/pixi/platform/linux.py,sha256=xLp8FEbFqZLQrzIZBI9z3C4g23Ab1ASTHLsXDzsdCoA,2062
20
20
  comfy_env/pixi/platform/windows.py,sha256=FCOCgpzGzorY9-HueMlJUR8DxM2eH-cj9iZk6K026Is,10891
21
21
  comfy_env/templates/comfy-env-instructions.txt,sha256=ve1RAthW7ouumU9h6DM7mIRX1MS8_Tyonq2U4tcrFu8,1031
22
- comfy_env/templates/comfy-env.toml,sha256=TBa_8V8gdwoRej4Lb_mkqZ_Ytk_quFTnbwwc1YK_gHE,4190
22
+ comfy_env/templates/comfy-env.toml,sha256=ROIqi4BlPL1MEdL1VgebfTHpdwPNYGHwWeigI9Kw-1I,4831
23
23
  comfy_env/workers/__init__.py,sha256=TMVG55d2XLP1mJ3x1d16H0SBDJZtk2kMC5P4HLk9TrA,1073
24
24
  comfy_env/workers/base.py,sha256=4ZYTaQ4J0kBHCoO_OfZnsowm4rJCoqinZUaOtgkOPbw,2307
25
25
  comfy_env/workers/mp.py,sha256=d6PFVrgqp3MK7Gkt08a8LQD_VSwHtoIcGx2Lovou3vM,25972
26
26
  comfy_env/workers/subprocess.py,sha256=UMhKhaJoSjv2-FWnwQq9TeMWtaDLIs3ajAFTq1ngdJw,57844
27
27
  comfy_env/workers/tensor_utils.py,sha256=TCuOAjJymrSbkgfyvcKtQ_KbVWTqSwP9VH_bCaFLLq8,6409
28
- comfy_env-0.0.71.dist-info/METADATA,sha256=jo9huPsDqbxsSsZEFtCvc_m7kJmz9XDDjNwAewXjnIk,6946
29
- comfy_env-0.0.71.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
30
- comfy_env-0.0.71.dist-info/entry_points.txt,sha256=J4fXeqgxU_YenuW_Zxn_pEL7J-3R0--b6MS5t0QmAr0,49
31
- comfy_env-0.0.71.dist-info/licenses/LICENSE,sha256=E68QZMMpW4P2YKstTZ3QU54HRQO8ecew09XZ4_Vn870,1093
32
- comfy_env-0.0.71.dist-info/RECORD,,
28
+ comfy_env-0.0.73.dist-info/METADATA,sha256=-VodLMkozSi5lHL_8xyarwc3SCWGSdIsfNPdSzCjZ4U,6946
29
+ comfy_env-0.0.73.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
30
+ comfy_env-0.0.73.dist-info/entry_points.txt,sha256=J4fXeqgxU_YenuW_Zxn_pEL7J-3R0--b6MS5t0QmAr0,49
31
+ comfy_env-0.0.73.dist-info/licenses/LICENSE,sha256=E68QZMMpW4P2YKstTZ3QU54HRQO8ecew09XZ4_Vn870,1093
32
+ comfy_env-0.0.73.dist-info/RECORD,,