comfy-env 0.0.8__py3-none-any.whl → 0.0.14__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/runner.py DELETED
@@ -1,273 +0,0 @@
1
- """
2
- Generic runner for isolated subprocess execution.
3
-
4
- This module is the entry point for subprocess execution. The runner handles
5
- requests for ANY @isolated class in the environment, importing classes on demand.
6
-
7
- Usage (Unix Domain Socket - recommended):
8
- python -m comfy_env.runner \
9
- --node-dir /path/to/ComfyUI-SAM3DObjects/nodes \
10
- --comfyui-base /path/to/ComfyUI \
11
- --import-paths ".,../vendor" \
12
- --socket /tmp/comfyui-isolation-myenv-12345.sock
13
-
14
- Usage (Legacy stdin/stdout):
15
- python -m comfy_env.runner \
16
- --node-dir /path/to/ComfyUI-SAM3DObjects/nodes \
17
- --comfyui-base /path/to/ComfyUI \
18
- --import-paths ".,../vendor"
19
-
20
- The runner:
21
- 1. Sets COMFYUI_ISOLATION_WORKER=1 (so @isolated decorator becomes no-op)
22
- 2. Adds paths to sys.path
23
- 3. Connects to Unix Domain Socket (or uses stdin/stdout)
24
- 4. Dynamically imports classes as needed (cached)
25
- 5. Calls methods and returns responses
26
- """
27
-
28
- import os
29
- import sys
30
- import json
31
- import argparse
32
- import traceback
33
- import warnings
34
- import logging
35
- import importlib
36
- from typing import Any, Dict, Optional
37
-
38
- # Suppress warnings that could interfere with JSON IPC
39
- warnings.filterwarnings("ignore")
40
- os.environ.setdefault("TF_CPP_MIN_LOG_LEVEL", "3")
41
- logging.disable(logging.WARNING)
42
-
43
- # Mark that we're in worker mode - this makes @isolated decorator a no-op
44
- os.environ["COMFYUI_ISOLATION_WORKER"] = "1"
45
-
46
-
47
- def setup_paths(node_dir: str, comfyui_base: Optional[str], import_paths: Optional[str]):
48
- """Setup sys.path for imports."""
49
- from pathlib import Path
50
-
51
- node_path = Path(node_dir)
52
-
53
- # Set COMFYUI_BASE env var for stubs to use
54
- if comfyui_base:
55
- os.environ["COMFYUI_BASE"] = comfyui_base
56
-
57
- # Add comfyui-isolation stubs directory (provides folder_paths, etc.)
58
- stubs_dir = Path(__file__).parent / "stubs"
59
- sys.path.insert(0, str(stubs_dir))
60
-
61
- # Add import paths
62
- if import_paths:
63
- for p in import_paths.split(","):
64
- p = p.strip()
65
- if p:
66
- full_path = node_path / p
67
- sys.path.insert(0, str(full_path))
68
-
69
- # Add node_dir itself
70
- sys.path.insert(0, str(node_path))
71
-
72
-
73
- def serialize_result(obj: Any) -> Any:
74
- """Serialize result for JSON transport."""
75
- from comfy_env.ipc.protocol import encode_object
76
- return encode_object(obj)
77
-
78
-
79
- def deserialize_arg(obj: Any) -> Any:
80
- """Deserialize argument from JSON transport."""
81
- from comfy_env.ipc.protocol import decode_object
82
- return decode_object(obj)
83
-
84
-
85
- # Cache for imported classes and instances
86
- _class_cache: Dict[str, type] = {}
87
- _instance_cache: Dict[str, object] = {}
88
-
89
-
90
- def get_instance(module_name: str, class_name: str) -> object:
91
- """Get or create an instance of a class."""
92
- cache_key = f"{module_name}.{class_name}"
93
-
94
- if cache_key not in _instance_cache:
95
- # Import the class if not cached
96
- if cache_key not in _class_cache:
97
- print(f"[Runner] Importing {class_name} from {module_name}...", file=sys.stderr)
98
- module = importlib.import_module(module_name)
99
- cls = getattr(module, class_name)
100
- _class_cache[cache_key] = cls
101
-
102
- # Create instance
103
- cls = _class_cache[cache_key]
104
- _instance_cache[cache_key] = cls()
105
- print(f"[Runner] Created instance of {class_name}", file=sys.stderr)
106
-
107
- return _instance_cache[cache_key]
108
-
109
-
110
- def run_worker(
111
- node_dir: str,
112
- comfyui_base: Optional[str],
113
- import_paths: Optional[str],
114
- socket_path: Optional[str] = None,
115
- ):
116
- """
117
- Main worker loop - handles JSON-RPC requests via transport.
118
-
119
- Args:
120
- node_dir: Path to node package directory
121
- comfyui_base: Path to ComfyUI base directory
122
- import_paths: Comma-separated import paths
123
- socket_path: Unix domain socket path (if None, uses stdin/stdout)
124
- """
125
- from comfy_env.ipc.transport import UnixSocketTransport, StdioTransport
126
-
127
- # Setup paths first
128
- setup_paths(node_dir, comfyui_base, import_paths)
129
-
130
- # Create transport
131
- if socket_path:
132
- # Unix Domain Socket transport (recommended)
133
- print(f"[Runner] Connecting to socket: {socket_path}", file=sys.stderr)
134
- transport = UnixSocketTransport.connect(socket_path)
135
- use_uds = True
136
- else:
137
- # Legacy stdin/stdout transport
138
- print("[Runner] Using stdin/stdout transport", file=sys.stderr)
139
- transport = StdioTransport()
140
- use_uds = False
141
-
142
- try:
143
- # Send ready signal
144
- transport.send({"status": "ready"})
145
-
146
- # Main loop - read requests, execute, respond
147
- while True:
148
- response = {"jsonrpc": "2.0", "id": None}
149
-
150
- try:
151
- request = transport.recv()
152
- response["id"] = request.get("id")
153
-
154
- method_name = request.get("method")
155
- params = request.get("params", {})
156
-
157
- if method_name == "shutdown":
158
- # Clean shutdown
159
- response["result"] = {"status": "shutdown"}
160
- transport.send(response)
161
- break
162
-
163
- # Get module/class from request
164
- module_name = request.get("module")
165
- class_name = request.get("class")
166
-
167
- if not module_name or not class_name:
168
- response["error"] = {
169
- "code": -32602,
170
- "message": "Missing 'module' or 'class' in request",
171
- }
172
- transport.send(response)
173
- continue
174
-
175
- # Get or create instance
176
- try:
177
- instance = get_instance(module_name, class_name)
178
- except Exception as e:
179
- response["error"] = {
180
- "code": -32000,
181
- "message": f"Failed to import {module_name}.{class_name}: {e}",
182
- "data": {"traceback": traceback.format_exc()}
183
- }
184
- transport.send(response)
185
- continue
186
-
187
- # Get the method
188
- method = getattr(instance, method_name, None)
189
- if method is None:
190
- response["error"] = {
191
- "code": -32601,
192
- "message": f"Method not found: {method_name}",
193
- }
194
- transport.send(response)
195
- continue
196
-
197
- # Deserialize arguments
198
- deserialized_params = {}
199
- for key, value in params.items():
200
- deserialized_params[key] = deserialize_arg(value)
201
-
202
- # For legacy stdio transport, redirect stdout to stderr during execution
203
- # This prevents print() in node code from corrupting JSON protocol
204
- # (UDS transport doesn't need this since it uses a separate socket)
205
- if not use_uds:
206
- original_stdout = sys.stdout
207
- sys.stdout = sys.stderr
208
-
209
- # Also redirect at file descriptor level for C libraries
210
- stdout_fd = original_stdout.fileno()
211
- stderr_fd = sys.stderr.fileno()
212
- stdout_fd_copy = os.dup(stdout_fd)
213
- os.dup2(stderr_fd, stdout_fd)
214
-
215
- # Call the method
216
- print(f"[Runner] Calling {class_name}.{method_name}...", file=sys.stderr)
217
- try:
218
- result = method(**deserialized_params)
219
- finally:
220
- if not use_uds:
221
- # Restore file descriptor first, then Python stdout
222
- os.dup2(stdout_fd_copy, stdout_fd)
223
- os.close(stdout_fd_copy)
224
- sys.stdout = original_stdout
225
-
226
- # Serialize result
227
- serialized_result = serialize_result(result)
228
- response["result"] = serialized_result
229
-
230
- print(f"[Runner] {class_name}.{method_name} completed", file=sys.stderr)
231
-
232
- except ConnectionError as e:
233
- # Socket closed - normal shutdown
234
- print(f"[Runner] Connection closed: {e}", file=sys.stderr)
235
- break
236
- except Exception as e:
237
- tb = traceback.format_exc()
238
- print(f"[Runner] Error: {e}", file=sys.stderr)
239
- print(tb, file=sys.stderr)
240
- response["error"] = {
241
- "code": -32000,
242
- "message": str(e),
243
- "data": {"traceback": tb}
244
- }
245
-
246
- try:
247
- transport.send(response)
248
- except ConnectionError:
249
- break
250
-
251
- finally:
252
- transport.close()
253
-
254
-
255
- def main():
256
- parser = argparse.ArgumentParser(description="Isolated node runner")
257
- parser.add_argument("--node-dir", required=True, help="Node package directory")
258
- parser.add_argument("--comfyui-base", help="ComfyUI base directory")
259
- parser.add_argument("--import-paths", help="Comma-separated import paths")
260
- parser.add_argument("--socket", help="Unix domain socket path (if not provided, uses stdin/stdout)")
261
-
262
- args = parser.parse_args()
263
-
264
- run_worker(
265
- node_dir=args.node_dir,
266
- comfyui_base=args.comfyui_base,
267
- import_paths=args.import_paths,
268
- socket_path=args.socket,
269
- )
270
-
271
-
272
- if __name__ == "__main__":
273
- main()