136 lines
5.8 KiB
Python
136 lines
5.8 KiB
Python
"""
|
|
Custom API routes for the Subworkflow extension.
|
|
"""
|
|
import asyncio
|
|
import json
|
|
import logging
|
|
from aiohttp import web
|
|
from .debug_utils import configure_logger
|
|
from .workflow_utils import (
|
|
load_workflow_file,
|
|
load_workflow_url,
|
|
get_workflow_interface,
|
|
list_workflow_files,
|
|
is_ui_format,
|
|
)
|
|
|
|
|
|
log = configure_logger("ComfyUI-Subworkflow")
|
|
|
|
|
|
def _query_bool(value: str | None, default: bool = True) -> bool:
|
|
if value is None:
|
|
return default
|
|
return str(value).strip().lower() not in {"0", "false", "no", "off"}
|
|
|
|
|
|
def setup_routes():
|
|
try:
|
|
from server import PromptServer
|
|
except ImportError:
|
|
log.warning("[Subworkflow] route setup skipped: PromptServer import failed")
|
|
return
|
|
routes = PromptServer.instance.routes
|
|
log.debug("[Subworkflow] route setup registering /subworkflow/info and /subworkflow/list")
|
|
|
|
async def get_workflow_info(request: web.Request):
|
|
"""
|
|
Returns the Subworkflow Input / Subworkflow Output slot info for a workflow.
|
|
Response: {"inputs": [...], "outputs": [...], "error": null | str}
|
|
"""
|
|
source = request.rel_url.query.get("source", "file")
|
|
workflow_name = request.rel_url.query.get("workflow", "")
|
|
url = request.rel_url.query.get("url", "")
|
|
verify_ssl = _query_bool(request.rel_url.query.get("verify_ssl"), True)
|
|
source_value = url if source == "url" else workflow_name
|
|
log.debug(
|
|
"[Subworkflow] route info requested path=%r query=%s source=%r value=%r verify_ssl=%s",
|
|
str(request.rel_url),
|
|
dict(request.rel_url.query),
|
|
source,
|
|
source_value,
|
|
verify_ssl,
|
|
)
|
|
if source == "url":
|
|
if not url:
|
|
log.warning("[Subworkflow] route URL info request missing URL")
|
|
return web.json_response({"inputs": [], "outputs": [], "modifiers": [], "error": "No workflow URL specified"})
|
|
log.debug("[Subworkflow] route selected URL loader for %r verify_ssl=%s", url, verify_ssl)
|
|
loader = load_workflow_url
|
|
elif source == "file":
|
|
if not workflow_name:
|
|
log.warning("[Subworkflow] route info request missing workflow name")
|
|
return web.json_response({"inputs": [], "outputs": [], "modifiers": [], "error": "No workflow specified"})
|
|
log.debug("[Subworkflow] route selected file loader for %r", workflow_name)
|
|
loader = load_workflow_file
|
|
else:
|
|
log.warning("[Subworkflow] route unsupported source=%r", source)
|
|
return web.json_response({"inputs": [], "outputs": [], "modifiers": [], "error": f"Unsupported source: {source}"})
|
|
|
|
try:
|
|
if source == "url":
|
|
log.debug(
|
|
"[Subworkflow] route dispatching URL load to worker thread for %r verify_ssl=%s",
|
|
source_value,
|
|
verify_ssl,
|
|
)
|
|
data = await asyncio.to_thread(loader, source_value, verify_ssl)
|
|
else:
|
|
log.debug("[Subworkflow] route loading file workflow synchronously for %r", source_value)
|
|
data = loader(source_value)
|
|
log.debug(
|
|
"[Subworkflow] route loader returned source=%r value=%r type=%s keys=%s",
|
|
source,
|
|
source_value,
|
|
type(data).__name__,
|
|
sorted(data.keys())[:12] if isinstance(data, dict) else None,
|
|
)
|
|
except FileNotFoundError:
|
|
log.warning("[Subworkflow] route workflow file not found: %r", source_value)
|
|
return web.json_response({"inputs": [], "outputs": [], "modifiers": [], "error": f"File not found: {source_value}"})
|
|
except json.JSONDecodeError as e:
|
|
log.warning("[Subworkflow] route invalid JSON in workflow %r: %s", source_value, e)
|
|
return web.json_response({"inputs": [], "outputs": [], "modifiers": [], "error": f"Invalid JSON: {e}"})
|
|
except UnicodeDecodeError as e:
|
|
log.warning("[Subworkflow] route invalid UTF-8 in workflow %r: %s", source_value, e)
|
|
return web.json_response({"inputs": [], "outputs": [], "modifiers": [], "error": f"Invalid UTF-8: {e}"})
|
|
except ValueError as e:
|
|
log.warning("[Subworkflow] route failed to load workflow %r: %s", source_value, e)
|
|
return web.json_response({"inputs": [], "outputs": [], "modifiers": [], "error": str(e)})
|
|
|
|
try:
|
|
interface = get_workflow_interface(data)
|
|
except Exception as e:
|
|
log.exception("[Subworkflow] route failed to discover I/O for workflow %r", source_value)
|
|
return web.json_response({
|
|
"inputs": [],
|
|
"outputs": [],
|
|
"modifiers": [],
|
|
"error": f"Failed to discover workflow I/O: {e}",
|
|
})
|
|
log.debug(
|
|
"[Subworkflow] route source=%r workflow=%r format=%s inputs=%s outputs=%s modifiers=%s",
|
|
source,
|
|
source_value,
|
|
"UI" if is_ui_format(data) else "API",
|
|
interface["inputs"],
|
|
interface["outputs"],
|
|
interface["modifiers"],
|
|
)
|
|
return web.json_response({
|
|
"inputs": interface["inputs"],
|
|
"outputs": interface["outputs"],
|
|
"modifiers": interface["modifiers"],
|
|
"error": None,
|
|
})
|
|
|
|
routes.get("/subworkflow/info")(get_workflow_info)
|
|
|
|
async def list_workflows(request: web.Request):
|
|
"""Returns the available workflow file names."""
|
|
workflows = list_workflow_files()
|
|
log.debug("[Subworkflow] route list requested, returning %d workflow option(s)", len(workflows))
|
|
return web.json_response(workflows)
|
|
|
|
routes.get("/subworkflow/list")(list_workflows)
|