Spaces:
Running on Zero
Running on Zero
feat(backend): ComfyUILibraryBackend skeleton + event dataclasses
Browse files- backend.py +86 -0
- tests/test_backend.py +13 -0
backend.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ComfyUI library-mode backend.
|
| 2 |
+
|
| 3 |
+
Single-process, single-implementation. The @spaces.GPU decorator is the only
|
| 4 |
+
divergence between local and HF Spaces deployment.
|
| 5 |
+
"""
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
import pathlib
|
| 10 |
+
import sys
|
| 11 |
+
from collections.abc import AsyncIterator
|
| 12 |
+
from dataclasses import dataclass, field
|
| 13 |
+
from typing import Any, Optional
|
| 14 |
+
|
| 15 |
+
import models
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@dataclass
|
| 19 |
+
class DownloadEvent:
|
| 20 |
+
filename: str
|
| 21 |
+
mb_done: float
|
| 22 |
+
mb_total: float
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@dataclass
|
| 26 |
+
class ProgressEvent:
|
| 27 |
+
stage: int
|
| 28 |
+
stage_label: str
|
| 29 |
+
step: int
|
| 30 |
+
total_steps: int
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@dataclass
|
| 34 |
+
class OutputEvent:
|
| 35 |
+
video_path: str
|
| 36 |
+
audio_path: Optional[str] = None
|
| 37 |
+
meta: dict = field(default_factory=dict)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@dataclass
|
| 41 |
+
class ErrorEvent:
|
| 42 |
+
category: str # "oom" | "zerogpu_timeout" | "execution" | "interrupt" | "download"
|
| 43 |
+
message: str
|
| 44 |
+
stage: Optional[int] = None
|
| 45 |
+
traceback: str = ""
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _on_spaces() -> bool:
|
| 49 |
+
return bool(os.environ.get("SPACES_ZERO_GPU"))
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _comfy_dir() -> pathlib.Path:
|
| 53 |
+
if _on_spaces():
|
| 54 |
+
return pathlib.Path("/data/comfyui")
|
| 55 |
+
return pathlib.Path(__file__).parent / "comfyui"
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
class ComfyUILibraryBackend:
|
| 59 |
+
"""Wraps PromptExecutor for in-process workflow execution."""
|
| 60 |
+
|
| 61 |
+
def __init__(self) -> None:
|
| 62 |
+
self._comfy_dir = _comfy_dir()
|
| 63 |
+
if not self._comfy_dir.exists():
|
| 64 |
+
raise RuntimeError(
|
| 65 |
+
f"ComfyUI not found at {self._comfy_dir}. "
|
| 66 |
+
f"Local: run `bash setup.sh`. Spaces: see app.py:_bootstrap()."
|
| 67 |
+
)
|
| 68 |
+
if str(self._comfy_dir) not in sys.path:
|
| 69 |
+
sys.path.insert(0, str(self._comfy_dir))
|
| 70 |
+
|
| 71 |
+
# Defer comfy imports until the path is set up.
|
| 72 |
+
# NOTE: ComfyUI ships PromptExecutor in the top-level `execution.py`
|
| 73 |
+
# module, NOT under `comfy.execution`. Same for `nodes`. Both must be
|
| 74 |
+
# imported AFTER the sys.path insert above.
|
| 75 |
+
import asyncio
|
| 76 |
+
|
| 77 |
+
import comfy.cli_args # noqa: F401 — side-effect: registers CLI flags
|
| 78 |
+
import execution # top-level module — provides PromptExecutor
|
| 79 |
+
import nodes # top-level module — provides init_extra_nodes (async)
|
| 80 |
+
|
| 81 |
+
# init_extra_nodes is an async function in modern ComfyUI; run it once.
|
| 82 |
+
asyncio.run(nodes.init_extra_nodes()) # discover custom_nodes/
|
| 83 |
+
self._executor = execution.PromptExecutor(server_instance=None)
|
| 84 |
+
|
| 85 |
+
def __repr__(self) -> str:
|
| 86 |
+
return f"ComfyUILibraryBackend(comfy_dir={self._comfy_dir!r})"
|
tests/test_backend.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Backend tests — most are smoke / structural since the real work is GPU."""
|
| 2 |
+
import backend
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def test_backend_class_exists():
|
| 6 |
+
assert hasattr(backend, "ComfyUILibraryBackend")
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def test_progress_event_dataclasses_exist():
|
| 10 |
+
assert hasattr(backend, "DownloadEvent")
|
| 11 |
+
assert hasattr(backend, "ProgressEvent")
|
| 12 |
+
assert hasattr(backend, "OutputEvent")
|
| 13 |
+
assert hasattr(backend, "ErrorEvent")
|