comfy-env 0.1.14__py3-none-any.whl → 0.1.15__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.
@@ -2,8 +2,9 @@
2
2
  Isolation module for wrapping ComfyUI nodes to run in isolated environments.
3
3
  """
4
4
 
5
- from .wrap import wrap_isolated_nodes
5
+ from .wrap import wrap_isolated_nodes, wrap_nodes
6
6
 
7
7
  __all__ = [
8
8
  "wrap_isolated_nodes",
9
+ "wrap_nodes",
9
10
  ]
@@ -174,12 +174,9 @@ def _find_env_paths(node_dir: Path) -> tuple[Optional[Path], Optional[Path]]:
174
174
  marker_path = node_dir / ".comfy-env-marker.toml"
175
175
  if marker_path.exists():
176
176
  try:
177
- if sys.version_info >= (3, 11):
178
- import tomllib
179
- else:
180
- import tomli as tomllib
177
+ import tomli
181
178
  with open(marker_path, "rb") as f:
182
- marker = tomllib.load(f)
179
+ marker = tomli.load(f)
183
180
  env_path = marker.get("env", {}).get("path")
184
181
  if env_path:
185
182
  env_dir = Path(env_path)
@@ -234,12 +231,9 @@ def _find_env_dir(node_dir: Path) -> Optional[Path]:
234
231
  marker_path = node_dir / ".comfy-env-marker.toml"
235
232
  if marker_path.exists():
236
233
  try:
237
- if sys.version_info >= (3, 11):
238
- import tomllib
239
- else:
240
- import tomli as tomllib
234
+ import tomli
241
235
  with open(marker_path, "rb") as f:
242
- marker = tomllib.load(f)
236
+ marker = tomli.load(f)
243
237
  env_path = marker.get("env", {}).get("path")
244
238
  if env_path:
245
239
  env_dir = Path(env_path)
@@ -391,6 +385,131 @@ def _wrap_node_class(
391
385
  return cls
392
386
 
393
387
 
388
+ def _is_comfy_env_enabled() -> bool:
389
+ """Check if comfy-env isolation is enabled (default: True)."""
390
+ val = os.environ.get("USE_COMFY_ENV", "1").lower()
391
+ return val not in ("0", "false", "no", "off")
392
+
393
+
394
+ def wrap_nodes() -> None:
395
+ """
396
+ Auto-wrap nodes for isolation. Call from your __init__.py after defining NODE_CLASS_MAPPINGS.
397
+
398
+ Usage:
399
+ from comfy_env import wrap_nodes
400
+ wrap_nodes()
401
+ """
402
+ # Skip if isolation is disabled
403
+ if not _is_comfy_env_enabled():
404
+ print(f"[comfy-env] Isolation disabled, nodes running in main process")
405
+ return
406
+
407
+ # Skip if running inside worker subprocess
408
+ if os.environ.get("COMFYUI_ISOLATION_WORKER") == "1":
409
+ return
410
+
411
+ # Get caller's frame and module
412
+ frame = inspect.stack()[1]
413
+ caller_module = inspect.getmodule(frame.frame)
414
+ if caller_module is None:
415
+ print("[comfy-env] Warning: Could not determine caller module")
416
+ return
417
+
418
+ # Get NODE_CLASS_MAPPINGS from caller's module
419
+ node_class_mappings = getattr(caller_module, "NODE_CLASS_MAPPINGS", None)
420
+ if not node_class_mappings:
421
+ print("[comfy-env] Warning: No NODE_CLASS_MAPPINGS found in caller module")
422
+ return
423
+
424
+ # Get package root directory
425
+ caller_file = Path(frame.filename).resolve()
426
+ package_dir = caller_file.parent
427
+
428
+ # Find all comfy-env.toml files
429
+ config_files = list(package_dir.rglob("comfy-env.toml"))
430
+ if not config_files:
431
+ return # No configs, nothing to wrap
432
+
433
+ # Get ComfyUI base path
434
+ try:
435
+ import folder_paths
436
+ comfyui_base = folder_paths.base_path
437
+ except ImportError:
438
+ comfyui_base = None
439
+
440
+ # Build a map of config_dir -> env info
441
+ config_envs = []
442
+ for config_file in config_files:
443
+ config_dir = config_file.parent
444
+ env_dir = _find_env_dir(config_dir)
445
+ site_packages, lib_dir = _find_env_paths(config_dir)
446
+
447
+ if not env_dir or not site_packages:
448
+ continue
449
+
450
+ # Read env_vars from config
451
+ env_vars = {}
452
+ try:
453
+ import tomli
454
+ with open(config_file, "rb") as f:
455
+ config = tomli.load(f)
456
+ env_vars_data = config.get("env_vars", {})
457
+ env_vars = {str(k): str(v) for k, v in env_vars_data.items()}
458
+ except Exception:
459
+ pass
460
+
461
+ if comfyui_base:
462
+ env_vars["COMFYUI_BASE"] = str(comfyui_base)
463
+
464
+ config_envs.append({
465
+ "config_dir": config_dir,
466
+ "env_dir": env_dir,
467
+ "site_packages": site_packages,
468
+ "lib_dir": lib_dir,
469
+ "env_vars": env_vars,
470
+ })
471
+
472
+ if not config_envs:
473
+ return
474
+
475
+ # Match nodes to configs by checking source file location
476
+ wrapped_count = 0
477
+ for node_name, node_cls in node_class_mappings.items():
478
+ if not hasattr(node_cls, "FUNCTION"):
479
+ continue
480
+
481
+ # Get node's source file
482
+ try:
483
+ source_file = Path(inspect.getfile(node_cls)).resolve()
484
+ except (TypeError, OSError):
485
+ continue
486
+
487
+ # Find which config this node belongs to
488
+ for env_info in config_envs:
489
+ config_dir = env_info["config_dir"]
490
+ try:
491
+ source_file.relative_to(config_dir)
492
+ # Node is under this config dir - wrap it
493
+ sys_path = [str(env_info["site_packages"]), str(config_dir)]
494
+ lib_path = str(env_info["lib_dir"]) if env_info["lib_dir"] else None
495
+
496
+ _wrap_node_class(
497
+ node_cls,
498
+ env_info["env_dir"],
499
+ config_dir,
500
+ sys_path,
501
+ lib_path,
502
+ env_info["env_vars"],
503
+ )
504
+ wrapped_count += 1
505
+ break
506
+ except ValueError:
507
+ continue # Node not under this config dir
508
+
509
+ if wrapped_count > 0:
510
+ print(f"[comfy-env] Wrapped {wrapped_count} nodes for isolation")
511
+
512
+
394
513
  def wrap_isolated_nodes(
395
514
  node_class_mappings: Dict[str, type],
396
515
  nodes_dir: Path,
@@ -425,6 +544,11 @@ def wrap_isolated_nodes(
425
544
  wrap_isolated_nodes(cgal_nodes, Path(__file__).parent / "nodes/cgal")
426
545
  )
427
546
  """
