griptape-nodes 0.65.1__py3-none-any.whl → 0.65.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- griptape_nodes/common/node_executor.py +14 -14
- griptape_nodes/exe_types/node_groups/__init__.py +6 -0
- griptape_nodes/exe_types/node_groups/base_node_group.py +31 -0
- griptape_nodes/exe_types/node_groups/subflow_node_group.py +1007 -0
- griptape_nodes/exe_types/node_types.py +0 -805
- griptape_nodes/machines/control_flow.py +4 -4
- griptape_nodes/node_library/library_registry.py +1 -0
- griptape_nodes/node_library/workflow_registry.py +1 -1
- griptape_nodes/retained_mode/events/flow_events.py +1 -1
- griptape_nodes/retained_mode/events/node_events.py +11 -77
- griptape_nodes/retained_mode/managers/flow_manager.py +72 -79
- griptape_nodes/retained_mode/managers/node_manager.py +113 -193
- griptape_nodes/retained_mode/managers/workflow_manager.py +5 -8
- griptape_nodes/version_compatibility/versions/v0_63_8/deprecated_nodegroup_parameters.py +2 -2
- {griptape_nodes-0.65.1.dist-info → griptape_nodes-0.65.3.dist-info}/METADATA +1 -1
- {griptape_nodes-0.65.1.dist-info → griptape_nodes-0.65.3.dist-info}/RECORD +18 -15
- {griptape_nodes-0.65.1.dist-info → griptape_nodes-0.65.3.dist-info}/WHEEL +1 -1
- {griptape_nodes-0.65.1.dist-info → griptape_nodes-0.65.3.dist-info}/entry_points.txt +0 -0
|
@@ -6,10 +6,10 @@ from dataclasses import dataclass
|
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
from griptape_nodes.exe_types.base_iterative_nodes import BaseIterativeStartNode
|
|
9
|
+
from griptape_nodes.exe_types.node_groups import SubflowNodeGroup
|
|
9
10
|
from griptape_nodes.exe_types.node_types import (
|
|
10
11
|
LOCAL_EXECUTION,
|
|
11
12
|
BaseNode,
|
|
12
|
-
NodeGroupNode,
|
|
13
13
|
NodeResolutionState,
|
|
14
14
|
)
|
|
15
15
|
from griptape_nodes.machines.fsm import FSM, State
|
|
@@ -209,7 +209,7 @@ def _resolve_target_node_for_control_flow(next_node_info: NextNodeInfo) -> tuple
|
|
|
209
209
|
entry_parameter = next_node_info.entry_parameter
|
|
210
210
|
|
|
211
211
|
# Check if node has a parent and if parent is not local execution
|
|
212
|
-
if target_node.parent_group is not None and isinstance(target_node.parent_group,
|
|
212
|
+
if target_node.parent_group is not None and isinstance(target_node.parent_group, SubflowNodeGroup):
|
|
213
213
|
parent_group = target_node.parent_group
|
|
214
214
|
execution_env = parent_group.get_parameter_value(parent_group.execution_environment.name)
|
|
215
215
|
if execution_env != LOCAL_EXECUTION:
|
|
@@ -345,7 +345,7 @@ class ControlFlowMachine(FSM[ControlFlowContext]):
|
|
|
345
345
|
current_nodes = await self._process_nodes_for_dag(start_node)
|
|
346
346
|
else:
|
|
347
347
|
current_nodes = [start_node]
|
|
348
|
-
if isinstance(start_node.parent_group,
|
|
348
|
+
if isinstance(start_node.parent_group, SubflowNodeGroup):
|
|
349
349
|
# In sequential mode, we aren't going to run this. Just continue.
|
|
350
350
|
node = GriptapeNodes.FlowManager().get_next_node_from_execution_queue()
|
|
351
351
|
if node is not None:
|
|
@@ -473,8 +473,8 @@ class ControlFlowMachine(FSM[ControlFlowContext]):
|
|
|
473
473
|
node.state = NodeResolutionState.UNRESOLVED
|
|
474
474
|
# Use proxy node if this node is part of a group, otherwise use original node
|
|
475
475
|
node_to_add = node
|
|
476
|
-
disconnected = True
|
|
477
476
|
# Only add if not already added (proxy might already be in DAG)
|
|
477
|
+
disconnected = True
|
|
478
478
|
if node_to_add.name not in dag_builder.node_to_reference:
|
|
479
479
|
# Now, we need to create the DAG, but it can't be queued or used until it's dependencies have been resolved.
|
|
480
480
|
# Figure out which graph the data node belongs to, if it belongs to a graph.
|
|
@@ -456,7 +456,7 @@ class PackageNodesAsSerializedFlowRequest(RequestPayload):
|
|
|
456
456
|
entry_control_node_name: str | None = None
|
|
457
457
|
entry_control_parameter_name: str | None = None
|
|
458
458
|
output_parameter_prefix: str = "packaged_node_"
|
|
459
|
-
node_group_name: str | None = None # Name of the
|
|
459
|
+
node_group_name: str | None = None # Name of the SubflowNodeGroup if packaging a group
|
|
460
460
|
|
|
461
461
|
|
|
462
462
|
@dataclass
|
|
@@ -55,6 +55,7 @@ class CreateNodeRequest(RequestPayload):
|
|
|
55
55
|
initial_setup: Skip setup work when loading from file (defaults to False)
|
|
56
56
|
set_as_new_context: Set this node as current context after creation (defaults to False)
|
|
57
57
|
create_error_proxy_on_failure: Create Error Proxy node if creation fails (defaults to True)
|
|
58
|
+
node_names_to_add: List of existing node names to add to this node after creation (used by SubflowNodeGroup, defaults to None)
|
|
58
59
|
|
|
59
60
|
Results: CreateNodeResultSuccess (with assigned name) | CreateNodeResultFailure (invalid type, missing library, flow not found)
|
|
60
61
|
"""
|
|
@@ -72,6 +73,8 @@ class CreateNodeRequest(RequestPayload):
|
|
|
72
73
|
set_as_new_context: bool = False
|
|
73
74
|
# When True, create an Error Proxy node if the requested node type fails to create
|
|
74
75
|
create_error_proxy_on_failure: bool = True
|
|
76
|
+
# List of node names to add to this node after creation (used by SubflowNodeGroup)
|
|
77
|
+
node_names_to_add: list[str] | None = None
|
|
75
78
|
|
|
76
79
|
|
|
77
80
|
@dataclass
|
|
@@ -102,6 +105,12 @@ class CreateNodeResultFailure(ResultPayloadFailure):
|
|
|
102
105
|
"""
|
|
103
106
|
|
|
104
107
|
|
|
108
|
+
# Backwards compatibility for workflows that use the deprecated CreateNodeGroupRequest
|
|
109
|
+
@dataclass
|
|
110
|
+
class CreateNodeGroupRequest:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
|
|
105
114
|
@dataclass
|
|
106
115
|
@PayloadRegistry.register
|
|
107
116
|
class DeleteNodeRequest(RequestPayload):
|
|
@@ -443,10 +452,11 @@ class SerializedNodeCommands:
|
|
|
443
452
|
set_parameter_value_command: SetParameterValueRequest
|
|
444
453
|
unique_value_uuid: SerializedNodeCommands.UniqueParameterValueUUID
|
|
445
454
|
|
|
446
|
-
create_node_command: CreateNodeRequest
|
|
455
|
+
create_node_command: CreateNodeRequest
|
|
447
456
|
element_modification_commands: list[RequestPayload]
|
|
448
457
|
node_dependencies: NodeDependencies
|
|
449
458
|
lock_node_command: SetLockNodeStateRequest | None = None
|
|
459
|
+
is_node_group: bool = False
|
|
450
460
|
node_uuid: NodeUUID = field(default_factory=lambda: SerializedNodeCommands.NodeUUID(str(uuid4())))
|
|
451
461
|
|
|
452
462
|
|
|
@@ -986,82 +996,6 @@ class RemoveNodeFromNodeGroupResultFailure(ResultPayloadFailure):
|
|
|
986
996
|
"""
|
|
987
997
|
|
|
988
998
|
|
|
989
|
-
@dataclass
|
|
990
|
-
@PayloadRegistry.register
|
|
991
|
-
class CreateNodeGroupRequest(RequestPayload):
|
|
992
|
-
"""Create a new NodeGroup node.
|
|
993
|
-
|
|
994
|
-
Use when: Need to create a new NodeGroup for organizing nodes, building workflows
|
|
995
|
-
with grouped nodes programmatically.
|
|
996
|
-
|
|
997
|
-
Args:
|
|
998
|
-
node_group_name: Desired name for the NodeGroup node (None for auto-generated)
|
|
999
|
-
flow_name: Optional flow name to create the NodeGroup in (None for current context flow)
|
|
1000
|
-
metadata: Initial metadata for the NodeGroup (position, display properties)
|
|
1001
|
-
|
|
1002
|
-
Results: CreateNodeGroupResultSuccess (with assigned name) | CreateNodeGroupResultFailure (creation failed)
|
|
1003
|
-
"""
|
|
1004
|
-
|
|
1005
|
-
node_group_name: str | None = None
|
|
1006
|
-
flow_name: str | None = None
|
|
1007
|
-
metadata: dict | None = None
|
|
1008
|
-
node_names_to_add: list[str] = field(default_factory=list)
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
@dataclass
|
|
1012
|
-
@PayloadRegistry.register
|
|
1013
|
-
class CreateNodeGroupResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
|
|
1014
|
-
"""NodeGroup created successfully. NodeGroup is now available for adding nodes.
|
|
1015
|
-
|
|
1016
|
-
Args:
|
|
1017
|
-
node_group_name: Final assigned name (may differ from requested)
|
|
1018
|
-
"""
|
|
1019
|
-
|
|
1020
|
-
node_group_name: str
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
@dataclass
|
|
1024
|
-
@PayloadRegistry.register
|
|
1025
|
-
class CreateNodeGroupResultFailure(ResultPayloadFailure):
|
|
1026
|
-
"""NodeGroup creation failed.
|
|
1027
|
-
|
|
1028
|
-
Common causes: flow not found, no current context, instantiation errors,
|
|
1029
|
-
NodeGroup node type not available.
|
|
1030
|
-
"""
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
@dataclass
|
|
1034
|
-
@PayloadRegistry.register
|
|
1035
|
-
class DeleteNodeGroupRequest(RequestPayload):
|
|
1036
|
-
"""Delete a NodeGroup node.
|
|
1037
|
-
|
|
1038
|
-
Use when: Removing obsolete NodeGroups, cleaning up workflow structure,
|
|
1039
|
-
implementing undo. Handles cascading cleanup of the NodeGroup node.
|
|
1040
|
-
|
|
1041
|
-
Args:
|
|
1042
|
-
node_group_name: Name of the NodeGroup node to delete
|
|
1043
|
-
|
|
1044
|
-
Results: DeleteNodeGroupResultSuccess | DeleteNodeGroupResultFailure (NodeGroup not found, deletion failed)
|
|
1045
|
-
"""
|
|
1046
|
-
|
|
1047
|
-
node_group_name: str
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
@dataclass
|
|
1051
|
-
@PayloadRegistry.register
|
|
1052
|
-
class DeleteNodeGroupResultSuccess(WorkflowAlteredMixin, ResultPayloadSuccess):
|
|
1053
|
-
"""NodeGroup deleted successfully. NodeGroup node removed from workflow."""
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
@dataclass
|
|
1057
|
-
@PayloadRegistry.register
|
|
1058
|
-
class DeleteNodeGroupResultFailure(ResultPayloadFailure):
|
|
1059
|
-
"""NodeGroup deletion failed.
|
|
1060
|
-
|
|
1061
|
-
Common causes: NodeGroup not found, deletion cleanup failed, node is not a NodeGroup.
|
|
1062
|
-
"""
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
999
|
@dataclass
|
|
1066
1000
|
@PayloadRegistry.register
|
|
1067
1001
|
class MoveNodeToNewFlowRequest(RequestPayload):
|
|
@@ -17,11 +17,11 @@ from griptape_nodes.exe_types.core_types import (
|
|
|
17
17
|
ParameterTypeBuiltin,
|
|
18
18
|
)
|
|
19
19
|
from griptape_nodes.exe_types.flow import ControlFlow
|
|
20
|
+
from griptape_nodes.exe_types.node_groups import SubflowNodeGroup
|
|
20
21
|
from griptape_nodes.exe_types.node_types import (
|
|
21
22
|
BaseNode,
|
|
22
23
|
ErrorProxyNode,
|
|
23
24
|
NodeDependencies,
|
|
24
|
-
NodeGroupNode,
|
|
25
25
|
NodeResolutionState,
|
|
26
26
|
StartNode,
|
|
27
27
|
)
|
|
@@ -123,7 +123,6 @@ from griptape_nodes.retained_mode.events.flow_events import (
|
|
|
123
123
|
SetFlowMetadataResultSuccess,
|
|
124
124
|
)
|
|
125
125
|
from griptape_nodes.retained_mode.events.node_events import (
|
|
126
|
-
CreateNodeGroupRequest,
|
|
127
126
|
CreateNodeRequest,
|
|
128
127
|
DeleteNodeRequest,
|
|
129
128
|
DeleteNodeResultFailure,
|
|
@@ -594,22 +593,6 @@ class FlowManager:
|
|
|
594
593
|
|
|
595
594
|
# Let this Flow assume the Current Context while we delete everything within it.
|
|
596
595
|
with GriptapeNodes.ContextManager().flow(flow=flow):
|
|
597
|
-
# Delete all child nodes in this Flow.
|
|
598
|
-
list_nodes_request = ListNodesInFlowRequest()
|
|
599
|
-
list_nodes_result = GriptapeNodes.handle_request(list_nodes_request)
|
|
600
|
-
if not isinstance(list_nodes_result, ListNodesInFlowResultSuccess):
|
|
601
|
-
details = f"Attempted to delete Flow '{flow.name}', but failed while attempting to get the list of Nodes owned by this Flow."
|
|
602
|
-
result = DeleteFlowResultFailure(result_details=details)
|
|
603
|
-
return result
|
|
604
|
-
node_names = list_nodes_result.node_names
|
|
605
|
-
for node_name in node_names:
|
|
606
|
-
delete_node_request = DeleteNodeRequest(node_name=node_name)
|
|
607
|
-
delete_node_result = GriptapeNodes.handle_request(delete_node_request)
|
|
608
|
-
if isinstance(delete_node_result, DeleteNodeResultFailure):
|
|
609
|
-
details = f"Attempted to delete Flow '{flow.name}', but failed while attempting to delete child Node '{node_name}'."
|
|
610
|
-
result = DeleteFlowResultFailure(result_details=details)
|
|
611
|
-
return result
|
|
612
|
-
|
|
613
596
|
# Delete all child Flows of this Flow.
|
|
614
597
|
# Note: We use ListFlowsInCurrentContextRequest here instead of ListFlowsInFlowRequest(parent_flow_name=None)
|
|
615
598
|
# because None in ListFlowsInFlowRequest means "get canvas/top-level flows". We want the flows in the
|
|
@@ -632,12 +615,27 @@ class FlowManager:
|
|
|
632
615
|
return result
|
|
633
616
|
with GriptapeNodes.ContextManager().flow(flow=child_flow):
|
|
634
617
|
# Delete them.
|
|
635
|
-
delete_flow_request = DeleteFlowRequest()
|
|
618
|
+
delete_flow_request = DeleteFlowRequest(flow_name=child_flow_name)
|
|
636
619
|
delete_flow_result = GriptapeNodes.handle_request(delete_flow_request)
|
|
637
620
|
if isinstance(delete_flow_result, DeleteFlowResultFailure):
|
|
638
621
|
details = f"Attempted to delete Flow '{flow.name}', but failed while attempting to delete child Flow '{child_flow.name}'."
|
|
639
622
|
result = DeleteFlowResultFailure(result_details=details)
|
|
640
623
|
return result
|
|
624
|
+
# Delete all child nodes in this Flow.
|
|
625
|
+
list_nodes_request = ListNodesInFlowRequest()
|
|
626
|
+
list_nodes_result = GriptapeNodes.handle_request(list_nodes_request)
|
|
627
|
+
if not isinstance(list_nodes_result, ListNodesInFlowResultSuccess):
|
|
628
|
+
details = f"Attempted to delete Flow '{flow.name}', but failed while attempting to get the list of Nodes owned by this Flow."
|
|
629
|
+
result = DeleteFlowResultFailure(result_details=details)
|
|
630
|
+
return result
|
|
631
|
+
node_names = list_nodes_result.node_names
|
|
632
|
+
for node_name in node_names:
|
|
633
|
+
delete_node_request = DeleteNodeRequest(node_name=node_name)
|
|
634
|
+
delete_node_result = GriptapeNodes.handle_request(delete_node_request)
|
|
635
|
+
if isinstance(delete_node_result, DeleteNodeResultFailure):
|
|
636
|
+
details = f"Attempted to delete Flow '{flow.name}', but failed while attempting to delete child Node '{node_name}'."
|
|
637
|
+
result = DeleteFlowResultFailure(result_details=details)
|
|
638
|
+
return result
|
|
641
639
|
|
|
642
640
|
# If we've made it this far, we have deleted all the children Flows and their nodes.
|
|
643
641
|
# Remove the flow from our map.
|
|
@@ -917,8 +915,8 @@ class FlowManager:
|
|
|
917
915
|
details = f"Deleted the previous connection from '{old_source_node_name}.{old_source_param_name}' to '{old_target_node_name}.{old_target_param_name}' to make room for the new connection."
|
|
918
916
|
try:
|
|
919
917
|
# Actually create the Connection.
|
|
920
|
-
if (isinstance(source_node,
|
|
921
|
-
isinstance(target_node,
|
|
918
|
+
if (isinstance(source_node, SubflowNodeGroup) and target_node.parent_group == source_node) or (
|
|
919
|
+
isinstance(target_node, SubflowNodeGroup) and source_node.parent_group == target_node
|
|
922
920
|
):
|
|
923
921
|
# Here we're checking if it's an internal connection. (from the NodeGroup to a node within it.)
|
|
924
922
|
# If that's true, we set that automatically.
|
|
@@ -976,7 +974,7 @@ class FlowManager:
|
|
|
976
974
|
# If source is in a group, this is an outgoing external connection
|
|
977
975
|
if (
|
|
978
976
|
source_parent is not None
|
|
979
|
-
and isinstance(source_parent,
|
|
977
|
+
and isinstance(source_parent, SubflowNodeGroup)
|
|
980
978
|
and source_parent not in (target_parent, target_node)
|
|
981
979
|
):
|
|
982
980
|
success = source_parent.map_external_connection(
|
|
@@ -992,7 +990,7 @@ class FlowManager:
|
|
|
992
990
|
# If target is in a group, this is an incoming external connection
|
|
993
991
|
if (
|
|
994
992
|
target_parent is not None
|
|
995
|
-
and isinstance(target_parent,
|
|
993
|
+
and isinstance(target_parent, SubflowNodeGroup)
|
|
996
994
|
and target_parent not in (source_parent, source_node)
|
|
997
995
|
):
|
|
998
996
|
success = target_parent.map_external_connection(
|
|
@@ -1291,15 +1289,15 @@ class FlowManager:
|
|
|
1291
1289
|
if isinstance(node_connections_dict, PackageNodesAsSerializedFlowResultFailure):
|
|
1292
1290
|
return node_connections_dict
|
|
1293
1291
|
|
|
1294
|
-
# Step 8: Retrieve
|
|
1295
|
-
node_group_node:
|
|
1292
|
+
# Step 8: Retrieve SubflowNodeGroup if node_group_name was provided
|
|
1293
|
+
node_group_node: SubflowNodeGroup | None = None
|
|
1296
1294
|
if request.node_group_name:
|
|
1297
1295
|
try:
|
|
1298
1296
|
node = GriptapeNodes.NodeManager().get_node_by_name(request.node_group_name)
|
|
1299
|
-
if isinstance(node,
|
|
1297
|
+
if isinstance(node, SubflowNodeGroup):
|
|
1300
1298
|
node_group_node = node
|
|
1301
1299
|
except Exception as e:
|
|
1302
|
-
logger.debug("Failed to retrieve
|
|
1300
|
+
logger.debug("Failed to retrieve SubflowNodeGroup '%s': %s", request.node_group_name, e)
|
|
1303
1301
|
|
|
1304
1302
|
# Step 9: Create start node with parameters for external incoming connections
|
|
1305
1303
|
start_node_result = self._create_multi_node_start_node_with_connections(
|
|
@@ -1509,7 +1507,7 @@ class FlowManager:
|
|
|
1509
1507
|
"""
|
|
1510
1508
|
# Serialize each node using shared unique_parameter_uuid_to_values dictionary for deduplication
|
|
1511
1509
|
serialized_node_commands = []
|
|
1512
|
-
serialized_node_group_commands = [] #
|
|
1510
|
+
serialized_node_group_commands = [] # SubflowNodeGroups must be added LAST
|
|
1513
1511
|
|
|
1514
1512
|
for node in nodes_to_package:
|
|
1515
1513
|
# Serialize this node using shared dictionaries for value deduplication
|
|
@@ -1527,16 +1525,14 @@ class FlowManager:
|
|
|
1527
1525
|
|
|
1528
1526
|
# Populate the shared node_name_to_uuid mapping
|
|
1529
1527
|
create_cmd = serialize_result.serialized_node_commands.create_node_command
|
|
1530
|
-
# Get the node name from the
|
|
1531
|
-
node_name =
|
|
1532
|
-
create_cmd.node_group_name if isinstance(create_cmd, CreateNodeGroupRequest) else create_cmd.node_name
|
|
1533
|
-
)
|
|
1528
|
+
# Get the node name from the CreateNodeRequest command.
|
|
1529
|
+
node_name = create_cmd.node_name
|
|
1534
1530
|
if node_name is not None:
|
|
1535
1531
|
node_name_to_uuid[node_name] = serialize_result.serialized_node_commands.node_uuid
|
|
1536
1532
|
|
|
1537
|
-
#
|
|
1533
|
+
# SubflowNodeGroups must be serialized LAST because they reference child node names via node_names_to_add
|
|
1538
1534
|
# If we deserialize a NodeGroup before its children, the child nodes won't exist yet
|
|
1539
|
-
if isinstance(node,
|
|
1535
|
+
if isinstance(node, SubflowNodeGroup):
|
|
1540
1536
|
serialized_node_group_commands.append(serialize_result.serialized_node_commands)
|
|
1541
1537
|
else:
|
|
1542
1538
|
serialized_node_commands.append(serialize_result.serialized_node_commands)
|
|
@@ -1547,13 +1543,13 @@ class FlowManager:
|
|
|
1547
1543
|
serialize_result.set_parameter_value_commands
|
|
1548
1544
|
)
|
|
1549
1545
|
|
|
1550
|
-
# Update
|
|
1546
|
+
# Update SubflowNodeGroup commands to use UUIDs instead of names in node_names_to_add
|
|
1551
1547
|
# This allows workflow generation to directly look up variable names from UUIDs
|
|
1552
1548
|
|
|
1553
1549
|
for node_group_command in serialized_node_group_commands:
|
|
1554
1550
|
create_cmd = node_group_command.create_node_command
|
|
1555
1551
|
|
|
1556
|
-
if
|
|
1552
|
+
if create_cmd.node_names_to_add:
|
|
1557
1553
|
node_uuids = []
|
|
1558
1554
|
for child_node_name in create_cmd.node_names_to_add:
|
|
1559
1555
|
if child_node_name in node_name_to_uuid:
|
|
@@ -1648,12 +1644,8 @@ class FlowManager:
|
|
|
1648
1644
|
for serialized_node_command in serialized_package_nodes:
|
|
1649
1645
|
# We need to get the create commoand.
|
|
1650
1646
|
create_cmd = serialized_node_command.create_node_command
|
|
1651
|
-
#
|
|
1652
|
-
cmd_node_name =
|
|
1653
|
-
create_cmd.node_group_name
|
|
1654
|
-
if isinstance(create_cmd, CreateNodeGroupRequest)
|
|
1655
|
-
else create_cmd.node_name
|
|
1656
|
-
)
|
|
1647
|
+
# Get the node name from CreateNodeRequest
|
|
1648
|
+
cmd_node_name = create_cmd.node_name
|
|
1657
1649
|
if cmd_node_name == package_node.name:
|
|
1658
1650
|
serialized_node = serialized_node_command
|
|
1659
1651
|
break
|
|
@@ -2048,7 +2040,7 @@ class FlowManager:
|
|
|
2048
2040
|
external_connections_dict: dict[
|
|
2049
2041
|
str, ConnectionAnalysis
|
|
2050
2042
|
], # Contains EXTERNAL connections only - used to determine which parameters need start node inputs
|
|
2051
|
-
node_group_node:
|
|
2043
|
+
node_group_node: SubflowNodeGroup | None = None,
|
|
2052
2044
|
) -> PackagingStartNodeResult | PackageNodesAsSerializedFlowResultFailure:
|
|
2053
2045
|
"""Create start node commands and connections for external incoming connections."""
|
|
2054
2046
|
# Generate UUID and name for start node
|
|
@@ -2123,7 +2115,7 @@ class FlowManager:
|
|
|
2123
2115
|
# Add control connections to the same list as data connections
|
|
2124
2116
|
start_to_package_connections.extend(control_connections)
|
|
2125
2117
|
|
|
2126
|
-
# Set parameter values from
|
|
2118
|
+
# Set parameter values from SubflowNodeGroup if provided
|
|
2127
2119
|
if node_group_node is not None:
|
|
2128
2120
|
self._apply_node_group_parameters_to_start_node(
|
|
2129
2121
|
node_group_node=node_group_node,
|
|
@@ -2156,21 +2148,21 @@ class FlowManager:
|
|
|
2156
2148
|
|
|
2157
2149
|
def _apply_node_group_parameters_to_start_node( # noqa: PLR0913
|
|
2158
2150
|
self,
|
|
2159
|
-
node_group_node:
|
|
2151
|
+
node_group_node: SubflowNodeGroup,
|
|
2160
2152
|
start_node_library_name: str,
|
|
2161
2153
|
start_node_type: str,
|
|
2162
2154
|
start_node_parameter_value_commands: list[SerializedNodeCommands.IndirectSetParameterValueCommand],
|
|
2163
2155
|
unique_parameter_uuid_to_values: dict[SerializedNodeCommands.UniqueParameterValueUUID, Any],
|
|
2164
2156
|
serialized_parameter_value_tracker: SerializedParameterValueTracker,
|
|
2165
2157
|
) -> None:
|
|
2166
|
-
"""Apply parameter values from
|
|
2158
|
+
"""Apply parameter values from SubflowNodeGroup to the StartFlow node.
|
|
2167
2159
|
|
|
2168
|
-
This method reads the execution environment metadata from the
|
|
2160
|
+
This method reads the execution environment metadata from the SubflowNodeGroup,
|
|
2169
2161
|
extracts parameter values for the specified StartFlow node type, and creates
|
|
2170
2162
|
set parameter value commands for those parameters.
|
|
2171
2163
|
|
|
2172
2164
|
Args:
|
|
2173
|
-
node_group_node: The
|
|
2165
|
+
node_group_node: The SubflowNodeGroup containing parameter values
|
|
2174
2166
|
start_node_library_name: Name of the library containing the StartFlow node
|
|
2175
2167
|
start_node_type: Type of the StartFlow node
|
|
2176
2168
|
start_node_parameter_value_commands: List to append parameter value commands to
|
|
@@ -2178,28 +2170,28 @@ class FlowManager:
|
|
|
2178
2170
|
serialized_parameter_value_tracker: Tracker for serialized parameter values
|
|
2179
2171
|
|
|
2180
2172
|
Raises:
|
|
2181
|
-
ValueError: If required metadata is missing from
|
|
2173
|
+
ValueError: If required metadata is missing from SubflowNodeGroup
|
|
2182
2174
|
"""
|
|
2183
|
-
# Get execution environment metadata from
|
|
2175
|
+
# Get execution environment metadata from SubflowNodeGroup
|
|
2184
2176
|
if not node_group_node.metadata:
|
|
2185
|
-
msg = f"
|
|
2177
|
+
msg = f"SubflowNodeGroup '{node_group_node.name}' is missing metadata. Cannot apply parameters to StartFlow node."
|
|
2186
2178
|
raise ValueError(msg)
|
|
2187
2179
|
|
|
2188
2180
|
execution_env_metadata = node_group_node.metadata.get("execution_environment")
|
|
2189
2181
|
if not execution_env_metadata:
|
|
2190
|
-
msg = f"
|
|
2182
|
+
msg = f"SubflowNodeGroup '{node_group_node.name}' metadata is missing 'execution_environment'. Cannot apply parameters to StartFlow node."
|
|
2191
2183
|
raise ValueError(msg)
|
|
2192
2184
|
|
|
2193
2185
|
# Find the metadata for the current library
|
|
2194
2186
|
library_metadata = execution_env_metadata.get(start_node_library_name)
|
|
2195
2187
|
if library_metadata is None:
|
|
2196
|
-
msg = f"
|
|
2188
|
+
msg = f"SubflowNodeGroup '{node_group_node.name}' metadata does not contain library '{start_node_library_name}'. Available libraries: {list(execution_env_metadata.keys())}"
|
|
2197
2189
|
raise ValueError(msg)
|
|
2198
2190
|
|
|
2199
2191
|
# Verify this is the correct StartFlow node type
|
|
2200
2192
|
registered_start_flow_node = library_metadata.get("start_flow_node")
|
|
2201
2193
|
if registered_start_flow_node != start_node_type:
|
|
2202
|
-
msg = f"
|
|
2194
|
+
msg = f"SubflowNodeGroup '{node_group_node.name}' has mismatched StartFlow node type. Expected '{start_node_type}', but metadata has '{registered_start_flow_node}'"
|
|
2203
2195
|
raise ValueError(msg)
|
|
2204
2196
|
|
|
2205
2197
|
# Get the list of parameter names that belong to this StartFlow node
|
|
@@ -2207,15 +2199,15 @@ class FlowManager:
|
|
|
2207
2199
|
if not parameter_names:
|
|
2208
2200
|
# This is not an error - it's valid for a StartFlow node to have no parameters
|
|
2209
2201
|
logger.debug(
|
|
2210
|
-
"
|
|
2202
|
+
"SubflowNodeGroup '%s' has no parameters registered for StartFlow node '%s'",
|
|
2211
2203
|
node_group_node.name,
|
|
2212
2204
|
start_node_type,
|
|
2213
2205
|
)
|
|
2214
2206
|
return
|
|
2215
2207
|
|
|
2216
|
-
# For each parameter, get its value from the
|
|
2208
|
+
# For each parameter, get its value from the SubflowNodeGroup and create a set value command
|
|
2217
2209
|
for prefixed_param_name in parameter_names:
|
|
2218
|
-
# Get the value from the
|
|
2210
|
+
# Get the value from the SubflowNodeGroup parameter
|
|
2219
2211
|
param_value = node_group_node.get_parameter_value(param_name=prefixed_param_name)
|
|
2220
2212
|
|
|
2221
2213
|
# Skip if no value is set
|
|
@@ -2621,7 +2613,7 @@ class FlowManager:
|
|
|
2621
2613
|
2. A control node with no incoming control connections, OR
|
|
2622
2614
|
3. A data node with no outgoing connections
|
|
2623
2615
|
|
|
2624
|
-
Nodes that are children of
|
|
2616
|
+
Nodes that are children of SubflowNodeGroups are excluded.
|
|
2625
2617
|
|
|
2626
2618
|
Args:
|
|
2627
2619
|
flow: The flow to search for start nodes
|
|
@@ -2988,9 +2980,6 @@ class FlowManager:
|
|
|
2988
2980
|
# Collect node types from all nodes in this flow
|
|
2989
2981
|
for node_cmd in serialized_node_commands:
|
|
2990
2982
|
create_cmd = node_cmd.create_node_command
|
|
2991
|
-
# Skip NodeGroupNode as it doesn't have node_type/specific_library_name
|
|
2992
|
-
if isinstance(create_cmd, CreateNodeGroupRequest):
|
|
2993
|
-
continue
|
|
2994
2983
|
node_type = create_cmd.node_type
|
|
2995
2984
|
library_name = create_cmd.specific_library_name
|
|
2996
2985
|
if library_name is None:
|
|
@@ -3127,7 +3116,7 @@ class FlowManager:
|
|
|
3127
3116
|
create_flow_request = None
|
|
3128
3117
|
|
|
3129
3118
|
serialized_node_commands = []
|
|
3130
|
-
serialized_node_group_commands = [] #
|
|
3119
|
+
serialized_node_group_commands = [] # SubflowNodeGroups must be added LAST
|
|
3131
3120
|
set_parameter_value_commands_per_node = {} # Maps a node UUID to a list of set parameter value commands
|
|
3132
3121
|
set_lock_commands_per_node = {} # Maps a node UUID to a set Lock command, if it exists.
|
|
3133
3122
|
|
|
@@ -3164,9 +3153,9 @@ class FlowManager:
|
|
|
3164
3153
|
# Store the serialized node's UUID for correlation to connections and setting parameter values later.
|
|
3165
3154
|
node_name_to_uuid[node_name] = serialized_node.node_uuid
|
|
3166
3155
|
|
|
3167
|
-
#
|
|
3156
|
+
# SubflowNodeGroups must be serialized LAST because CreateNodeGroupRequest references child node names
|
|
3168
3157
|
# If we deserialize a NodeGroup before its children, the child nodes won't exist yet
|
|
3169
|
-
if isinstance(node,
|
|
3158
|
+
if isinstance(node, SubflowNodeGroup):
|
|
3170
3159
|
serialized_node_group_commands.append(serialized_node)
|
|
3171
3160
|
else:
|
|
3172
3161
|
serialized_node_commands.append(serialized_node)
|
|
@@ -3239,7 +3228,7 @@ class FlowManager:
|
|
|
3239
3228
|
# This ensures child nodes exist before their parent NodeGroups are created during deserialization
|
|
3240
3229
|
serialized_node_commands.extend(serialized_node_group_commands)
|
|
3241
3230
|
|
|
3242
|
-
# Update
|
|
3231
|
+
# Update SubflowNodeGroup commands to use UUIDs instead of names in node_names_to_add
|
|
3243
3232
|
# This allows workflow generation to directly look up variable names from UUIDs
|
|
3244
3233
|
# Build a complete node name to UUID map including nodes from all subflows
|
|
3245
3234
|
complete_node_name_to_uuid = dict(node_name_to_uuid) # Start with current flow's nodes
|
|
@@ -3250,10 +3239,8 @@ class FlowManager:
|
|
|
3250
3239
|
for node_cmd in subflow_cmd.serialized_node_commands:
|
|
3251
3240
|
# Extract node name from the create command
|
|
3252
3241
|
create_cmd = node_cmd.create_node_command
|
|
3253
|
-
if
|
|
3242
|
+
if create_cmd.node_name:
|
|
3254
3243
|
complete_node_name_to_uuid[create_cmd.node_name] = node_cmd.node_uuid
|
|
3255
|
-
elif isinstance(create_cmd, CreateNodeGroupRequest) and create_cmd.node_group_name:
|
|
3256
|
-
complete_node_name_to_uuid[create_cmd.node_group_name] = node_cmd.node_uuid
|
|
3257
3244
|
# Recursively process nested subflows
|
|
3258
3245
|
if subflow_cmd.sub_flows_commands:
|
|
3259
3246
|
collect_subflow_node_uuids(subflow_cmd.sub_flows_commands)
|
|
@@ -3263,7 +3250,7 @@ class FlowManager:
|
|
|
3263
3250
|
for node_group_command in serialized_node_group_commands:
|
|
3264
3251
|
create_cmd = node_group_command.create_node_command
|
|
3265
3252
|
|
|
3266
|
-
if
|
|
3253
|
+
if create_cmd.node_names_to_add:
|
|
3267
3254
|
# Convert node names to UUIDs using the complete map (including subflows)
|
|
3268
3255
|
node_uuids = []
|
|
3269
3256
|
for child_node_name in create_cmd.node_names_to_add:
|
|
@@ -3382,16 +3369,14 @@ class FlowManager:
|
|
|
3382
3369
|
node_uuid_to_deserialized_node_result = {}
|
|
3383
3370
|
node_name_mappings = {}
|
|
3384
3371
|
for serialized_node in request.serialized_flow_commands.serialized_node_commands:
|
|
3385
|
-
# Get the node name from the
|
|
3372
|
+
# Get the node name from the CreateNodeRequest command
|
|
3386
3373
|
create_cmd = serialized_node.create_node_command
|
|
3387
|
-
original_node_name =
|
|
3388
|
-
create_cmd.node_group_name if isinstance(create_cmd, CreateNodeGroupRequest) else create_cmd.node_name
|
|
3389
|
-
)
|
|
3374
|
+
original_node_name = create_cmd.node_name
|
|
3390
3375
|
|
|
3391
|
-
# For
|
|
3376
|
+
# For SubflowNodeGroups, remap node_names_to_add from UUIDs to actual node names
|
|
3392
3377
|
# Create a copy to avoid mutating the original serialized data
|
|
3393
3378
|
serialized_node_for_deserialization = serialized_node
|
|
3394
|
-
if
|
|
3379
|
+
if create_cmd.node_names_to_add:
|
|
3395
3380
|
# Use list comprehension to remap UUIDs to deserialized node names
|
|
3396
3381
|
remapped_names = [
|
|
3397
3382
|
node_uuid_to_deserialized_node_result[node_uuid].node_name
|
|
@@ -3399,10 +3384,17 @@ class FlowManager:
|
|
|
3399
3384
|
if node_uuid in node_uuid_to_deserialized_node_result
|
|
3400
3385
|
]
|
|
3401
3386
|
# Create a copy of the command with remapped names instead of mutating original
|
|
3402
|
-
create_cmd_copy =
|
|
3403
|
-
|
|
3387
|
+
create_cmd_copy = CreateNodeRequest(
|
|
3388
|
+
node_type=create_cmd.node_type,
|
|
3389
|
+
specific_library_name=create_cmd.specific_library_name,
|
|
3390
|
+
node_name=create_cmd.node_name,
|
|
3404
3391
|
node_names_to_add=remapped_names,
|
|
3392
|
+
override_parent_flow_name=create_cmd.override_parent_flow_name,
|
|
3405
3393
|
metadata=create_cmd.metadata,
|
|
3394
|
+
resolution=create_cmd.resolution,
|
|
3395
|
+
initial_setup=create_cmd.initial_setup,
|
|
3396
|
+
set_as_new_context=create_cmd.set_as_new_context,
|
|
3397
|
+
create_error_proxy_on_failure=create_cmd.create_error_proxy_on_failure,
|
|
3406
3398
|
)
|
|
3407
3399
|
# Create a copy of serialized_node with the new command
|
|
3408
3400
|
serialized_node_for_deserialization = SerializedNodeCommands(
|
|
@@ -3411,6 +3403,7 @@ class FlowManager:
|
|
|
3411
3403
|
element_modification_commands=serialized_node.element_modification_commands,
|
|
3412
3404
|
node_dependencies=serialized_node.node_dependencies,
|
|
3413
3405
|
lock_node_command=serialized_node.lock_node_command,
|
|
3406
|
+
is_node_group=serialized_node.is_node_group,
|
|
3414
3407
|
)
|
|
3415
3408
|
|
|
3416
3409
|
deserialize_node_request = DeserializeNodeFromCommandsRequest(
|
|
@@ -3837,8 +3830,8 @@ class FlowManager:
|
|
|
3837
3830
|
control_nodes = []
|
|
3838
3831
|
cn_mgr = self.get_connections()
|
|
3839
3832
|
for node in all_nodes:
|
|
3840
|
-
# Skip nodes that are children of a
|
|
3841
|
-
if node.parent_group is not None and isinstance(node.parent_group,
|
|
3833
|
+
# Skip nodes that are children of a SubflowNodeGroup - they should not be start nodes
|
|
3834
|
+
if node.parent_group is not None and isinstance(node.parent_group, SubflowNodeGroup):
|
|
3842
3835
|
continue
|
|
3843
3836
|
|
|
3844
3837
|
# if it's a start node, start here! Return the first one!
|