comfy-env 0.0.23__py3-none-any.whl → 0.0.25__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/install.py CHANGED
@@ -22,9 +22,9 @@ import shutil
22
22
  import subprocess
23
23
  import sys
24
24
  from pathlib import Path
25
- from typing import Any, Callable, Dict, List, Optional, Union
25
+ from typing import Any, Callable, Dict, List, Optional, Set, Union
26
26
 
27
- from .env.config import IsolatedEnv, SystemConfig
27
+ from .env.config import IsolatedEnv, NodeReq, SystemConfig
28
28
  from .env.config_file import load_config, discover_config
29
29
  from .env.manager import IsolatedEnvManager
30
30
  from .errors import CUDANotFoundError, DependencyError, InstallError, WheelNotFoundError
@@ -132,6 +132,47 @@ def _install_system_packages(
132
132
  return True
133
133
 
134
134
 
135
+ def _install_node_dependencies(
136
+ node_reqs: List[NodeReq],
137
+ node_dir: Path,
138
+ log: Callable[[str], None],
139
+ dry_run: bool = False,
140
+ ) -> bool:
141
+ """
142
+ Install node dependencies (other ComfyUI custom nodes).
143
+
144
+ Args:
145
+ node_reqs: List of NodeReq objects from [node_reqs] config section.
146
+ node_dir: Directory of the current node (used to find custom_nodes/).
147
+ log: Logging callback.
148
+ dry_run: If True, show what would be installed without installing.
149
+
150
+ Returns:
151
+ True if installation succeeded or no dependencies needed.
152
+ """
153
+ from .nodes import install_node_deps
154
+
155
+ # Detect custom_nodes directory (parent of current node)
156
+ custom_nodes_dir = node_dir.parent
157
+
158
+ log(f"\nInstalling {len(node_reqs)} node dependencies...")
159
+
160
+ if dry_run:
161
+ for req in node_reqs:
162
+ node_path = custom_nodes_dir / req.name
163
+ status = "exists" if node_path.exists() else "would clone"
164
+ log(f" {req.name}: {status}")
165
+ return True
166
+
167
+ # Track visited nodes to prevent cycles
168
+ # Start with current node's directory name
169
+ visited: Set[str] = {node_dir.name}
170
+
171
+ install_node_deps(node_reqs, custom_nodes_dir, log, visited)
172
+
173
+ return True
174
+
175
+
135
176
  def install(
136
177
  config: Optional[Union[str, Path]] = None,
137
178
  mode: str = "inplace",
@@ -185,7 +226,12 @@ def install(
185
226
  "Create comfy-env.toml or specify path explicitly."
186
227
  )
187
228
 
188
- # Install system packages first (apt, brew, etc.)
229
+ # Install node dependencies first (other ComfyUI custom nodes)
230
+ # These may have their own system packages and Python packages
231
+ if full_config.node_reqs:
232
+ _install_node_dependencies(full_config.node_reqs, node_dir, log, dry_run)
233
+
234
+ # Install system packages (apt, brew, etc.)
189
235
  # These need to be installed before Python packages that depend on them
190
236
  if full_config.has_system:
191
237
  _install_system_packages(full_config.system, log, dry_run)
comfy_env/nodes.py ADDED
@@ -0,0 +1,154 @@
1
+ """
2
+ Node dependency installation for comfy-env.
3
+
4
+ This module handles installation of dependent ComfyUI custom nodes
5
+ specified in the [node_reqs] section of comfy-env.toml.
6
+
7
+ Example configuration:
8
+ [node_reqs]
9
+ ComfyUI_essentials = "cubiq/ComfyUI_essentials"
10
+ ComfyUI-DepthAnythingV2 = "kijai/ComfyUI-DepthAnythingV2"
11
+ """
12
+
13
+ import subprocess
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import TYPE_CHECKING, Callable, List, Set
17
+
18
+ if TYPE_CHECKING:
19
+ from .env.config import NodeReq
20
+
21
+
22
+ def normalize_repo_url(repo: str) -> str:
23
+ """
24
+ Convert GitHub shorthand to full URL.
25
+
26
+ Args:
27
+ repo: Either 'owner/repo' or full URL like 'https://github.com/owner/repo'
28
+
29
+ Returns:
30
+ Full GitHub URL
31
+ """
32
+ if repo.startswith("http://") or repo.startswith("https://"):
33
+ return repo
34
+ return f"https://github.com/{repo}"
35
+
36
+
37
+ def clone_node(
38
+ repo: str,
39
+ name: str,
40
+ target_dir: Path,
41
+ log: Callable[[str], None],
42
+ ) -> Path:
43
+ """
44
+ Clone a node repository to target_dir/name.
45
+
46
+ Args:
47
+ repo: GitHub repo path (e.g., 'owner/repo') or full URL
48
+ name: Directory name for the cloned repo
49
+ target_dir: Parent directory (usually custom_nodes/)
50
+ log: Logging callback
51
+
52
+ Returns:
53
+ Path to the cloned node directory
54
+
55
+ Raises:
56
+ RuntimeError: If git clone fails
57
+ """
58
+ node_path = target_dir / name
59
+ url = normalize_repo_url(repo)
60
+
61
+ log(f" Cloning {name} from {url}...")
62
+ result = subprocess.run(
63
+ ["git", "clone", "--depth", "1", url, str(node_path)],
64
+ capture_output=True,
65
+ text=True,
66
+ )
67
+
68
+ if result.returncode != 0:
69
+ raise RuntimeError(f"Failed to clone {url}: {result.stderr.strip()}")
70
+
71
+ return node_path
72
+
73
+
74
+ def run_install_script(
75
+ node_dir: Path,
76
+ log: Callable[[str], None],
77
+ ) -> None:
78
+ """
79
+ Run install.py in a node directory if it exists.
80
+
81
+ Args:
82
+ node_dir: Path to the node directory
83
+ log: Logging callback
84
+ """
85
+ install_script = node_dir / "install.py"
86
+
87
+ if install_script.exists():
88
+ log(f" Running install.py for {node_dir.name}...")
89
+ result = subprocess.run(
90
+ [sys.executable, str(install_script)],
91
+ cwd=node_dir,
92
+ capture_output=True,
93
+ text=True,
94
+ )
95
+ if result.returncode != 0:
96
+ log(f" Warning: install.py failed for {node_dir.name}: {result.stderr.strip()[:200]}")
97
+
98
+
99
+ def install_node_deps(
100
+ node_reqs: "List[NodeReq]",
101
+ custom_nodes_dir: Path,
102
+ log: Callable[[str], None],
103
+ visited: Set[str],
104
+ ) -> None:
105
+ """
106
+ Install node dependencies recursively.
107
+
108
+ Args:
109
+ node_reqs: List of NodeReq objects to install
110
+ custom_nodes_dir: Path to custom_nodes directory
111
+ log: Logging callback
112
+ visited: Set of already-processed node names (for cycle detection)
113
+ """
114
+ from .env.config_file import discover_config
115
+
116
+ for req in node_reqs:
117
+ # Skip if already visited (cycle detection)
118
+ if req.name in visited:
119
+ log(f" {req.name}: already in dependency chain, skipping")
120
+ continue
121
+
122
+ visited.add(req.name)
123
+
124
+ node_path = custom_nodes_dir / req.name
125
+
126
+ # Skip if already installed (directory exists)
127
+ if node_path.exists():
128
+ log(f" {req.name}: already installed, skipping")
129
+ continue
130
+
131
+ try:
132
+ # Clone the repository
133
+ clone_node(req.repo, req.name, custom_nodes_dir, log)
134
+
135
+ # Run install.py if present
136
+ run_install_script(node_path, log)
137
+
138
+ # Recursively process nested node_reqs
139
+ try:
140
+ nested_config = discover_config(node_path)
141
+ if nested_config and nested_config.node_reqs:
142
+ log(f" {req.name}: found {len(nested_config.node_reqs)} nested dependencies")
143
+ install_node_deps(
144
+ nested_config.node_reqs,
145
+ custom_nodes_dir,
146
+ log,
147
+ visited,
148
+ )
149
+ except Exception as e:
150
+ # Don't fail if we can't parse nested config
151
+ log(f" {req.name}: could not check for nested deps: {e}")
152
+
153
+ except Exception as e:
154
+ log(f" Warning: Failed to install {req.name}: {e}")
comfy_env/pixi.py CHANGED
@@ -274,6 +274,35 @@ def create_pixi_toml(
274
274
  return pixi_toml_path
275
275
 
276
276
 
277
+ def clean_pixi_artifacts(
278
+ node_dir: Path,
279
+ log: Callable[[str], None] = print,
280
+ ) -> None:
281
+ """
282
+ Remove previous pixi installation artifacts.
283
+
284
+ This ensures a clean state before generating a new pixi.toml,
285
+ preventing stale lock files or cached environments from causing conflicts.
286
+
287
+ Args:
288
+ node_dir: Directory containing the pixi artifacts.
289
+ log: Logging callback.
290
+ """
291
+ pixi_toml = node_dir / "pixi.toml"
292
+ pixi_lock = node_dir / "pixi.lock"
293
+ pixi_dir = node_dir / ".pixi"
294
+
295
+ if pixi_toml.exists():
296
+ pixi_toml.unlink()
297
+ log(" Removed previous pixi.toml")
298
+ if pixi_lock.exists():
299
+ pixi_lock.unlink()
300
+ log(" Removed previous pixi.lock")
301
+ if pixi_dir.exists():
302
+ shutil.rmtree(pixi_dir)
303
+ log(" Removed previous .pixi/ directory")
304
+
305
+
277
306
  def pixi_install(
278
307
  env_config: IsolatedEnv,
279
308
  node_dir: Path,
@@ -284,9 +313,10 @@ def pixi_install(
284
313
  Install conda and pip packages using pixi.
285
314
 
286
315
  This is the main entry point for pixi-based installation. It:
287
- 1. Ensures pixi is installed
288
- 2. Generates pixi.toml from the config
289
- 3. Runs `pixi install` to install all dependencies
316
+ 1. Cleans previous pixi artifacts
317
+ 2. Ensures pixi is installed
318
+ 3. Generates pixi.toml from the config
319
+ 4. Runs `pixi install` to install all dependencies
290
320
 
291
321
  Args:
292
322
  env_config: The isolated environment configuration.
@@ -304,6 +334,7 @@ def pixi_install(
304
334
 
305
335
  if dry_run:
306
336
  log("Dry run - would:")
337
+ log(f" - Clean previous pixi artifacts")
307
338
  log(f" - Ensure pixi is installed")
308
339
  log(f" - Generate pixi.toml in {node_dir}")
309
340
  if env_config.conda:
@@ -312,6 +343,9 @@ def pixi_install(
312
343
  log(f" - Install {len(env_config.requirements)} pip packages")
313
344
  return True
314
345
 
346
+ # Clean previous pixi artifacts
347
+ clean_pixi_artifacts(node_dir, log)
348
+
315
349
  # Ensure pixi is installed
316
350
  pixi_path = ensure_pixi(log=log)
317
351
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comfy-env
3
- Version: 0.0.23
3
+ Version: 0.0.25
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
@@ -2,8 +2,9 @@ comfy_env/__init__.py,sha256=u2uTyoysPQyNMcRp5U4VTMJF11FBW6Goqu0DN-BdUuY,3678
2
2
  comfy_env/cli.py,sha256=X-GCQMP0mtMcE3ZgkT-VLQ4Gq3UUvcb_Ux_NClEFhgI,15975
3
3
  comfy_env/decorator.py,sha256=6JCKwLHaZtOLVDexs_gh_-NtS2ZK0V7nGCPqkyeYEAA,16688
4
4
  comfy_env/errors.py,sha256=8hN8NDlo8oBUdapc-eT3ZluigI5VBzfqsSBvQdfWlz4,9943
5
- comfy_env/install.py,sha256=CA5O0-ghkTdV67fVyKJy6wt1vL35inBeqWtA52udjHI,24301
6
- comfy_env/pixi.py,sha256=VqAv_sSdCBGIbLS5jLYnxV6rgiunsCUC5NtmpfV7rI4,12296
5
+ comfy_env/install.py,sha256=pyyaYbCFZn6T5_sgjumd1TeFaTEXUTcdFbex31m_JEc,25837
6
+ comfy_env/nodes.py,sha256=CWUe35jU5SKk4ur-SddZePdqWgxJDlxGhpcJiu5pAK4,4354
7
+ comfy_env/pixi.py,sha256=Oj9Z9bBg8IpsPF3usAx9v4w6OhpNk4LQjUuAP5ohnQY,13324
7
8
  comfy_env/registry.py,sha256=uFCtGmWYvwGCqObXgzmArX7o5JsFNsHXxayofk3m6no,2569
8
9
  comfy_env/resolver.py,sha256=l-AnmCE1puG6CvdpDB-KrsfG_cn_3uO2DryYizUnG_4,12474
9
10
  comfy_env/env/__init__.py,sha256=imQdoQEQvrRT-QDtyNpFlkVbm2fBzgACdpQwRPd09fI,1157
@@ -33,8 +34,8 @@ comfy_env/workers/tensor_utils.py,sha256=TCuOAjJymrSbkgfyvcKtQ_KbVWTqSwP9VH_bCaF
33
34
  comfy_env/workers/torch_mp.py,sha256=4YSNPn7hALrvMVbkO4RkTeFTcc0lhfLMk5QTWjY4PHw,22134
34
35
  comfy_env/workers/venv.py,sha256=_ekHfZPqBIPY08DjqiXm6cTBQH4DrbxRWR3AAv3mit8,31589
35
36
  comfy_env/wheel_sources.yml,sha256=nSZ8XB_I5JXQGB7AgC6lHs_IXMd9Kcno10artNL8BKw,7775
36
- comfy_env-0.0.23.dist-info/METADATA,sha256=oTiCkl-rEq5VD2B9BhRvcCRo9iZyFF5QWLjuYQI3tDI,5400
37
- comfy_env-0.0.23.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
38
- comfy_env-0.0.23.dist-info/entry_points.txt,sha256=J4fXeqgxU_YenuW_Zxn_pEL7J-3R0--b6MS5t0QmAr0,49
39
- comfy_env-0.0.23.dist-info/licenses/LICENSE,sha256=E68QZMMpW4P2YKstTZ3QU54HRQO8ecew09XZ4_Vn870,1093
40
- comfy_env-0.0.23.dist-info/RECORD,,
37
+ comfy_env-0.0.25.dist-info/METADATA,sha256=IcYAcYC42bhuCU4ijkeY1CfUwX4b3AVRhnFZ3mR1BXw,5400
38
+ comfy_env-0.0.25.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
39
+ comfy_env-0.0.25.dist-info/entry_points.txt,sha256=J4fXeqgxU_YenuW_Zxn_pEL7J-3R0--b6MS5t0QmAr0,49
40
+ comfy_env-0.0.25.dist-info/licenses/LICENSE,sha256=E68QZMMpW4P2YKstTZ3QU54HRQO8ecew09XZ4_Vn870,1093
41
+ comfy_env-0.0.25.dist-info/RECORD,,