547
+ # Skip if isolation is disabled
548
+ if not _is_comfy_env_enabled():
549
+ print(f"[comfy-env] Isolation disabled, nodes running in main process")
550
+ return node_class_mappings
551
+
428
552
  # Skip if running inside worker subprocess
429
553
  if os.environ.get("COMFYUI_ISOLATION_WORKER") == "1":
430
554
  return node_class_mappings
@@ -447,12 +571,9 @@ def wrap_isolated_nodes(
447
571
  # Read env_vars from comfy-env.toml
448
572
  env_vars = {}
449
573
  try:
450
- if sys.version_info >= (3, 11):
451
- import tomllib
452
- else:
453
- import tomli as tomllib
574
+ import tomli
454
575
  with open(config_file, "rb") as f:
455
- config = tomllib.load(f)
576
+ config = tomli.load(f)
456
577
  env_vars_data = config.get("env_vars", {})
457
578
  env_vars = {str(k): str(v) for k, v in env_vars_data.items()}
458
579
  except Exception:
comfy_env/nodes.py CHANGED
@@ -17,7 +17,7 @@ from pathlib import Path
17
17
  from typing import TYPE_CHECKING, Callable, List, Set
18
18
 
19
19
  if TYPE_CHECKING:
20
- from .config.types import NodeReq
20
+ from .config.parser import NodeReq
21
21
 
22
22
 
23
23
  def normalize_repo_url(repo: str) -> str:
comfy_env/pixi/core.py CHANGED
@@ -18,7 +18,7 @@ import urllib.request
18
18
  from pathlib import Path
19
19
  from typing import Any, Callable, Dict, List, Optional
20
20
 
21
- from ..config.types import ComfyEnvConfig
21
+ from ..config.parser import ComfyEnvConfig
22
22
 
23
23
 
24
24
  # Pixi download URLs by platform
@@ -37,7 +37,6 @@ CUDA_WHEELS_INDEX = "https://pozzettiandrea.github.io/cuda-wheels/"
37
37
  CUDA_TORCH_MAP = {
38
38
  "12.8": "2.8",
39
39
  "12.4": "2.4",
40
- "12.1": "2.4",
41
40
  }
42
41
 
43
42
  def find_wheel_url(
comfy_env/prestartup.py CHANGED
@@ -20,24 +20,16 @@ def get_env_name(dir_name: str) -> str:
20
20
  def _load_env_vars(config_path: str) -> Dict[str, str]:
21
21
  """
22
22
  Load [env_vars] section from comfy-env.toml.
23
-
24
- Uses tomllib (Python 3.11+) or tomli fallback.
25
23
  Returns empty dict if file not found or parsing fails.
26
24
  """
27
25
  if not os.path.exists(config_path):
28
26
  return {}
29
27
 
30
28
  try:
31
- if sys.version_info >= (3, 11):
32
- import tomllib
33
- else:
34
- try:
35
- import tomli as tomllib
36
- except ImportError:
37
- return {}
29
+ import tomli
38
30
 
39
31
  with open(config_path, "rb") as f:
40
- data = tomllib.load(f)
32
+ data = tomli.load(f)
41
33
 
42
34
  env_vars_data = data.get("env_vars", {})
43
35
  return {str(k): str(v) for k, v in env_vars_data.items()}
@@ -95,6 +87,12 @@ def _dedupe_libomp_macos():
95
87
  pass # Permission denied, etc.
96
88
 
97
89
 
90
+ def _is_comfy_env_enabled() -> bool:
91
+ """Check if comfy-env isolation is enabled (default: True)."""
92
+ val = os.environ.get("USE_COMFY_ENV", "1").lower()
93
+ return val not in ("0", "false", "no", "off")
94
+
95
+
98
96
  def setup_env(node_dir: Optional[str] = None) -> None:
99
97
  """
100
98
  Set up environment for pixi conda libraries.
@@ -112,6 +110,10 @@ def setup_env(node_dir: Optional[str] = None) -> None:
112
110
  from comfy_env import setup_env
113
111
  setup_env()
114
112
  """
113
+ # Skip if isolation is disabled
114
+ if not _is_comfy_env_enabled():
115
+ return
116
+
115
117
  # macOS: Dedupe libomp to prevent OpenMP conflicts (torch vs pymeshlab, etc.)
116
118
  _dedupe_libomp_macos()
117
119
 
@@ -137,12 +139,9 @@ def setup_env(node_dir: Optional[str] = None) -> None:
137
139
  marker_path = os.path.join(node_dir, ".comfy-env-marker.toml")
138
140
  if os.path.exists(marker_path):
139
141
  try:
140
- if sys.version_info >= (3, 11):
141
- import tomllib
142
- else:
143
- import tomli as tomllib
142
+ import tomli
144
143
  with open(marker_path, "rb") as f:
145
- marker = tomllib.load(f)
144
+ marker = tomli.load(f)
146
145
  env_path = marker.get("env", {}).get("path")
147
146
  if env_path and os.path.exists(env_path):
148
147
  pixi_env = env_path
@@ -190,3 +189,20 @@ def setup_env(node_dir: Optional[str] = None) -> None:
190
189
 
191
190
  if site_packages and os.path.exists(site_packages) and site_packages not in sys.path:
192
191
  sys.path.insert(0, site_packages)
192
+
193
+
194
+ def copy_files(src, dst, pattern: str = "*") -> None:
195
+ """Copy files matching pattern from src to dst (skip existing)."""
196
+ import shutil
197
+
198
+ src, dst = Path(src), Path(dst)
199
+ if not src.exists():
200
+ return
201
+
202
+ dst.mkdir(parents=True, exist_ok=True)
203
+ for f in src.glob(pattern):
204
+ if f.is_file():
205
+ target = dst / f.relative_to(src)
206
+ target.parent.mkdir(parents=True, exist_ok=True)
207
+ if not target.exists():
208
+ shutil.copy2(f, target)
@@ -440,7 +440,7 @@ def _watchdog():
440
440
  f.flush()
441
441
 
442
442
  # Also print
443
- print(f"\\n=== WATCHDOG TICK {tick} ===", flush=True)
443
+ print(f"\\n=== WATCHDOG TICK {tick} (debug only, don't worry) ===", flush=True)
444
444
  print(dump, flush=True)
445
445
  print("=== END ===\\n", flush=True)
446
446
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comfy-env
3
- Version: 0.1.14
3
+ Version: 0.1.15
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
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.13
19
19
  Requires-Python: >=3.10
20
20
  Requires-Dist: pip>=21.0
21
21
  Requires-Dist: tomli-w>=1.0.0
22
- Requires-Dist: tomli>=2.0.0; python_version < '3.11'
22
+ Requires-Dist: tomli>=2.0.0
23
23
  Requires-Dist: uv>=0.4.0
24
24
  Provides-Extra: dev
25
25
  Requires-Dist: mypy; extra == 'dev'
@@ -0,0 +1,31 @@
1
+ comfy_env/__init__.py,sha256=vtMEMhys8esoSXM3H5pFJGecE_ZoDwPS0foeEzlbmZA,2331
2
+ comfy_env/cache.py,sha256=LnOVGCheF0hM-JZcX_-HD46o_nA7U2SIptdOT1TQkBE,6293
3
+ comfy_env/cli.py,sha256=UJbPbCEKfAAoZ_J0JeUs4jVsWuJ8BeR6CV6XtCSuy1g,12707
4
+ comfy_env/install.py,sha256=nuG3z5V1PQ4IoXKknLfS9sUKAUq0RHcpZp7jvdOTgIY,14853
5
+ comfy_env/nodes.py,sha256=tqMBf3hTjtgbT9sdcUiNnMaiFNPsHWtw79_TgvsrU9Q,5392
6
+ comfy_env/prestartup.py,sha256=Aw8yvmkraP3bRXPTZKOFnhUHLNtGujAZ4aD76nKx6pA,7215
7
+ comfy_env/config/__init__.py,sha256=_udIO2AkntkFfktst1uKqRkLdne1cXf2b0BrErvNT_k,289
8
+ comfy_env/config/parser.py,sha256=B9iK3RQdErAHnmvD_jfZm87u5K5gTnEwFDMdOXil3YM,3458
9
+ comfy_env/isolation/__init__.py,sha256=Fy8A7_5plxcCMFOf00cJlEMbRVu8aKFkTo0Spj31YqI,206
10
+ comfy_env/isolation/wrap.py,sha256=vJz75TtS6NMyfxX_UlqNdZNQQYROHP9KbcghmNIPyFM,20046
11
+ comfy_env/pixi/__init__.py,sha256=BUrq7AQf3WDm0cHWh72B2xZbURNnDu2dCuELWiQCUiM,997
12
+ comfy_env/pixi/core.py,sha256=LY2wrvMNqO-cp9MOgixvpe1mJKtGK3U311-0rbURTBo,21770
13
+ comfy_env/pixi/cuda_detection.py,sha256=sqB3LjvGNdV4eFqiARQGfyecBM3ZiUmeh6nG0YCRYQw,9751
14
+ comfy_env/pixi/resolver.py,sha256=U_A8rBDxCj4gUlJt2YJQniP4cCKqxJEiVFgXOoH7vM8,6339
15
+ comfy_env/pixi/platform/__init__.py,sha256=Nb5MPZIEeanSMEWwqU4p4bnEKTJn1tWcwobnhq9x9IY,614
16
+ comfy_env/pixi/platform/base.py,sha256=iS0ptTTVjXRwPU4qWUdvHI7jteuzxGSjWr5BUQ7hGiU,2453
17
+ comfy_env/pixi/platform/darwin.py,sha256=HK3VkLT6DfesAnIXwx2IaUFHTBclF0xTQnC7azWY6Kc,1552
18
+ comfy_env/pixi/platform/linux.py,sha256=xLp8FEbFqZLQrzIZBI9z3C4g23Ab1ASTHLsXDzsdCoA,2062
19
+ comfy_env/pixi/platform/windows.py,sha256=FCOCgpzGzorY9-HueMlJUR8DxM2eH-cj9iZk6K026Is,10891
20
+ comfy_env/templates/comfy-env-instructions.txt,sha256=ve1RAthW7ouumU9h6DM7mIRX1MS8_Tyonq2U4tcrFu8,1031
21
+ comfy_env/templates/comfy-env.toml,sha256=ROIqi4BlPL1MEdL1VgebfTHpdwPNYGHwWeigI9Kw-1I,4831
22
+ comfy_env/workers/__init__.py,sha256=TMVG55d2XLP1mJ3x1d16H0SBDJZtk2kMC5P4HLk9TrA,1073
23
+ comfy_env/workers/base.py,sha256=4ZYTaQ4J0kBHCoO_OfZnsowm4rJCoqinZUaOtgkOPbw,2307
24
+ comfy_env/workers/mp.py,sha256=R0XWsiHv8gswxa_-iNHU14o_9Og0RFG0QnY9DRZzn2c,34060
25
+ comfy_env/workers/subprocess.py,sha256=FvhEWilFWkjYNVDm7mmbFmXiSYB7eT5Tl2uvJ_23qnc,57154
26
+ comfy_env/workers/tensor_utils.py,sha256=TCuOAjJymrSbkgfyvcKtQ_KbVWTqSwP9VH_bCaFLLq8,6409
27
+ comfy_env-0.1.15.dist-info/METADATA,sha256=HMJQ4qs9ObD6YpTGffqmUACR67_kzcWv392WpdNASzc,6946
28
+ comfy_env-0.1.15.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
29
+ comfy_env-0.1.15.dist-info/entry_points.txt,sha256=J4fXeqgxU_YenuW_Zxn_pEL7J-3R0--b6MS5t0QmAr0,49
30
+ comfy_env-0.1.15.dist-info/licenses/LICENSE,sha256=E68QZMMpW4P2YKstTZ3QU54HRQO8ecew09XZ4_Vn870,1093
31
+ comfy_env-0.1.15.dist-info/RECORD,,
comfy_env/config/types.py DELETED
@@ -1,70 +0,0 @@
1
- """Configuration types for comfy-env."""
2
-
3
- from dataclasses import dataclass, field
4
- from typing import Any, Dict, List, Optional
5
-
6
-
7
- @dataclass
8
- class NodeReq:
9
- """A node dependency (another ComfyUI custom node)."""
10
- name: str
11
- repo: str # GitHub repo, e.g., "owner/repo"
12
-
13
-
14
- @dataclass
15
- class ComfyEnvConfig:
16
- """
17
- Configuration from comfy-env.toml.
18
-
19
- comfy-env.toml is a superset of pixi.toml. Custom sections we handle:
20
- - python = "3.11" - Python version for isolated envs
21
- - [cuda] packages = [...] - CUDA packages (triggers find-links + PyTorch detection)
22
- - [node_reqs] - Other ComfyUI nodes to clone
23
-
24
- Everything else passes through to pixi.toml directly:
25
- - [dependencies] - conda packages
26
- - [pypi-dependencies] - pip packages
27
- - [target.linux-64.pypi-dependencies] - platform-specific deps
28
- - Any other pixi.toml syntax
29
-
30
- Example config:
31
- python = "3.11"
32
-
33
- [cuda]
34
- packages = ["cumesh"]
35
-
36
- [dependencies]
37
- mesalib = "*"
38
- cgal = "*"
39
-
40
- [pypi-dependencies]
41
- numpy = ">=1.21.0,<2"
42
- trimesh = { version = ">=4.0.0", extras = ["easy"] }
43
-
44
- [target.linux-64.pypi-dependencies]
45
- embreex = "*"
46
-
47
- [node_reqs]
48
- SomeNode = "owner/repo"
49
- """
50
- # python = "3.11" - Python version (for isolated envs)
51
- python: Optional[str] = None
52
-
53
- # [cuda] - CUDA packages (installed via find-links index)
54
- cuda_packages: List[str] = field(default_factory=list)
55
-
56
- # [apt] - System packages to install via apt (Linux only)
57
- apt_packages: List[str] = field(default_factory=list)
58
-
59
- # [env_vars] - Environment variables to set early (in prestartup)
60
- env_vars: Dict[str, str] = field(default_factory=dict)
61
-
62
- # [node_reqs] - other ComfyUI nodes to clone
63
- node_reqs: List[NodeReq] = field(default_factory=list)
64
-
65
- # Everything else from comfy-env.toml passes through to pixi.toml
66
- pixi_passthrough: Dict[str, Any] = field(default_factory=dict)
67
-
68
- @property
69
- def has_cuda(self) -> bool:
70
- return bool(self.cuda_packages)