Workflow1111 / test_pipelines.py
ysharma's picture
ysharma HF Staff
Workflow1111 — Automatic1111-style diffusion studio on gr.Workflow
db59f73 verified
Raw
History Blame Contribute Delete
8.16 kB
"""
End-to-end test: run every output in workflow.json through the real
`WorkflowExecutor` — the same code path the canvas and the REST API use.
python apps/05_workflow1111/test_pipelines.py # everything
python apps/05_workflow1111/test_pipelines.py local # only offline nodes
python apps/05_workflow1111/test_pipelines.py grid image # substring filters
Hits Hugging Face for the `model`/`space` nodes, so it costs quota and takes a
couple of minutes. Rendered outputs are written to ./_test_output for eyeballing.
"""
import base64
import inspect
import json
import logging
import os
import sys
import time
import types
import urllib.request
import warnings
warnings.filterwarnings("ignore")
logging.disable(logging.CRITICAL)
for _s in (sys.stdout, sys.stderr):
try:
_s.reconfigure(encoding="utf-8", errors="replace")
except (AttributeError, ValueError):
pass
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import gradio.workflow as W # noqa: E402
from gradio.helpers import special_args # noqa: E402
from gradio.workflow_api import ( # noqa: E402
WorkflowExecutor,
WorkflowGraph,
group_free_inputs,
subject_groups,
)
from huggingface_hub import get_token # noqa: E402
import nodes as N # noqa: E402
TOKEN = types.SimpleNamespace(token=get_token() or os.environ.get("HF_TOKEN"))
OUTDIR = os.path.join(HERE, "_test_output")
os.makedirs(OUTDIR, exist_ok=True)
# A real photograph — DETR/ViT/the VLM need actual content to say anything about.
SAMPLE_URL = "https://huggingface.co/datasets/mishig/sample_images/resolve/main/tiger.jpg"
SAMPLE = os.path.join(OUTDIR, "_sample.jpg")
if not os.path.exists(SAMPLE):
urllib.request.urlretrieve(SAMPLE_URL, SAMPLE) # noqa: S310
# An image that carries embedded generation parameters, for the PNG Info pipeline.
STAMPED = os.path.join(OUTDIR, "_stamped.png")
if not os.path.exists(STAMPED):
_info = N.generation_info("a red fox in snow", "blurry, watermark", 8, 3.5,
987654, 1024, 768, "black-forest-labs/FLUX.1-schnell")
_uri = N.postprocess(N._emit(N._load_image(SAMPLE)), 1, "Lanczos", 0, 1, 1, 1,
0, 0, 0, "", _info)
with open(STAMPED, "wb") as f:
f.write(base64.b64decode(_uri.partition(",")[2]))
def call_fn(data, request=None, token=None):
"""Mirror of gradio's bound-function server fn (workflow.py `call_fn`)."""
name = data[0] if data else ""
fn = N.BIND.get(name)
if fn is None:
return json.dumps({"error": f"No function '{name}' bound"})
try:
args = json.loads(data[1] if len(data) > 1 else "[]")
if not isinstance(args, list):
args = [args]
# gradio injects OAuthToken/Request params before calling — mirror that
# so `txt2img` receives its token exactly as it will in the app.
args, *_ = special_args(fn, args, request, None, token=token)
result = fn(*args)
return json.dumps(list(result) if isinstance(result, (list, tuple)) else [result])
except Exception as e:
return json.dumps({"error": f"{type(e).__name__}: {e}"})
CALLERS = {"fn": call_fn, "model": W.call_model,
"space": W.call_space, "dataset": W.fetch_dataset}
with open(os.path.join(HERE, "workflow.json"), encoding="utf-8") as f:
GRAPH = WorkflowGraph.from_json(f.read())
# Values fed to reference nodes. Anything not listed falls back to the node's
# own default (`data.out`), or to the sample image for media ports.
OVERRIDES = {
"ref_interrogate_image": SAMPLE,
"ref_detect_image": SAMPLE,
"ref_extras_image": SAMPLE,
"ref_control_image": SAMPLE,
"ref_init_image": SAMPLE,
"ref_pnginfo_image": STAMPED,
}
# Subjects whose upstream is entirely local — these must pass with no network.
LOCAL_ONLY = {"sub_control", "sub_png_report", "sub_png_fields",
"sub_upscaled", "sub_upscale_report"}
def seed_for(subject_id):
node = GRAPH.node_by_id[subject_id]
inputs = {}
# free_inputs yields {"node", "port", "type", "label"} wrappers, not the
# reference nodes themselves.
for free in group_free_inputs(GRAPH, [node]):
ref = free["node"]
rid = ref["id"]
if rid in OVERRIDES:
inputs[rid] = OVERRIDES[rid]
continue
default = (ref.get("data") or {}).get("out")
if default in (None, "") and free["type"] in ("image", "audio", "video"):
default = SAMPLE
inputs[rid] = default
return inputs
def save(subject_id, value):
"""Persist an output so it can actually be looked at; return a summary."""
if isinstance(value, str) and value.startswith("data:"):
header, _, payload = value.partition(",")
ext = "png" if "png" in header else "jpg"
path = os.path.join(OUTDIR, f"{subject_id}.{ext}")
raw = base64.b64decode(payload)
with open(path, "wb") as f:
f.write(raw)
from PIL import Image
with Image.open(path) as im:
return f"image {im.width}×{im.height} {ext.upper()}, {len(raw) // 1024} KB"
if isinstance(value, str) and os.path.isfile(value):
from PIL import Image
try:
with Image.open(value) as im:
dst = os.path.join(OUTDIR, f"{subject_id}.png")
im.convert("RGBA" if im.mode in ("RGBA", "LA", "P") else "RGB").save(dst)
return f"image {im.width}×{im.height} (file)"
except Exception:
return f"file {os.path.basename(value)}"
if isinstance(value, (dict, list)):
path = os.path.join(OUTDIR, f"{subject_id}.json")
with open(path, "w", encoding="utf-8") as f:
json.dump(value, f, indent=2, ensure_ascii=False)
return f"{type(value).__name__} ({len(value)} entries)"
text = str(value)
path = os.path.join(OUTDIR, f"{subject_id}.txt")
with open(path, "w", encoding="utf-8") as f:
f.write(text)
return f"text ({len(text)} chars): {text.splitlines()[0][:76] if text.strip() else '(empty)'}"
def main():
args = [a.lower() for a in sys.argv[1:]]
local_only = "local" in args
filters = [a for a in args if a != "local"]
executor = WorkflowExecutor(GRAPH, CALLERS)
targets = [s["id"] for s in GRAPH.subjects]
if local_only:
targets = [t for t in targets if t in LOCAL_ONLY]
if filters:
targets = [t for t in targets
if any(f in t.lower() or f in GRAPH.node_by_id[t]["label"].lower()
for f in filters)]
if not targets:
print("no subjects matched")
return 1
print(f"\nRunning {len(targets)} output(s) through WorkflowExecutor")
print(f"outputs → {os.path.relpath(OUTDIR, os.getcwd())}\n")
passed, failed = [], []
for sid in targets:
label = GRAPH.node_by_id[sid]["label"]
t0 = time.time()
try:
value = executor.run(sid, seed_for(sid), request=None, token=TOKEN)
if value is None or value == "":
raise AssertionError("output was empty")
summary = save(sid, value)
except Exception as e:
failed.append((sid, f"{type(e).__name__}: {e}"))
print(f" FAIL {label:28} ({time.time() - t0:6.1f}s) "
f"{type(e).__name__}: {str(e)[:130]}")
else:
passed.append(sid)
print(f" ok {label:28} ({time.time() - t0:6.1f}s) {summary}")
sys.stdout.flush()
print("\n── API surface (one endpoint per subject group) ──")
for group in subject_groups(GRAPH):
names = ", ".join(s["label"] for s in group)
params = ", ".join(f"{f['label']} ({f['type']})"
for f in group_free_inputs(GRAPH, group))
print(f" • {names}\n inputs: {params or '(none)'}")
print(f"\n{'=' * 66}\n {len(passed)} passed, {len(failed)} failed\n{'=' * 66}")
for sid, err in failed:
print(f" {sid}: {err}")
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main())