cddb07a176
build container image / cpu (push) Waiting to run
build container image / cuda (push) Waiting to run
build container image / rocm (push) Waiting to run
frontend tests / frontend-tests (push) Waiting to run
openapi checks / openapi-checks (push) Waiting to run
python tests / py3.12: macos-default (push) Waiting to run
python tests / py3.11: windows-cpu (push) Waiting to run
python tests / py3.12: windows-cpu (push) Waiting to run
python tests / py3.11: linux-cpu (push) Waiting to run
python tests / py3.12: linux-cpu (push) Waiting to run
typegen checks / typegen-checks (push) Waiting to run
uv lock checks / uv-lock-checks (push) Waiting to run
frontend checks / frontend-checks (push) Waiting to run
lfs checks / lfs-check (push) Waiting to run
python checks / python-checks (push) Waiting to run
python tests / py3.11: macos-default (push) Waiting to run
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
271 lines
8.5 KiB
Python
271 lines
8.5 KiB
Python
import pytest
|
|
|
|
from invokeai.app.services.shared.graph import Graph
|
|
from invokeai.app.services.shared.workflow_graph_builder import (
|
|
UnsupportedWorkflowNodeError,
|
|
build_graph_from_workflow,
|
|
)
|
|
|
|
|
|
def _build_workflow_node(
|
|
node_id: str,
|
|
invocation_type: str,
|
|
inputs: dict[str, object],
|
|
*,
|
|
is_intermediate: bool = False,
|
|
use_cache: bool = True,
|
|
):
|
|
return {
|
|
"id": node_id,
|
|
"type": "invocation",
|
|
"position": {"x": 0, "y": 0},
|
|
"data": {
|
|
"id": node_id,
|
|
"type": invocation_type,
|
|
"version": "1.0.0",
|
|
"nodePack": "invokeai",
|
|
"label": "",
|
|
"notes": "",
|
|
"isOpen": True,
|
|
"isIntermediate": is_intermediate,
|
|
"useCache": use_cache,
|
|
"dynamicInputTemplates": {},
|
|
"inputs": {name: {"value": value} for name, value in inputs.items()},
|
|
},
|
|
}
|
|
|
|
|
|
def _build_connector_node(node_id: str):
|
|
return {
|
|
"id": node_id,
|
|
"type": "connector",
|
|
"position": {"x": 0, "y": 0},
|
|
"data": {
|
|
"id": node_id,
|
|
"type": "connector",
|
|
"label": "Connector",
|
|
"isOpen": True,
|
|
},
|
|
}
|
|
|
|
|
|
def _build_workflow(edges: list[dict], nodes: list[dict]):
|
|
return {
|
|
"name": "Child Workflow",
|
|
"author": "Tester",
|
|
"description": "",
|
|
"version": "1.0.0",
|
|
"contact": "",
|
|
"tags": "",
|
|
"notes": "",
|
|
"exposedFields": [],
|
|
"meta": {"version": "1.0.0", "category": "user"},
|
|
"nodes": nodes,
|
|
"edges": edges,
|
|
"form": None,
|
|
}
|
|
|
|
|
|
def _build_named_return_nodes():
|
|
return [
|
|
_build_workflow_node("return-value-1", "workflow_return_value", {"key": "result", "value": None}),
|
|
_build_workflow_node("return-collect-1", "collect", {"collection": []}),
|
|
_build_workflow_node("return-1", "workflow_return", {"values": []}),
|
|
]
|
|
|
|
|
|
def _build_named_return_edges(source: str, source_handle: str):
|
|
return [
|
|
{
|
|
"id": "edge-return-value",
|
|
"type": "default",
|
|
"source": source,
|
|
"sourceHandle": source_handle,
|
|
"target": "return-value-1",
|
|
"targetHandle": "value",
|
|
},
|
|
{
|
|
"id": "edge-return-collect",
|
|
"type": "default",
|
|
"source": "return-value-1",
|
|
"sourceHandle": "value",
|
|
"target": "return-collect-1",
|
|
"targetHandle": "item",
|
|
},
|
|
{
|
|
"id": "edge-return-values",
|
|
"type": "default",
|
|
"source": "return-collect-1",
|
|
"sourceHandle": "collection",
|
|
"target": "return-1",
|
|
"targetHandle": "values",
|
|
},
|
|
]
|
|
|
|
|
|
def test_build_graph_from_workflow_converts_invocation_nodes():
|
|
workflow = _build_workflow(
|
|
nodes=[
|
|
_build_workflow_node("add-1", "add", {"a": 1, "b": 2}),
|
|
_build_workflow_node("return-1", "workflow_return", {"values": []}),
|
|
],
|
|
edges=[],
|
|
)
|
|
|
|
graph = build_graph_from_workflow(workflow)
|
|
|
|
assert isinstance(graph, Graph)
|
|
assert set(graph.nodes.keys()) == {"add-1", "return-1"}
|
|
assert graph.nodes["add-1"].get_type() == "add"
|
|
assert graph.nodes["add-1"].a == 1
|
|
assert graph.nodes["add-1"].b == 2
|
|
assert graph.nodes["return-1"].get_type() == "workflow_return"
|
|
|
|
|
|
def test_build_graph_from_workflow_flattens_connector_edges():
|
|
workflow = _build_workflow(
|
|
nodes=[
|
|
_build_workflow_node("add-1", "add", {"a": 1, "b": 2}),
|
|
_build_connector_node("connector-1"),
|
|
_build_workflow_node("add-2", "add", {"a": 999, "b": 3}),
|
|
*_build_named_return_nodes(),
|
|
],
|
|
edges=[
|
|
{
|
|
"id": "edge-1",
|
|
"type": "default",
|
|
"source": "add-1",
|
|
"sourceHandle": "value",
|
|
"target": "connector-1",
|
|
"targetHandle": "in",
|
|
},
|
|
{
|
|
"id": "edge-2",
|
|
"type": "default",
|
|
"source": "connector-1",
|
|
"sourceHandle": "out",
|
|
"target": "add-2",
|
|
"targetHandle": "a",
|
|
},
|
|
*_build_named_return_edges("add-2", "value"),
|
|
],
|
|
)
|
|
|
|
graph = build_graph_from_workflow(workflow)
|
|
|
|
assert len(graph.edges) == 4
|
|
first_edge, second_edge, third_edge, fourth_edge = graph.edges
|
|
assert first_edge.source.node_id == "add-1"
|
|
assert first_edge.source.field == "value"
|
|
assert first_edge.destination.node_id == "add-2"
|
|
assert first_edge.destination.field == "a"
|
|
assert second_edge.source.node_id == "add-2"
|
|
assert second_edge.source.field == "value"
|
|
assert second_edge.destination.node_id == "return-value-1"
|
|
assert second_edge.destination.field == "value"
|
|
assert third_edge.destination.node_id == "return-collect-1"
|
|
assert third_edge.destination.field == "item"
|
|
assert fourth_edge.destination.node_id == "return-1"
|
|
assert fourth_edge.destination.field == "values"
|
|
assert graph.nodes["add-2"].a == 0
|
|
assert graph.nodes["add-2"].b == 3
|
|
assert graph.nodes["return-1"].values == []
|
|
|
|
|
|
def test_build_graph_from_workflow_uses_defaults_for_inputs_without_saved_values():
|
|
collect_node = _build_workflow_node("return-collect-1", "collect", {})
|
|
collect_node["data"]["inputs"] = {
|
|
"item": {"name": "item", "label": "", "description": ""},
|
|
"collection": {"name": "collection", "label": "", "description": ""},
|
|
}
|
|
workflow = _build_workflow(
|
|
nodes=[
|
|
_build_workflow_node("return-value-1", "workflow_return_value", {"key": "result", "value": None}),
|
|
collect_node,
|
|
_build_workflow_node("return-1", "workflow_return", {"values": []}),
|
|
],
|
|
edges=[
|
|
{
|
|
"id": "edge-return-collect",
|
|
"type": "default",
|
|
"source": "return-value-1",
|
|
"sourceHandle": "value",
|
|
"target": "return-collect-1",
|
|
"targetHandle": "item",
|
|
},
|
|
{
|
|
"id": "edge-return-values",
|
|
"type": "default",
|
|
"source": "return-collect-1",
|
|
"sourceHandle": "collection",
|
|
"target": "return-1",
|
|
"targetHandle": "values",
|
|
},
|
|
],
|
|
)
|
|
|
|
graph = build_graph_from_workflow(workflow)
|
|
|
|
assert graph.nodes["return-collect-1"].collection == []
|
|
|
|
|
|
def test_build_graph_from_workflow_uses_default_for_legacy_auto_board_values():
|
|
workflow = _build_workflow(
|
|
nodes=[
|
|
_build_workflow_node("image-1", "blank_image", {"board": "auto", "width": 64, "height": 64}),
|
|
*_build_named_return_nodes(),
|
|
],
|
|
edges=_build_named_return_edges("image-1", "image"),
|
|
)
|
|
|
|
graph = build_graph_from_workflow(workflow)
|
|
|
|
assert graph.nodes["image-1"].board is None
|
|
|
|
|
|
def test_build_graph_from_workflow_uses_default_for_legacy_none_board_values():
|
|
workflow = _build_workflow(
|
|
nodes=[
|
|
_build_workflow_node("image-1", "blank_image", {"board": "none", "width": 64, "height": 64}),
|
|
*_build_named_return_nodes(),
|
|
],
|
|
edges=_build_named_return_edges("image-1", "image"),
|
|
)
|
|
|
|
graph = build_graph_from_workflow(workflow)
|
|
|
|
assert graph.nodes["image-1"].board is None
|
|
|
|
|
|
def test_build_graph_from_workflow_rejects_batch_special_nodes_with_clear_error():
|
|
workflow = _build_workflow(
|
|
nodes=[_build_workflow_node("image-batch-1", "image_batch", {"images": []})],
|
|
edges=[],
|
|
)
|
|
|
|
with pytest.raises(UnsupportedWorkflowNodeError, match="call_saved_workflow does not yet support batch-special"):
|
|
build_graph_from_workflow(workflow)
|
|
|
|
|
|
def test_build_graph_from_workflow_rejects_workflows_without_workflow_return():
|
|
workflow = _build_workflow(
|
|
nodes=[_build_workflow_node("add-1", "add", {"a": 1, "b": 2})],
|
|
edges=[],
|
|
)
|
|
|
|
with pytest.raises(UnsupportedWorkflowNodeError, match="exactly one workflow_return"):
|
|
build_graph_from_workflow(workflow)
|
|
|
|
|
|
def test_build_graph_from_workflow_rejects_workflows_with_multiple_workflow_return_nodes():
|
|
workflow = _build_workflow(
|
|
nodes=[
|
|
_build_workflow_node("return-1", "workflow_return", {"values": []}),
|
|
_build_workflow_node("return-2", "workflow_return", {"values": []}),
|
|
],
|
|
edges=[],
|
|
)
|
|
|
|
with pytest.raises(UnsupportedWorkflowNodeError, match="exactly one workflow_return"):
|
|
build_graph_from_workflow(workflow)
|