comfy-env 0.1.2__tar.gz → 0.1.4__tar.gz
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-0.1.2 → comfy_env-0.1.4}/PKG-INFO +1 -1
- {comfy_env-0.1.2 → comfy_env-0.1.4}/pyproject.toml +1 -1
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/isolation/wrap.py +23 -2
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/pixi/core.py +86 -19
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/workers/mp.py +10 -3
- {comfy_env-0.1.2 → comfy_env-0.1.4}/.github/workflows/ci.yml +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/.github/workflows/publish.yml +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/.gitignore +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/LICENSE +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/README.md +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/__init__.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/cli.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/config/__init__.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/config/parser.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/config/types.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/errors.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/install.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/isolation/__init__.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/nodes.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/pixi/__init__.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/pixi/cuda_detection.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/pixi/platform/__init__.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/pixi/platform/base.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/pixi/platform/darwin.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/pixi/platform/linux.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/pixi/platform/windows.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/pixi/resolver.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/prestartup.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/templates/comfy-env-instructions.txt +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/templates/comfy-env.toml +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/workers/__init__.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/workers/base.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/workers/subprocess.py +0 -0
- {comfy_env-0.1.2 → comfy_env-0.1.4}/src/comfy_env/workers/tensor_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: comfy-env
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
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
|
|
@@ -67,6 +67,7 @@ def _get_worker(
|
|
|
67
67
|
working_dir: Path,
|
|
68
68
|
sys_path: list[str],
|
|
69
69
|
lib_path: Optional[str] = None,
|
|
70
|
+
env_vars: Optional[dict] = None,
|
|
70
71
|
):
|
|
71
72
|
"""Get or create a persistent worker for the isolated environment."""
|
|
72
73
|
cache_key = str(env_dir)
|
|
@@ -106,11 +107,14 @@ def _get_worker(
|
|
|
106
107
|
|
|
107
108
|
print(f"[comfy-env] Starting isolated worker (MPWorker)")
|
|
108
109
|
print(f"[comfy-env] Env: {env_dir}")
|
|
110
|
+
if env_vars:
|
|
111
|
+
print(f"[comfy-env] env_vars: {', '.join(f'{k}={v}' for k, v in env_vars.items())}")
|
|
109
112
|
|
|
110
113
|
worker = MPWorker(
|
|
111
114
|
name=working_dir.name,
|
|
112
115
|
sys_path=sys_path,
|
|
113
116
|
lib_path=lib_path,
|
|
117
|
+
env_vars=env_vars,
|
|
114
118
|
)
|
|
115
119
|
|
|
116
120
|
_workers[cache_key] = worker
|
|
@@ -187,6 +191,7 @@ def _wrap_node_class(
|
|
|
187
191
|
working_dir: Path,
|
|
188
192
|
sys_path: list[str],
|
|
189
193
|
lib_path: Optional[str] = None,
|
|
194
|
+
env_vars: Optional[dict] = None,
|
|
190
195
|
) -> type:
|
|
191
196
|
"""
|
|
192
197
|
Wrap a node class so its FUNCTION method runs in the isolated environment.
|
|
@@ -232,7 +237,7 @@ def _wrap_node_class(
|
|
|
232
237
|
print(f"[comfy-env] PROXY CALLED: {cls.__name__}.{func_name}", flush=True)
|
|
233
238
|
print(f"[comfy-env] kwargs keys: {list(kwargs.keys())}", flush=True)
|
|
234
239
|
|
|
235
|
-
worker = _get_worker(env_dir, working_dir, sys_path, lib_path)
|
|
240
|
+
worker = _get_worker(env_dir, working_dir, sys_path, lib_path, env_vars)
|
|
236
241
|
if _DEBUG:
|
|
237
242
|
print(f"[comfy-env] worker alive: {worker.is_alive()}", flush=True)
|
|
238
243
|
|
|
@@ -322,6 +327,20 @@ def wrap_isolated_nodes(
|
|
|
322
327
|
print(f"[comfy-env] Warning: No comfy-env.toml in {nodes_dir}")
|
|
323
328
|
return node_class_mappings
|
|
324
329
|
|
|
330
|
+
# Read env_vars from comfy-env.toml
|
|
331
|
+
env_vars = {}
|
|
332
|
+
try:
|
|
333
|
+
if sys.version_info >= (3, 11):
|
|
334
|
+
import tomllib
|
|
335
|
+
else:
|
|
336
|
+
import tomli as tomllib
|
|
337
|
+
with open(config_file, "rb") as f:
|
|
338
|
+
config = tomllib.load(f)
|
|
339
|
+
env_vars_data = config.get("env_vars", {})
|
|
340
|
+
env_vars = {str(k): str(v) for k, v in env_vars_data.items()}
|
|
341
|
+
except Exception:
|
|
342
|
+
pass # Ignore errors reading config
|
|
343
|
+
|
|
325
344
|
# Find environment directory and paths
|
|
326
345
|
env_dir = _find_env_dir(nodes_dir)
|
|
327
346
|
site_packages, lib_dir = _find_env_paths(nodes_dir)
|
|
@@ -342,10 +361,12 @@ def wrap_isolated_nodes(
|
|
|
342
361
|
print(f"[comfy-env] site-packages: {site_packages}")
|
|
343
362
|
if lib_path:
|
|
344
363
|
print(f"[comfy-env] lib: {lib_path}")
|
|
364
|
+
if env_vars:
|
|
365
|
+
print(f"[comfy-env] env_vars: {', '.join(f'{k}={v}' for k, v in env_vars.items())}")
|
|
345
366
|
|
|
346
367
|
# Wrap all node classes
|
|
347
368
|
for node_name, node_cls in node_class_mappings.items():
|
|
348
369
|
if hasattr(node_cls, "FUNCTION"):
|
|
349
|
-
_wrap_node_class(node_cls, env_dir, nodes_dir, sys_path, lib_path)
|
|
370
|
+
_wrap_node_class(node_cls, env_dir, nodes_dir, sys_path, lib_path, env_vars)
|
|
350
371
|
|
|
351
372
|
return node_class_mappings
|
|
@@ -9,10 +9,12 @@ See: https://pixi.sh/
|
|
|
9
9
|
|
|
10
10
|
import copy
|
|
11
11
|
import platform
|
|
12
|
+
import re
|
|
12
13
|
import shutil
|
|
13
14
|
import stat
|
|
14
15
|
import subprocess
|
|
15
16
|
import sys
|
|
17
|
+
import urllib.request
|
|
16
18
|
from pathlib import Path
|
|
17
19
|
from typing import Any, Callable, Dict, List, Optional
|
|
18
20
|
|
|
@@ -28,15 +30,9 @@ PIXI_URLS = {
|
|
|
28
30
|
("Windows", "AMD64"): "https://github.com/prefix-dev/pixi/releases/latest/download/pixi-x86_64-pc-windows-msvc.exe",
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
# CUDA wheels index
|
|
33
|
+
# CUDA wheels index (includes flash-attn, PyG packages, and custom wheels)
|
|
32
34
|
CUDA_WHEELS_INDEX = "https://pozzettiandrea.github.io/cuda-wheels/"
|
|
33
35
|
|
|
34
|
-
# Flash attention pre-built wheels (mjun0812)
|
|
35
|
-
FLASH_ATTN_INDEX = "https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/expanded_assets/v0.7.16"
|
|
36
|
-
|
|
37
|
-
# PyTorch Geometric wheels index
|
|
38
|
-
PYG_WHEELS_INDEX = "https://data.pyg.org/whl/"
|
|
39
|
-
|
|
40
36
|
# CUDA version -> PyTorch version mapping
|
|
41
37
|
CUDA_TORCH_MAP = {
|
|
42
38
|
"12.8": "2.8",
|
|
@@ -44,19 +40,87 @@ CUDA_TORCH_MAP = {
|
|
|
44
40
|
"12.1": "2.4",
|
|
45
41
|
}
|
|
46
42
|
|
|
43
|
+
def find_matching_wheel(package: str, torch_version: str, cuda_version: str) -> Optional[str]:
|
|
44
|
+
"""
|
|
45
|
+
Query cuda-wheels index to find a wheel matching the CUDA/torch version.
|
|
46
|
+
Returns the full version spec (e.g., "flash-attn===2.8.3+cu128torch2.8") or None.
|
|
47
|
+
"""
|
|
48
|
+
cuda_short = cuda_version.replace(".", "")[:3] # "12.8" -> "128"
|
|
49
|
+
torch_short = torch_version.replace(".", "")[:2] # "2.8" -> "28"
|
|
50
|
+
|
|
51
|
+
# Try different directory name variants
|
|
52
|
+
pkg_variants = [package, package.replace("-", "_"), package.replace("_", "-")]
|
|
53
|
+
|
|
54
|
+
for pkg_dir in pkg_variants:
|
|
55
|
+
url = f"{CUDA_WHEELS_INDEX}{pkg_dir}/"
|
|
56
|
+
try:
|
|
57
|
+
with urllib.request.urlopen(url, timeout=10) as resp:
|
|
58
|
+
html = resp.read().decode("utf-8")
|
|
59
|
+
except Exception:
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
# Parse wheel filenames from href attributes
|
|
63
|
+
# Pattern: package_name-version+localversion-cpXX-cpXX-platform.whl
|
|
64
|
+
wheel_pattern = re.compile(
|
|
65
|
+
r'href="[^"]*?([^"/]+\.whl)"',
|
|
66
|
+
re.IGNORECASE
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Local version patterns to match:
|
|
70
|
+
# flash-attn style: +cu128torch2.8
|
|
71
|
+
# PyG style: +pt28cu128
|
|
72
|
+
local_patterns = [
|
|
73
|
+
f"+cu{cuda_short}torch{torch_version}", # flash-attn style
|
|
74
|
+
f"+pt{torch_short}cu{cuda_short}", # PyG style
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
best_match = None
|
|
78
|
+
best_version = None
|
|
79
|
+
|
|
80
|
+
for match in wheel_pattern.finditer(html):
|
|
81
|
+
wheel_name = match.group(1)
|
|
82
|
+
# URL decode
|
|
83
|
+
wheel_name = wheel_name.replace("%2B", "+")
|
|
84
|
+
|
|
85
|
+
# Check if wheel matches our CUDA/torch version
|
|
86
|
+
for local_pattern in local_patterns:
|
|
87
|
+
if local_pattern in wheel_name:
|
|
88
|
+
# Extract version from wheel name
|
|
89
|
+
# Format: name-version+local-cpXX-cpXX-platform.whl
|
|
90
|
+
parts = wheel_name.split("-")
|
|
91
|
+
if len(parts) >= 2:
|
|
92
|
+
version_part = parts[1] # e.g., "2.8.3+cu128torch2.8"
|
|
93
|
+
if best_version is None or version_part > best_version:
|
|
94
|
+
best_version = version_part
|
|
95
|
+
best_match = f"{package}==={version_part}"
|
|
96
|
+
break
|
|
97
|
+
|
|
98
|
+
if best_match:
|
|
99
|
+
return best_match
|
|
100
|
+
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_package_spec(package: str, torch_version: str, cuda_version: str) -> str:
|
|
105
|
+
"""
|
|
106
|
+
Get package spec with local version for CUDA/torch compatibility.
|
|
107
|
+
Queries the index to find matching wheels dynamically.
|
|
108
|
+
"""
|
|
109
|
+
spec = find_matching_wheel(package, torch_version, cuda_version)
|
|
110
|
+
return spec if spec else package
|
|
111
|
+
|
|
47
112
|
|
|
48
113
|
def get_all_find_links(package: str, torch_version: str, cuda_version: str) -> list:
|
|
49
114
|
"""Get all find-links URLs for a CUDA package."""
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
]
|
|
115
|
+
# Try both underscore and hyphen variants since directory naming is inconsistent
|
|
116
|
+
pkg_underscore = package.replace("-", "_")
|
|
117
|
+
pkg_hyphen = package.replace("_", "-")
|
|
118
|
+
urls = [f"{CUDA_WHEELS_INDEX}{package}/"]
|
|
119
|
+
if pkg_underscore != package:
|
|
120
|
+
urls.append(f"{CUDA_WHEELS_INDEX}{pkg_underscore}/")
|
|
121
|
+
if pkg_hyphen != package:
|
|
122
|
+
urls.append(f"{CUDA_WHEELS_INDEX}{pkg_hyphen}/")
|
|
123
|
+
return urls
|
|
60
124
|
|
|
61
125
|
|
|
62
126
|
def get_current_platform() -> str:
|
|
@@ -340,6 +404,7 @@ def pixi_install(
|
|
|
340
404
|
for package in cfg.cuda_packages:
|
|
341
405
|
pip_cmd = [
|
|
342
406
|
str(python_path), "-m", "pip", "install",
|
|
407
|
+
"--no-index", # Only use --find-links, don't query PyPI
|
|
343
408
|
"--no-deps",
|
|
344
409
|
"--no-cache-dir",
|
|
345
410
|
]
|
|
@@ -348,8 +413,10 @@ def pixi_install(
|
|
|
348
413
|
for url in get_all_find_links(package, torch_version, cuda_version):
|
|
349
414
|
pip_cmd.extend(["--find-links", url])
|
|
350
415
|
|
|
351
|
-
|
|
352
|
-
|
|
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)
|
|
353
420
|
result = subprocess.run(pip_cmd, capture_output=True, text=True)
|
|
354
421
|
if result.returncode != 0:
|
|
355
422
|
log(f"CUDA package install failed for {package}:\n{result.stderr}")
|
|
@@ -40,7 +40,7 @@ _SHUTDOWN = object()
|
|
|
40
40
|
_CALL_METHOD = "call_method"
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
def _worker_loop(queue_in, queue_out, sys_path_additions=None, lib_path=None):
|
|
43
|
+
def _worker_loop(queue_in, queue_out, sys_path_additions=None, lib_path=None, env_vars=None):
|
|
44
44
|
"""
|
|
45
45
|
Worker process main loop.
|
|
46
46
|
|
|
@@ -56,11 +56,16 @@ def _worker_loop(queue_in, queue_out, sys_path_additions=None, lib_path=None):
|
|
|
56
56
|
queue_out: Output queue for sending results
|
|
57
57
|
sys_path_additions: Paths to add to sys.path
|
|
58
58
|
lib_path: Path to add to LD_LIBRARY_PATH (for conda libraries)
|
|
59
|
+
env_vars: Environment variables to set (from comfy-env.toml)
|
|
59
60
|
"""
|
|
60
61
|
import os
|
|
61
62
|
import sys
|
|
62
63
|
from pathlib import Path
|
|
63
64
|
|
|
65
|
+
# Apply env_vars FIRST (before any library imports that might check them)
|
|
66
|
+
if env_vars:
|
|
67
|
+
os.environ.update(env_vars)
|
|
68
|
+
|
|
64
69
|
# Set worker mode env var
|
|
65
70
|
os.environ["COMFYUI_ISOLATION_WORKER"] = "1"
|
|
66
71
|
|
|
@@ -424,7 +429,7 @@ class MPWorker(Worker):
|
|
|
424
429
|
interpreter without inherited state from the parent.
|
|
425
430
|
"""
|
|
426
431
|
|
|
427
|
-
def __init__(self, name: Optional[str] = None, sys_path: Optional[list] = None, lib_path: Optional[str] = None):
|
|
432
|
+
def __init__(self, name: Optional[str] = None, sys_path: Optional[list] = None, lib_path: Optional[str] = None, env_vars: Optional[dict] = None):
|
|
428
433
|
"""
|
|
429
434
|
Initialize the worker.
|
|
430
435
|
|
|
@@ -432,10 +437,12 @@ class MPWorker(Worker):
|
|
|
432
437
|
name: Optional name for logging/debugging.
|
|
433
438
|
sys_path: Optional list of paths to add to sys.path in worker process.
|
|
434
439
|
lib_path: Optional path to add to LD_LIBRARY_PATH (for conda libraries).
|
|
440
|
+
env_vars: Optional environment variables to set in worker process.
|
|
435
441
|
"""
|
|
436
442
|
self.name = name or "MPWorker"
|
|
437
443
|
self._sys_path = sys_path or []
|
|
438
444
|
self._lib_path = lib_path
|
|
445
|
+
self._env_vars = env_vars or {}
|
|
439
446
|
self._process = None
|
|
440
447
|
self._queue_in = None
|
|
441
448
|
self._queue_out = None
|
|
@@ -504,7 +511,7 @@ class MPWorker(Worker):
|
|
|
504
511
|
self._queue_out = ctx.Queue()
|
|
505
512
|
self._process = ctx.Process(
|
|
506
513
|
target=_worker_loop,
|
|
507
|
-
args=(self._queue_in, self._queue_out, self._sys_path, self._lib_path),
|
|
514
|
+
args=(self._queue_in, self._queue_out, self._sys_path, self._lib_path, self._env_vars),
|
|
508
515
|
daemon=True,
|
|
509
516
|
)
|
|
510
517
|
self._process.start()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|