comfy-env 0.1.8__py3-none-any.whl → 0.1.9__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.
@@ -319,6 +319,13 @@ def wrap_isolated_nodes(
319
319
  if os.environ.get("COMFYUI_ISOLATION_WORKER") == "1":
320
320
  return node_class_mappings
321
321
 
322
+ # Get ComfyUI base path from folder_paths (canonical source)
323
+ try:
324
+ import folder_paths
325
+ comfyui_base = folder_paths.base_path
326
+ except ImportError:
327
+ comfyui_base = None
328
+
322
329
  nodes_dir = Path(nodes_dir).resolve()
323
330
 
324
331
  # Check for comfy-env.toml
@@ -341,6 +348,10 @@ def wrap_isolated_nodes(
341
348
  except Exception:
342
349
  pass # Ignore errors reading config
343
350
 
351
+ # Set COMFYUI_BASE for worker to find ComfyUI modules
352
+ if comfyui_base:
353
+ env_vars["COMFYUI_BASE"] = str(comfyui_base)
354
+
344
355
  # Find environment directory and paths
345
356
  env_dir = _find_env_dir(nodes_dir)
346
357
  site_packages, lib_dir = _find_env_paths(nodes_dir)
comfy_env/nodes.py CHANGED
@@ -71,6 +71,31 @@ def clone_node(
71
71
  return node_path
72
72
 
73
73
 
74
+ def install_requirements(
75
+ node_dir: Path,
76
+ log: Callable[[str], None],
77
+ ) -> None:
78
+ """
79
+ Install requirements.txt in a node directory if it exists.
80
+
81
+ Args:
82
+ node_dir: Path to the node directory
83
+ log: Logging callback
84
+ """
85
+ requirements_file = node_dir / "requirements.txt"
86
+
87
+ if requirements_file.exists():
88
+ log(f" Installing requirements for {node_dir.name}...")
89
+ result = subprocess.run(
90
+ ["uv", "pip", "install", "-r", str(requirements_file), "--python", sys.executable],
91
+ cwd=node_dir,
92
+ capture_output=True,
93
+ text=True,
94
+ )
95
+ if result.returncode != 0:
96
+ log(f" Warning: requirements.txt install failed for {node_dir.name}: {result.stderr.strip()[:200]}")
97
+
98
+
74
99
  def run_install_script(
75
100
  node_dir: Path,
76
101
  log: Callable[[str], None],
@@ -132,6 +157,9 @@ def install_node_deps(
132
157
  # Clone the repository
133
158
  clone_node(req.repo, req.name, custom_nodes_dir, log)
134
159
 
160
+ # Install requirements.txt if present
161
+ install_requirements(node_path, log)
162
+
135
163
  # Run install.py if present
136
164
  run_install_script(node_path, log)
137
165
 
comfy_env/pixi/core.py CHANGED
@@ -40,10 +40,87 @@ CUDA_TORCH_MAP = {
40
40
  "12.1": "2.4",
41
41
  }
42
42
 
43
+ def find_wheel_url(
44
+ package: str,
45
+ torch_version: str,
46
+ cuda_version: str,
47
+ python_version: str,
48
+ ) -> Optional[str]:
49
+ """
50
+ Query cuda-wheels index and return the direct URL for the matching wheel.
51
+
52
+ This bypasses pip's version validation by providing a direct URL,
53
+ which is necessary for wheels where the filename has a local version
54
+ but the internal METADATA doesn't (e.g., flash-attn from mjun0812).
55
+
56
+ Args:
57
+ package: Package name (e.g., "flash-attn")
58
+ torch_version: PyTorch version (e.g., "2.8")
59
+ cuda_version: CUDA version (e.g., "12.8")
60
+ python_version: Python version (e.g., "3.10")
61
+
62
+ Returns:
63
+ Direct URL to the wheel file, or None if no match found.
64
+ """
65
+ cuda_short = cuda_version.replace(".", "")[:3] # "12.8" -> "128"
66
+ torch_short = torch_version.replace(".", "")[:2] # "2.8" -> "28"
67
+ py_tag = f"cp{python_version.replace('.', '')}" # "3.10" -> "cp310"
68
+
69
+ # Platform tag for current system
70
+ if sys.platform == "linux":
71
+ platform_tag = "linux_x86_64"
72
+ elif sys.platform == "win32":
73
+ platform_tag = "win_amd64"
74
+ else:
75
+ platform_tag = None # macOS doesn't typically have CUDA wheels
76
+
77
+ # Local version patterns to match:
78
+ # cuda-wheels style: +cu128torch28
79
+ # PyG style: +pt28cu128
80
+ local_patterns = [
81
+ f"+cu{cuda_short}torch{torch_short}", # cuda-wheels style
82
+ f"+pt{torch_short}cu{cuda_short}", # PyG style
83
+ ]
84
+
85
+ pkg_variants = [package, package.replace("-", "_"), package.replace("_", "-")]
86
+
87
+ for pkg_dir in pkg_variants:
88
+ index_url = f"{CUDA_WHEELS_INDEX}{pkg_dir}/"
89
+ try:
90
+ with urllib.request.urlopen(index_url, timeout=10) as resp:
91
+ html = resp.read().decode("utf-8")
92
+ except Exception:
93
+ continue
94
+
95
+ # Parse href and display name from HTML: <a href="URL">DISPLAY_NAME</a>
96
+ link_pattern = re.compile(r'href="([^"]+\.whl)"[^>]*>([^<]+)</a>', re.IGNORECASE)
97
+
98
+ for match in link_pattern.finditer(html):
99
+ wheel_url = match.group(1)
100
+ display_name = match.group(2)
101
+
102
+ # Match on display name (has normalized torch28 format)
103
+ matches_cuda_torch = any(p in display_name for p in local_patterns)
104
+ matches_python = py_tag in display_name
105
+ matches_platform = platform_tag is None or platform_tag in display_name
106
+
107
+ if matches_cuda_torch and matches_python and matches_platform:
108
+ # Return absolute URL
109
+ if wheel_url.startswith("http"):
110
+ return wheel_url
111
+ # Relative URL - construct absolute
112
+ return f"{CUDA_WHEELS_INDEX}{pkg_dir}/{wheel_url}"
113
+
114
+ return None
115
+
116
+
43
117
  def find_matching_wheel(package: str, torch_version: str, cuda_version: str) -> Optional[str]:
44
118
  """
45
119
  Query cuda-wheels index to find a wheel matching the CUDA/torch version.
46
120
  Returns the full version spec (e.g., "flash-attn===2.8.3+cu128torch2.8") or None.
121
+
122
+ Note: This is used as a fallback for packages with correct wheel metadata.
123
+ For packages with mismatched metadata (like flash-attn), use find_wheel_url() instead.
47
124
  """
48
125
  cuda_short = cuda_version.replace(".", "")[:3] # "12.8" -> "128"
49
126
  torch_short = torch_version.replace(".", "")[:2] # "2.8" -> "28"
@@ -67,11 +144,11 @@ def find_matching_wheel(package: str, torch_version: str, cuda_version: str) ->
67
144
  )
68
145
 
69
146
  # Local version patterns to match:
70
- # flash-attn style: +cu128torch2.8
147
+ # cuda-wheels style: +cu128torch28
71
148
  # PyG style: +pt28cu128
72
149
  local_patterns = [
73
- f"+cu{cuda_short}torch{torch_version}", # flash-attn style
74
- f"+pt{torch_short}cu{cuda_short}", # PyG style
150
+ f"+cu{cuda_short}torch{torch_short}", # cuda-wheels style
151
+ f"+pt{torch_short}cu{cuda_short}", # PyG style
75
152
  ]
76
153
 
77
154
  best_match = None
@@ -394,29 +471,35 @@ def pixi_install(
394
471
  log(f"pixi install failed:\n{result.stderr}")
395
472
  raise RuntimeError(f"pixi install failed: {result.stderr}")
396
473
 
397
- # Install CUDA packages with --find-links (searches all known sources)
474
+ # Install CUDA packages via direct URL or find-links fallback
398
475
  if cfg.cuda_packages and cuda_version:
399
476
  log(f"Installing CUDA packages: {cfg.cuda_packages}")
400
477
  python_path = get_pixi_python(node_dir)
401
478
  if not python_path:
402
479
  raise RuntimeError("Could not find Python in pixi environment")
403
480
 
481
+ # Get Python version from the pixi environment
482
+ py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
483
+
404
484
  for package in cfg.cuda_packages:
485
+ # Find direct wheel URL (bypasses metadata validation)
486
+ wheel_url = find_wheel_url(package, torch_version, cuda_version, py_version)
487
+
488
+ if not wheel_url:
489
+ raise RuntimeError(
490
+ f"No wheel found for {package} with CUDA {cuda_version}, "
491
+ f"torch {torch_version}, Python {py_version}. "
492
+ f"Check cuda-wheels index."
493
+ )
494
+
495
+ log(f" Installing {package} from {wheel_url}")
405
496
  pip_cmd = [
406
497
  str(python_path), "-m", "pip", "install",
407
- "--no-index", # Only use --find-links, don't query PyPI
408
498
  "--no-deps",
409
499
  "--no-cache-dir",
500
+ wheel_url,
410
501
  ]
411
502
 
412
- # Add all find-links sources
413
- for url in get_all_find_links(package, torch_version, cuda_version):
414
- pip_cmd.extend(["--find-links", url])
415
-
416
- # Get package spec with local version for CUDA/torch compatibility
417
- pkg_spec = get_package_spec(package, torch_version, cuda_version)
418
- log(f" Installing {pkg_spec}")
419
- pip_cmd.append(pkg_spec)
420
503
  result = subprocess.run(pip_cmd, capture_output=True, text=True)
421
504
  if result.returncode != 0:
422
505
  log(f"CUDA package install failed for {package}:\n{result.stderr}")
@@ -1044,15 +1044,21 @@ class SubprocessWorker(Worker):
1044
1044
 
1045
1045
  def _find_comfyui_base(self) -> Optional[Path]:
1046
1046
  """Find ComfyUI base directory."""
1047
- # Check common child directories (for test environments)
1048
- # Also check parent's children (isolated venv is sibling to .comfy-test-env)
1047
+ # Use folder_paths.base_path (canonical source) if available
1048
+ try:
1049
+ import folder_paths
1050
+ return Path(folder_paths.base_path)
1051
+ except ImportError:
1052
+ pass
1053
+
1054
+ # Fallback: Check common child directories (for test environments)
1049
1055
  for base in [self.working_dir, self.working_dir.parent]:
1050
1056
  for child in [".comfy-test-env/ComfyUI", "ComfyUI"]:
1051
1057
  candidate = base / child
1052
1058
  if (candidate / "main.py").exists() and (candidate / "comfy").exists():
1053
1059
  return candidate
1054
1060
 
1055
- # Walk up from working_dir (standard ComfyUI custom_nodes layout)
1061
+ # Fallback: Walk up from working_dir (standard ComfyUI custom_nodes layout)
1056
1062
  current = self.working_dir.resolve()
1057
1063
  for _ in range(10):
1058
1064
  if (current / "main.py").exists() and (current / "comfy").exists():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comfy-env
3
- Version: 0.1.8
3
+ Version: 0.1.9
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
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Requires-Python: >=3.10
20
+ Requires-Dist: pip>=21.0
20
21
  Requires-Dist: tomli-w>=1.0.0
21
22
  Requires-Dist: tomli>=2.0.0; python_version < '3.11'
22
23
  Requires-Dist: uv>=0.4.0
@@ -2,15 +2,15 @@ comfy_env/__init__.py,sha256=s0RkyKsBlDiSI4ZSwivtWLvYhc8DS0CUEWgFLVJbtLA,2176
2
2
  comfy_env/cli.py,sha256=ty4HYlzollCUCS0o6Sha6eczPAsW_gHRVgvck3IfA2w,12723
3
3
  comfy_env/errors.py,sha256=q-C3vyrPa_kk_Ao8l17mIGfJiG2IR0hCFV0GFcNLmcI,9924
4
4
  comfy_env/install.py,sha256=N7eBj8wB2DrGepVYk-Hks2mSf6UuGzj34pfVLNYJgQ4,10357
5
- comfy_env/nodes.py,sha256=nBkG2pESeOt5kXSgTDIHAHaVdHK8-rEueR3BxXWn6TE,4354
5
+ comfy_env/nodes.py,sha256=nR-LctovXYTqk-seHovq9W_Ll0hlLeGMw-SQsa6IkBM,5235
6
6
  comfy_env/prestartup.py,sha256=NML_nzg4h37YdEg7gHxA7ke_uXWZsldz-X3HldHEUsE,5338
7
7
  comfy_env/config/__init__.py,sha256=4Guylkb-FV8QxhFwschzpzbr2eu8y-KNgNT3_JOm9jc,403
8
8
  comfy_env/config/parser.py,sha256=dA1lX5ExBEfCqUJwe4V5i_jn2NJ69bMq3c3ji3lMSV8,4295
9
9
  comfy_env/config/types.py,sha256=Sb8HO34xsSZu5YAc2K4M7Gb3QNevJlngf12hHiwuU0w,2140
10
10
  comfy_env/isolation/__init__.py,sha256=vw9a4mpJ2CFjy-PLe_A3zQ6umBQklgqWNxwn9beNw3g,175
11
- comfy_env/isolation/wrap.py,sha256=NACfz-EmKj6X5Ko6dshYjS-jUm-q8xm7uZWpr52858Y,12307
11
+ comfy_env/isolation/wrap.py,sha256=fYsf3tWpzNPoZT7mvWHbD-LmgBMhMCIfzlfcOFgwhlU,12641
12
12
  comfy_env/pixi/__init__.py,sha256=BUrq7AQf3WDm0cHWh72B2xZbURNnDu2dCuELWiQCUiM,997
13
- comfy_env/pixi/core.py,sha256=SO4v68-Lvrt6Z60j6jt91bLPGieF1wi43Uuq5F77Vv4,15627
13
+ comfy_env/pixi/core.py,sha256=zPEtuF8D2gnIaljmGEGT8Ttw0lTy_cV-hIIn1fvlbBU,18788
14
14
  comfy_env/pixi/cuda_detection.py,sha256=sqB3LjvGNdV4eFqiARQGfyecBM3ZiUmeh6nG0YCRYQw,9751
15
15
  comfy_env/pixi/resolver.py,sha256=U_A8rBDxCj4gUlJt2YJQniP4cCKqxJEiVFgXOoH7vM8,6339
16
16
  comfy_env/pixi/platform/__init__.py,sha256=Nb5MPZIEeanSMEWwqU4p4bnEKTJn1tWcwobnhq9x9IY,614
@@ -23,10 +23,10 @@ comfy_env/templates/comfy-env.toml,sha256=ROIqi4BlPL1MEdL1VgebfTHpdwPNYGHwWeigI9
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=8Xqcr4R4KlHtP1P_UDAtSTPHA_5i-ySTlxxLpqyOk-w,29757
26
- comfy_env/workers/subprocess.py,sha256=UMhKhaJoSjv2-FWnwQq9TeMWtaDLIs3ajAFTq1ngdJw,57844
26
+ comfy_env/workers/subprocess.py,sha256=a9gpaTppbL7ZRgv4grK__QioGUiN0Ci9RIZGTVTsQ78,57993
27
27
  comfy_env/workers/tensor_utils.py,sha256=TCuOAjJymrSbkgfyvcKtQ_KbVWTqSwP9VH_bCaFLLq8,6409
28
- comfy_env-0.1.8.dist-info/METADATA,sha256=8WOllc7KtZ1wb3pkCam0qZd5YW1CEd33WwQIzBqENJI,6945
29
- comfy_env-0.1.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
30
- comfy_env-0.1.8.dist-info/entry_points.txt,sha256=J4fXeqgxU_YenuW_Zxn_pEL7J-3R0--b6MS5t0QmAr0,49
31
- comfy_env-0.1.8.dist-info/licenses/LICENSE,sha256=E68QZMMpW4P2YKstTZ3QU54HRQO8ecew09XZ4_Vn870,1093
32
- comfy_env-0.1.8.dist-info/RECORD,,
28
+ comfy_env-0.1.9.dist-info/METADATA,sha256=qplbcTtKJj8y2m8rGUpIMcZyZymKdqA7wQNQf4t5HsI,6970
29
+ comfy_env-0.1.9.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
30
+ comfy_env-0.1.9.dist-info/entry_points.txt,sha256=J4fXeqgxU_YenuW_Zxn_pEL7J-3R0--b6MS5t0QmAr0,49
31
+ comfy_env-0.1.9.dist-info/licenses/LICENSE,sha256=E68QZMMpW4P2YKstTZ3QU54HRQO8ecew09XZ4_Vn870,1093
32
+ comfy_env-0.1.9.dist-info/RECORD,,