Files

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)