griptape-nodes 0.41.0__py3-none-any.whl → 0.42.0__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/app/__init__.py +1 -5
- griptape_nodes/app/app.py +12 -9
- griptape_nodes/app/app_sessions.py +132 -36
- griptape_nodes/app/watch.py +3 -1
- griptape_nodes/drivers/storage/local_storage_driver.py +3 -2
- griptape_nodes/exe_types/flow.py +68 -368
- griptape_nodes/machines/control_flow.py +16 -13
- griptape_nodes/machines/node_resolution.py +16 -14
- griptape_nodes/node_library/workflow_registry.py +2 -2
- griptape_nodes/retained_mode/events/agent_events.py +70 -8
- griptape_nodes/retained_mode/events/app_events.py +132 -11
- griptape_nodes/retained_mode/events/arbitrary_python_events.py +23 -0
- griptape_nodes/retained_mode/events/base_events.py +7 -25
- griptape_nodes/retained_mode/events/config_events.py +87 -11
- griptape_nodes/retained_mode/events/connection_events.py +56 -5
- griptape_nodes/retained_mode/events/context_events.py +27 -4
- griptape_nodes/retained_mode/events/execution_events.py +99 -14
- griptape_nodes/retained_mode/events/flow_events.py +165 -7
- griptape_nodes/retained_mode/events/library_events.py +193 -15
- griptape_nodes/retained_mode/events/logger_events.py +11 -0
- griptape_nodes/retained_mode/events/node_events.py +242 -22
- griptape_nodes/retained_mode/events/object_events.py +40 -4
- griptape_nodes/retained_mode/events/os_events.py +13 -2
- griptape_nodes/retained_mode/events/parameter_events.py +212 -8
- griptape_nodes/retained_mode/events/secrets_events.py +59 -7
- griptape_nodes/retained_mode/events/static_file_events.py +57 -4
- griptape_nodes/retained_mode/events/validation_events.py +39 -4
- griptape_nodes/retained_mode/events/workflow_events.py +188 -17
- griptape_nodes/retained_mode/griptape_nodes.py +46 -323
- griptape_nodes/retained_mode/managers/agent_manager.py +1 -1
- griptape_nodes/retained_mode/managers/engine_identity_manager.py +146 -0
- griptape_nodes/retained_mode/managers/event_manager.py +14 -2
- griptape_nodes/retained_mode/managers/flow_manager.py +749 -64
- griptape_nodes/retained_mode/managers/library_manager.py +112 -2
- griptape_nodes/retained_mode/managers/node_manager.py +34 -31
- griptape_nodes/retained_mode/managers/object_manager.py +11 -3
- griptape_nodes/retained_mode/managers/os_manager.py +70 -1
- griptape_nodes/retained_mode/managers/secrets_manager.py +4 -0
- griptape_nodes/retained_mode/managers/session_manager.py +328 -0
- griptape_nodes/retained_mode/managers/settings.py +7 -0
- griptape_nodes/retained_mode/managers/workflow_manager.py +523 -454
- griptape_nodes/retained_mode/retained_mode.py +44 -0
- griptape_nodes/retained_mode/utils/engine_identity.py +141 -27
- {griptape_nodes-0.41.0.dist-info → griptape_nodes-0.42.0.dist-info}/METADATA +2 -2
- {griptape_nodes-0.41.0.dist-info → griptape_nodes-0.42.0.dist-info}/RECORD +48 -47
- griptape_nodes/retained_mode/utils/session_persistence.py +0 -105
- {griptape_nodes-0.41.0.dist-info → griptape_nodes-0.42.0.dist-info}/WHEEL +0 -0
- {griptape_nodes-0.41.0.dist-info → griptape_nodes-0.42.0.dist-info}/entry_points.txt +0 -0
- {griptape_nodes-0.41.0.dist-info → griptape_nodes-0.42.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
from queue import Queue
|
|
4
5
|
from typing import TYPE_CHECKING, cast
|
|
5
6
|
|
|
7
|
+
from griptape.events import EventBus
|
|
8
|
+
|
|
9
|
+
from griptape_nodes.exe_types.connections import Connections
|
|
6
10
|
from griptape_nodes.exe_types.core_types import (
|
|
11
|
+
Parameter,
|
|
7
12
|
ParameterContainer,
|
|
8
13
|
ParameterMode,
|
|
14
|
+
ParameterTypeBuiltin,
|
|
9
15
|
)
|
|
10
16
|
from griptape_nodes.exe_types.flow import ControlFlow
|
|
11
|
-
from griptape_nodes.exe_types.node_types import BaseNode, NodeResolutionState
|
|
17
|
+
from griptape_nodes.exe_types.node_types import BaseNode, NodeResolutionState, StartLoopNode, StartNode
|
|
18
|
+
from griptape_nodes.machines.control_flow import CompleteState, ControlFlowMachine
|
|
12
19
|
from griptape_nodes.retained_mode.events.base_events import (
|
|
20
|
+
ExecutionEvent,
|
|
21
|
+
ExecutionGriptapeNodeEvent,
|
|
13
22
|
FlushParameterChangesRequest,
|
|
14
23
|
FlushParameterChangesResultSuccess,
|
|
15
24
|
)
|
|
@@ -28,6 +37,7 @@ from griptape_nodes.retained_mode.events.execution_events import (
|
|
|
28
37
|
ContinueExecutionStepRequest,
|
|
29
38
|
ContinueExecutionStepResultFailure,
|
|
30
39
|
ContinueExecutionStepResultSuccess,
|
|
40
|
+
ControlFlowCancelledEvent,
|
|
31
41
|
GetFlowStateRequest,
|
|
32
42
|
GetFlowStateResultFailure,
|
|
33
43
|
GetFlowStateResultSuccess,
|
|
@@ -57,6 +67,12 @@ from griptape_nodes.retained_mode.events.flow_events import (
|
|
|
57
67
|
DeserializeFlowFromCommandsRequest,
|
|
58
68
|
DeserializeFlowFromCommandsResultFailure,
|
|
59
69
|
DeserializeFlowFromCommandsResultSuccess,
|
|
70
|
+
GetFlowDetailsRequest,
|
|
71
|
+
GetFlowDetailsResultFailure,
|
|
72
|
+
GetFlowDetailsResultSuccess,
|
|
73
|
+
GetFlowMetadataRequest,
|
|
74
|
+
GetFlowMetadataResultFailure,
|
|
75
|
+
GetFlowMetadataResultSuccess,
|
|
60
76
|
GetTopLevelFlowRequest,
|
|
61
77
|
GetTopLevelFlowResultSuccess,
|
|
62
78
|
ListFlowsInCurrentContextRequest,
|
|
@@ -72,6 +88,9 @@ from griptape_nodes.retained_mode.events.flow_events import (
|
|
|
72
88
|
SerializeFlowToCommandsRequest,
|
|
73
89
|
SerializeFlowToCommandsResultFailure,
|
|
74
90
|
SerializeFlowToCommandsResultSuccess,
|
|
91
|
+
SetFlowMetadataRequest,
|
|
92
|
+
SetFlowMetadataResultFailure,
|
|
93
|
+
SetFlowMetadataResultSuccess,
|
|
75
94
|
)
|
|
76
95
|
from griptape_nodes.retained_mode.events.node_events import (
|
|
77
96
|
DeleteNodeRequest,
|
|
@@ -89,6 +108,10 @@ from griptape_nodes.retained_mode.events.validation_events import (
|
|
|
89
108
|
ValidateFlowDependenciesResultFailure,
|
|
90
109
|
ValidateFlowDependenciesResultSuccess,
|
|
91
110
|
)
|
|
111
|
+
from griptape_nodes.retained_mode.events.workflow_events import (
|
|
112
|
+
ImportWorkflowAsReferencedSubFlowRequest,
|
|
113
|
+
ImportWorkflowAsReferencedSubFlowResultSuccess,
|
|
114
|
+
)
|
|
92
115
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
93
116
|
|
|
94
117
|
if TYPE_CHECKING:
|
|
@@ -100,6 +123,13 @@ logger = logging.getLogger("griptape_nodes")
|
|
|
100
123
|
|
|
101
124
|
class FlowManager:
|
|
102
125
|
_name_to_parent_name: dict[str, str | None]
|
|
126
|
+
_flow_to_referenced_workflow_name: dict[ControlFlow, str]
|
|
127
|
+
_connections: Connections
|
|
128
|
+
|
|
129
|
+
# Global execution state (moved from individual ControlFlows)
|
|
130
|
+
_global_flow_queue: Queue[BaseNode]
|
|
131
|
+
_global_control_flow_machine: ControlFlowMachine | None
|
|
132
|
+
_global_single_node_resolution: bool
|
|
103
133
|
|
|
104
134
|
def __init__(self, event_manager: EventManager) -> None:
|
|
105
135
|
event_manager.assign_manager_to_request_type(CreateFlowRequest, self.on_create_flow_request)
|
|
@@ -126,6 +156,9 @@ class FlowManager:
|
|
|
126
156
|
ValidateFlowDependenciesRequest, self.on_validate_flow_dependencies_request
|
|
127
157
|
)
|
|
128
158
|
event_manager.assign_manager_to_request_type(GetTopLevelFlowRequest, self.on_get_top_level_flow_request)
|
|
159
|
+
event_manager.assign_manager_to_request_type(GetFlowDetailsRequest, self.on_get_flow_details_request)
|
|
160
|
+
event_manager.assign_manager_to_request_type(GetFlowMetadataRequest, self.on_get_flow_metadata_request)
|
|
161
|
+
event_manager.assign_manager_to_request_type(SetFlowMetadataRequest, self.on_set_flow_metadata_request)
|
|
129
162
|
event_manager.assign_manager_to_request_type(SerializeFlowToCommandsRequest, self.on_serialize_flow_to_commands)
|
|
130
163
|
event_manager.assign_manager_to_request_type(
|
|
131
164
|
DeserializeFlowFromCommandsRequest, self.on_deserialize_flow_from_commands
|
|
@@ -133,6 +166,53 @@ class FlowManager:
|
|
|
133
166
|
event_manager.assign_manager_to_request_type(FlushParameterChangesRequest, self.on_flush_request)
|
|
134
167
|
|
|
135
168
|
self._name_to_parent_name = {}
|
|
169
|
+
self._flow_to_referenced_workflow_name = {}
|
|
170
|
+
self._connections = Connections()
|
|
171
|
+
|
|
172
|
+
# Initialize global execution state
|
|
173
|
+
self._global_flow_queue = Queue[BaseNode]()
|
|
174
|
+
self._global_control_flow_machine = None # Will be initialized when first flow starts
|
|
175
|
+
self._global_single_node_resolution = False
|
|
176
|
+
|
|
177
|
+
def get_connections(self) -> Connections:
|
|
178
|
+
"""Get the connections instance."""
|
|
179
|
+
return self._connections
|
|
180
|
+
|
|
181
|
+
def _has_connection(
|
|
182
|
+
self,
|
|
183
|
+
source_node: BaseNode,
|
|
184
|
+
source_parameter: Parameter,
|
|
185
|
+
target_node: BaseNode,
|
|
186
|
+
target_parameter: Parameter,
|
|
187
|
+
) -> bool:
|
|
188
|
+
"""Check if a connection exists."""
|
|
189
|
+
connected_outputs = self.get_connected_output_parameters(source_node, source_parameter)
|
|
190
|
+
for connected_node, connected_param in connected_outputs:
|
|
191
|
+
if connected_node is target_node and connected_param is target_parameter:
|
|
192
|
+
return True
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
def get_connected_output_parameters(self, node: BaseNode, param: Parameter) -> list[tuple[BaseNode, Parameter]]:
|
|
196
|
+
"""Get connected output parameters."""
|
|
197
|
+
connections = []
|
|
198
|
+
if node.name in self._connections.outgoing_index:
|
|
199
|
+
outgoing_params = self._connections.outgoing_index[node.name]
|
|
200
|
+
if param.name in outgoing_params:
|
|
201
|
+
for connection_id in outgoing_params[param.name]:
|
|
202
|
+
connection = self._connections.connections[connection_id]
|
|
203
|
+
connections.append((connection.target_node, connection.target_parameter))
|
|
204
|
+
return connections
|
|
205
|
+
|
|
206
|
+
def _get_connections_for_flow(self, flow: ControlFlow) -> list:
|
|
207
|
+
"""Get connections where both nodes are in the specified flow."""
|
|
208
|
+
flow_connections = []
|
|
209
|
+
for connection in self._connections.connections.values():
|
|
210
|
+
source_in_flow = connection.source_node.name in flow.nodes
|
|
211
|
+
target_in_flow = connection.target_node.name in flow.nodes
|
|
212
|
+
# Only include connection if BOTH nodes are in this flow (for serialization)
|
|
213
|
+
if source_in_flow and target_in_flow:
|
|
214
|
+
flow_connections.append(connection)
|
|
215
|
+
return flow_connections
|
|
136
216
|
|
|
137
217
|
def get_parent_flow(self, flow_name: str) -> str | None:
|
|
138
218
|
if flow_name in self._name_to_parent_name:
|
|
@@ -140,6 +220,22 @@ class FlowManager:
|
|
|
140
220
|
msg = f"Flow with name {flow_name} doesn't exist"
|
|
141
221
|
raise ValueError(msg)
|
|
142
222
|
|
|
223
|
+
def is_referenced_workflow(self, flow: ControlFlow) -> bool:
|
|
224
|
+
"""Check if this flow was created by importing a referenced workflow.
|
|
225
|
+
|
|
226
|
+
Returns True if this flow originated from a workflow import operation,
|
|
227
|
+
False if it was created standalone.
|
|
228
|
+
"""
|
|
229
|
+
return flow in self._flow_to_referenced_workflow_name
|
|
230
|
+
|
|
231
|
+
def get_referenced_workflow_name(self, flow: ControlFlow) -> str | None:
|
|
232
|
+
"""Get the name of the referenced workflow, if any.
|
|
233
|
+
|
|
234
|
+
Returns the workflow name that was imported to create this flow,
|
|
235
|
+
or None if this flow was created standalone.
|
|
236
|
+
"""
|
|
237
|
+
return self._flow_to_referenced_workflow_name.get(flow)
|
|
238
|
+
|
|
143
239
|
def on_get_top_level_flow_request(self, request: GetTopLevelFlowRequest) -> ResultPayload: # noqa: ARG002 (the request has to be assigned to the method)
|
|
144
240
|
for flow_name, parent in self._name_to_parent_name.items():
|
|
145
241
|
if parent is None:
|
|
@@ -148,6 +244,103 @@ class FlowManager:
|
|
|
148
244
|
logger.debug(msg)
|
|
149
245
|
return GetTopLevelFlowResultSuccess(flow_name=None)
|
|
150
246
|
|
|
247
|
+
def on_get_flow_details_request(self, request: GetFlowDetailsRequest) -> ResultPayload:
|
|
248
|
+
flow_name = request.flow_name
|
|
249
|
+
flow = None
|
|
250
|
+
|
|
251
|
+
if flow_name is None:
|
|
252
|
+
# We want to get details for whatever is at the top of the Current Context.
|
|
253
|
+
if not GriptapeNodes.ContextManager().has_current_flow():
|
|
254
|
+
details = "Attempted to get Flow details from the Current Context. Failed because the Current Context was empty."
|
|
255
|
+
logger.error(details)
|
|
256
|
+
return GetFlowDetailsResultFailure()
|
|
257
|
+
flow = GriptapeNodes.ContextManager().get_current_flow()
|
|
258
|
+
flow_name = flow.name
|
|
259
|
+
else:
|
|
260
|
+
flow = GriptapeNodes.ObjectManager().attempt_get_object_by_name_as_type(flow_name, ControlFlow)
|
|
261
|
+
if flow is None:
|
|
262
|
+
details = (
|
|
263
|
+
f"Attempted to get Flow details for '{flow_name}'. Failed because no Flow with that name exists."
|
|
264
|
+
)
|
|
265
|
+
logger.error(details)
|
|
266
|
+
return GetFlowDetailsResultFailure()
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
parent_flow_name = self.get_parent_flow(flow_name)
|
|
270
|
+
except ValueError:
|
|
271
|
+
details = f"Attempted to get Flow details for '{flow_name}'. Failed because Flow does not exist in parent mapping."
|
|
272
|
+
logger.error(details)
|
|
273
|
+
return GetFlowDetailsResultFailure()
|
|
274
|
+
|
|
275
|
+
referenced_workflow_name = None
|
|
276
|
+
if self.is_referenced_workflow(flow):
|
|
277
|
+
referenced_workflow_name = self.get_referenced_workflow_name(flow)
|
|
278
|
+
|
|
279
|
+
details = f"Successfully retrieved Flow details for '{flow_name}'."
|
|
280
|
+
logger.debug(details)
|
|
281
|
+
return GetFlowDetailsResultSuccess(
|
|
282
|
+
referenced_workflow_name=referenced_workflow_name,
|
|
283
|
+
parent_flow_name=parent_flow_name,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
def on_get_flow_metadata_request(self, request: GetFlowMetadataRequest) -> ResultPayload:
|
|
287
|
+
flow_name = request.flow_name
|
|
288
|
+
flow = None
|
|
289
|
+
if flow_name is None:
|
|
290
|
+
# Get from the current context.
|
|
291
|
+
if not GriptapeNodes.ContextManager().has_current_flow():
|
|
292
|
+
details = "Attempted to get metadata for a Flow from the Current Context. Failed because the Current Context is empty."
|
|
293
|
+
logger.error(details)
|
|
294
|
+
return GetFlowMetadataResultFailure()
|
|
295
|
+
|
|
296
|
+
flow = GriptapeNodes.ContextManager().get_current_flow()
|
|
297
|
+
flow_name = flow.name
|
|
298
|
+
|
|
299
|
+
# Does this flow exist?
|
|
300
|
+
if flow is None:
|
|
301
|
+
obj_mgr = GriptapeNodes.ObjectManager()
|
|
302
|
+
flow = obj_mgr.attempt_get_object_by_name_as_type(flow_name, ControlFlow)
|
|
303
|
+
if flow is None:
|
|
304
|
+
details = f"Attempted to get metadata for a Flow '{flow_name}', but no such Flow was found."
|
|
305
|
+
logger.error(details)
|
|
306
|
+
return GetFlowMetadataResultFailure()
|
|
307
|
+
|
|
308
|
+
metadata = flow.metadata
|
|
309
|
+
details = f"Successfully retrieved metadata for a Flow '{flow_name}'."
|
|
310
|
+
logger.debug(details)
|
|
311
|
+
|
|
312
|
+
return GetFlowMetadataResultSuccess(metadata=metadata)
|
|
313
|
+
|
|
314
|
+
def on_set_flow_metadata_request(self, request: SetFlowMetadataRequest) -> ResultPayload:
|
|
315
|
+
flow_name = request.flow_name
|
|
316
|
+
flow = None
|
|
317
|
+
if flow_name is None:
|
|
318
|
+
# Get from the current context.
|
|
319
|
+
if not GriptapeNodes.ContextManager().has_current_flow():
|
|
320
|
+
details = "Attempted to set metadata for a Flow from the Current Context. Failed because the Current Context is empty."
|
|
321
|
+
logger.error(details)
|
|
322
|
+
return SetFlowMetadataResultFailure()
|
|
323
|
+
|
|
324
|
+
flow = GriptapeNodes.ContextManager().get_current_flow()
|
|
325
|
+
flow_name = flow.name
|
|
326
|
+
|
|
327
|
+
# Does this flow exist?
|
|
328
|
+
if flow is None:
|
|
329
|
+
obj_mgr = GriptapeNodes.ObjectManager()
|
|
330
|
+
flow = obj_mgr.attempt_get_object_by_name_as_type(flow_name, ControlFlow)
|
|
331
|
+
if flow is None:
|
|
332
|
+
details = f"Attempted to set metadata for a Flow '{flow_name}', but no such Flow was found."
|
|
333
|
+
logger.error(details)
|
|
334
|
+
return SetFlowMetadataResultFailure()
|
|
335
|
+
|
|
336
|
+
# We can't completely overwrite metadata.
|
|
337
|
+
for key, value in request.metadata.items():
|
|
338
|
+
flow.metadata[key] = value
|
|
339
|
+
details = f"Successfully set metadata for a Flow '{flow_name}'."
|
|
340
|
+
logger.debug(details)
|
|
341
|
+
|
|
342
|
+
return SetFlowMetadataResultSuccess()
|
|
343
|
+
|
|
151
344
|
def does_canvas_exist(self) -> bool:
|
|
152
345
|
"""Determines if there is already an existing flow with no parent flow.Returns True if there is an existing flow with no parent flow.Return False if there is no existing flow with no parent flow."""
|
|
153
346
|
return any([parent is None for parent in self._name_to_parent_name.values()]) # noqa: C419
|
|
@@ -198,10 +391,19 @@ class FlowManager:
|
|
|
198
391
|
final_flow_name = GriptapeNodes.ObjectManager().generate_name_for_object(
|
|
199
392
|
type_name="ControlFlow", requested_name=request.flow_name
|
|
200
393
|
)
|
|
201
|
-
flow
|
|
394
|
+
# Check if we're creating this flow within a referenced workflow context
|
|
395
|
+
# This will inform the engine to maintain a reference to the workflow
|
|
396
|
+
# when serializing it. It may inform the editor to render it differently.
|
|
397
|
+
workflow_manager = GriptapeNodes.WorkflowManager()
|
|
398
|
+
flow = ControlFlow(name=final_flow_name, metadata=request.metadata)
|
|
202
399
|
GriptapeNodes.ObjectManager().add_object_by_name(name=final_flow_name, obj=flow)
|
|
203
400
|
self._name_to_parent_name[final_flow_name] = parent_name
|
|
204
401
|
|
|
402
|
+
# Track referenced workflow if this flow was created within a referenced workflow context
|
|
403
|
+
if workflow_manager.has_current_referenced_workflow():
|
|
404
|
+
referenced_workflow_name = workflow_manager.get_current_referenced_workflow()
|
|
405
|
+
self._flow_to_referenced_workflow_name[flow] = referenced_workflow_name
|
|
406
|
+
|
|
205
407
|
# See if we need to push it as the current context.
|
|
206
408
|
if request.set_as_new_context:
|
|
207
409
|
GriptapeNodes.ContextManager().push_flow(flow)
|
|
@@ -242,7 +444,7 @@ class FlowManager:
|
|
|
242
444
|
logger.error(details)
|
|
243
445
|
result = DeleteFlowResultFailure()
|
|
244
446
|
return result
|
|
245
|
-
if
|
|
447
|
+
if self.check_for_existing_running_flow():
|
|
246
448
|
result = GriptapeNodes.handle_request(CancelFlowRequest(flow_name=flow.name))
|
|
247
449
|
if not result.succeeded():
|
|
248
450
|
details = f"Attempted to delete flow '{flow_name}'. Failed because running flow could not cancel."
|
|
@@ -306,6 +508,10 @@ class FlowManager:
|
|
|
306
508
|
obj_mgr.del_obj_by_name(flow.name)
|
|
307
509
|
del self._name_to_parent_name[flow.name]
|
|
308
510
|
|
|
511
|
+
# Clean up referenced workflow tracking
|
|
512
|
+
if flow in self._flow_to_referenced_workflow_name:
|
|
513
|
+
del self._flow_to_referenced_workflow_name[flow]
|
|
514
|
+
|
|
309
515
|
details = f"Successfully deleted Flow '{flow_name}'."
|
|
310
516
|
logger.debug(details)
|
|
311
517
|
result = DeleteFlowResultSuccess()
|
|
@@ -313,6 +519,10 @@ class FlowManager:
|
|
|
313
519
|
|
|
314
520
|
def on_get_is_flow_running_request(self, request: GetIsFlowRunningRequest) -> ResultPayload:
|
|
315
521
|
obj_mgr = GriptapeNodes.ObjectManager()
|
|
522
|
+
if request.flow_name is None:
|
|
523
|
+
details = "Attempted to get Flow, but no flow name was provided."
|
|
524
|
+
logger.error(details)
|
|
525
|
+
return GetIsFlowRunningResultFailure()
|
|
316
526
|
flow = obj_mgr.attempt_get_object_by_name_as_type(request.flow_name, ControlFlow)
|
|
317
527
|
if flow is None:
|
|
318
528
|
details = f"Attempted to get Flow '{request.flow_name}', but no Flow with that name could be found."
|
|
@@ -320,7 +530,7 @@ class FlowManager:
|
|
|
320
530
|
result = GetIsFlowRunningResultFailure()
|
|
321
531
|
return result
|
|
322
532
|
try:
|
|
323
|
-
is_running =
|
|
533
|
+
is_running = self.check_for_existing_running_flow()
|
|
324
534
|
except Exception:
|
|
325
535
|
details = f"Error while trying to get status of '{request.flow_name}'."
|
|
326
536
|
logger.error(details)
|
|
@@ -452,10 +662,9 @@ class FlowManager:
|
|
|
452
662
|
# The two nodes exist.
|
|
453
663
|
# Get the parent flows.
|
|
454
664
|
source_flow_name = None
|
|
455
|
-
source_flow = None
|
|
456
665
|
try:
|
|
457
666
|
source_flow_name = GriptapeNodes.NodeManager().get_node_parent_flow_by_name(source_node_name)
|
|
458
|
-
|
|
667
|
+
self.get_flow_by_name(flow_name=source_flow_name)
|
|
459
668
|
except KeyError as err:
|
|
460
669
|
details = f'Connection "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}" failed: {err}.'
|
|
461
670
|
logger.error(details)
|
|
@@ -470,11 +679,7 @@ class FlowManager:
|
|
|
470
679
|
logger.error(details)
|
|
471
680
|
return CreateConnectionResultFailure()
|
|
472
681
|
|
|
473
|
-
#
|
|
474
|
-
if target_flow_name != source_flow_name:
|
|
475
|
-
details = f'Connection "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}" failed: Different flows.'
|
|
476
|
-
logger.error(details)
|
|
477
|
-
return CreateConnectionResultFailure()
|
|
682
|
+
# Cross-flow connections are now supported via global connection storage
|
|
478
683
|
|
|
479
684
|
# Now validate the parameters.
|
|
480
685
|
source_param = source_node.get_parameter_by_name(request.source_parameter_name)
|
|
@@ -543,7 +748,7 @@ class FlowManager:
|
|
|
543
748
|
|
|
544
749
|
# Some scenarios restrict when we can have more than one connection. See if we're in such a scenario and replace the
|
|
545
750
|
# existing connection instead of adding a new one.
|
|
546
|
-
connection_mgr =
|
|
751
|
+
connection_mgr = self._connections
|
|
547
752
|
# Try the OUTGOING restricted scenario first.
|
|
548
753
|
restricted_scenario_connection = connection_mgr.get_existing_connection_for_restricted_scenario(
|
|
549
754
|
node=source_node, parameter=source_param, is_source=True
|
|
@@ -577,7 +782,7 @@ class FlowManager:
|
|
|
577
782
|
logger.debug(details)
|
|
578
783
|
try:
|
|
579
784
|
# Actually create the Connection.
|
|
580
|
-
|
|
785
|
+
self._connections.add_connection(
|
|
581
786
|
source_node=source_node,
|
|
582
787
|
source_parameter=source_param,
|
|
583
788
|
target_node=target_node,
|
|
@@ -699,10 +904,9 @@ class FlowManager:
|
|
|
699
904
|
# The two nodes exist.
|
|
700
905
|
# Get the parent flows.
|
|
701
906
|
source_flow_name = None
|
|
702
|
-
source_flow = None
|
|
703
907
|
try:
|
|
704
908
|
source_flow_name = GriptapeNodes.NodeManager().get_node_parent_flow_by_name(source_node_name)
|
|
705
|
-
|
|
909
|
+
self.get_flow_by_name(flow_name=source_flow_name)
|
|
706
910
|
except KeyError as err:
|
|
707
911
|
details = f'Connection not deleted "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}". Error: {err}'
|
|
708
912
|
logger.error(details)
|
|
@@ -719,12 +923,7 @@ class FlowManager:
|
|
|
719
923
|
|
|
720
924
|
return DeleteConnectionResultFailure()
|
|
721
925
|
|
|
722
|
-
#
|
|
723
|
-
if target_flow_name != source_flow_name:
|
|
724
|
-
details = f'Connection not deleted "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}". They are in different Flows (TEMPORARY RESTRICTION).'
|
|
725
|
-
logger.error(details)
|
|
726
|
-
|
|
727
|
-
return DeleteConnectionResultFailure()
|
|
926
|
+
# Cross-flow connections are now supported via global connection storage
|
|
728
927
|
|
|
729
928
|
# Now validate the parameters.
|
|
730
929
|
source_param = source_node.get_parameter_by_name(request.source_parameter_name)
|
|
@@ -742,7 +941,7 @@ class FlowManager:
|
|
|
742
941
|
return DeleteConnectionResultFailure()
|
|
743
942
|
|
|
744
943
|
# Vet that a Connection actually exists between them already.
|
|
745
|
-
if not
|
|
944
|
+
if not self._has_connection(
|
|
746
945
|
source_node=source_node,
|
|
747
946
|
source_parameter=source_param,
|
|
748
947
|
target_node=target_node,
|
|
@@ -754,11 +953,11 @@ class FlowManager:
|
|
|
754
953
|
return DeleteConnectionResultFailure()
|
|
755
954
|
|
|
756
955
|
# Remove the connection.
|
|
757
|
-
if not
|
|
758
|
-
source_node=source_node,
|
|
759
|
-
source_parameter=source_param,
|
|
760
|
-
target_node=target_node,
|
|
761
|
-
target_parameter=target_param,
|
|
956
|
+
if not self._connections.remove_connection(
|
|
957
|
+
source_node=source_node.name,
|
|
958
|
+
source_parameter=source_param.name,
|
|
959
|
+
target_node=target_node.name,
|
|
960
|
+
target_parameter=target_param.name,
|
|
762
961
|
):
|
|
763
962
|
details = f'Connection not deleted "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}". Unknown failure.'
|
|
764
963
|
logger.error(details)
|
|
@@ -773,7 +972,7 @@ class FlowManager:
|
|
|
773
972
|
target_node.remove_parameter_value(target_param.name)
|
|
774
973
|
# It removed it accurately
|
|
775
974
|
# Unresolve future nodes that depended on that value
|
|
776
|
-
|
|
975
|
+
self._connections.unresolve_future_nodes(target_node)
|
|
777
976
|
target_node.make_node_unresolved(
|
|
778
977
|
current_states_to_trigger_change_event=set(
|
|
779
978
|
{NodeResolutionState.RESOLVED, NodeResolutionState.RESOLVING}
|
|
@@ -816,7 +1015,7 @@ class FlowManager:
|
|
|
816
1015
|
logger.error(details)
|
|
817
1016
|
return StartFlowResultFailure(validation_exceptions=[err])
|
|
818
1017
|
# Check to see if the flow is already running.
|
|
819
|
-
if
|
|
1018
|
+
if self.check_for_existing_running_flow():
|
|
820
1019
|
details = "Cannot start flow. Flow is already running."
|
|
821
1020
|
logger.error(details)
|
|
822
1021
|
return StartFlowResultFailure(validation_exceptions=[])
|
|
@@ -829,7 +1028,7 @@ class FlowManager:
|
|
|
829
1028
|
logger.error(details)
|
|
830
1029
|
return StartFlowResultFailure(validation_exceptions=[])
|
|
831
1030
|
# lets get the first control node in the flow!
|
|
832
|
-
start_node =
|
|
1031
|
+
start_node = self.get_start_node_from_node(flow, flow_node)
|
|
833
1032
|
# if the start is not the node provided, set a breakpoint at the stop (we're running up until there)
|
|
834
1033
|
if not start_node:
|
|
835
1034
|
details = f"Start node for node with name {flow_node_name} does not exist"
|
|
@@ -840,7 +1039,7 @@ class FlowManager:
|
|
|
840
1039
|
else:
|
|
841
1040
|
# we wont hit this if we dont have a request id, our requests always have nodes
|
|
842
1041
|
# If there is a request, reinitialize the queue
|
|
843
|
-
|
|
1042
|
+
self.get_start_node_queue() # initialize the start flow queue!
|
|
844
1043
|
start_node = None
|
|
845
1044
|
# Run Validation before starting a flow
|
|
846
1045
|
result = self.on_validate_flow_dependencies_request(
|
|
@@ -866,7 +1065,7 @@ class FlowManager:
|
|
|
866
1065
|
return StartFlowResultFailure(validation_exceptions=[e])
|
|
867
1066
|
# By now, it has been validated with no exceptions.
|
|
868
1067
|
try:
|
|
869
|
-
|
|
1068
|
+
self.start_flow(flow, start_node, debug_mode)
|
|
870
1069
|
except Exception as e:
|
|
871
1070
|
details = f"Failed to kick off flow with name {flow_name}. Exception occurred: {e} "
|
|
872
1071
|
logger.error(details)
|
|
@@ -890,7 +1089,7 @@ class FlowManager:
|
|
|
890
1089
|
logger.error(details)
|
|
891
1090
|
return GetFlowStateResultFailure()
|
|
892
1091
|
try:
|
|
893
|
-
control_node, resolving_node =
|
|
1092
|
+
control_node, resolving_node = self.flow_state(flow)
|
|
894
1093
|
except Exception as e:
|
|
895
1094
|
details = f"Failed to get flow state of flow with name {flow_name}. Exception occurred: {e} "
|
|
896
1095
|
logger.exception(details)
|
|
@@ -907,14 +1106,14 @@ class FlowManager:
|
|
|
907
1106
|
|
|
908
1107
|
return CancelFlowResultFailure()
|
|
909
1108
|
try:
|
|
910
|
-
|
|
1109
|
+
self.get_flow_by_name(flow_name)
|
|
911
1110
|
except KeyError as err:
|
|
912
1111
|
details = f"Could not cancel flow execution. Error: {err}"
|
|
913
1112
|
logger.error(details)
|
|
914
1113
|
|
|
915
1114
|
return CancelFlowResultFailure()
|
|
916
1115
|
try:
|
|
917
|
-
|
|
1116
|
+
self.cancel_flow_run()
|
|
918
1117
|
except Exception as e:
|
|
919
1118
|
details = f"Could not cancel flow execution. Exception: {e}"
|
|
920
1119
|
logger.error(details)
|
|
@@ -933,14 +1132,15 @@ class FlowManager:
|
|
|
933
1132
|
|
|
934
1133
|
return SingleNodeStepResultFailure(validation_exceptions=[])
|
|
935
1134
|
try:
|
|
936
|
-
|
|
1135
|
+
self.get_flow_by_name(flow_name)
|
|
937
1136
|
except KeyError as err:
|
|
938
1137
|
details = f"Could not advance to the next step of a running workflow. No flow with name {flow_name} exists. Error: {err}"
|
|
939
1138
|
logger.error(details)
|
|
940
1139
|
|
|
941
1140
|
return SingleNodeStepResultFailure(validation_exceptions=[err])
|
|
942
1141
|
try:
|
|
943
|
-
flow.
|
|
1142
|
+
flow = self.get_flow_by_name(flow_name)
|
|
1143
|
+
self.single_node_step(flow)
|
|
944
1144
|
except Exception as e:
|
|
945
1145
|
details = f"Could not advance to the next step of a running workflow. Exception: {e}"
|
|
946
1146
|
logger.error(details)
|
|
@@ -968,12 +1168,12 @@ class FlowManager:
|
|
|
968
1168
|
return SingleExecutionStepResultFailure()
|
|
969
1169
|
change_debug_mode = request.request_id is not None
|
|
970
1170
|
try:
|
|
971
|
-
|
|
1171
|
+
self.single_execution_step(flow, change_debug_mode)
|
|
972
1172
|
except Exception as e:
|
|
973
1173
|
# We REALLY don't want to fail here, else we'll take the whole engine down
|
|
974
1174
|
try:
|
|
975
|
-
if
|
|
976
|
-
|
|
1175
|
+
if self.check_for_existing_running_flow():
|
|
1176
|
+
self.cancel_flow_run()
|
|
977
1177
|
except Exception as e_inner:
|
|
978
1178
|
details = f"Could not cancel flow execution. Exception: {e_inner}"
|
|
979
1179
|
logger.error(details)
|
|
@@ -1001,7 +1201,7 @@ class FlowManager:
|
|
|
1001
1201
|
|
|
1002
1202
|
return ContinueExecutionStepResultFailure()
|
|
1003
1203
|
try:
|
|
1004
|
-
|
|
1204
|
+
self.continue_executing(flow)
|
|
1005
1205
|
except Exception as e:
|
|
1006
1206
|
details = f"Failed to continue execution step. An exception occurred: {e}."
|
|
1007
1207
|
logger.error(details)
|
|
@@ -1023,7 +1223,7 @@ class FlowManager:
|
|
|
1023
1223
|
logger.error(details)
|
|
1024
1224
|
return UnresolveFlowResultFailure()
|
|
1025
1225
|
try:
|
|
1026
|
-
|
|
1226
|
+
self.unresolve_whole_flow(flow)
|
|
1027
1227
|
except Exception as e:
|
|
1028
1228
|
details = f"Failed to unresolve flow. An exception occurred: {e}."
|
|
1029
1229
|
logger.error(details)
|
|
@@ -1108,6 +1308,9 @@ class FlowManager:
|
|
|
1108
1308
|
# Track all node libraries that were in use by these Nodes
|
|
1109
1309
|
node_libraries_in_use = set()
|
|
1110
1310
|
|
|
1311
|
+
# Track all referenced workflows used by this flow and its sub-flows
|
|
1312
|
+
referenced_workflows_in_use = set()
|
|
1313
|
+
|
|
1111
1314
|
# Track all parameter values that were in use by these Nodes (maps UUID to Parameter value)
|
|
1112
1315
|
unique_parameter_uuid_to_values = {}
|
|
1113
1316
|
# And track how values map into that map.
|
|
@@ -1116,7 +1319,20 @@ class FlowManager:
|
|
|
1116
1319
|
with GriptapeNodes.ContextManager().flow(flow):
|
|
1117
1320
|
# The base flow creation, if desired.
|
|
1118
1321
|
if request.include_create_flow_command:
|
|
1119
|
-
|
|
1322
|
+
# Check if this flow is a referenced workflow
|
|
1323
|
+
if self.is_referenced_workflow(flow):
|
|
1324
|
+
referenced_workflow_name = self.get_referenced_workflow_name(flow)
|
|
1325
|
+
create_flow_request = ImportWorkflowAsReferencedSubFlowRequest(
|
|
1326
|
+
workflow_name=referenced_workflow_name, # type: ignore[arg-type] # is_referenced_workflow() guarantees this is not None
|
|
1327
|
+
imported_flow_metadata=flow.metadata,
|
|
1328
|
+
)
|
|
1329
|
+
referenced_workflows_in_use.add(referenced_workflow_name) # type: ignore[arg-type] # is_referenced_workflow() guarantees this is not None
|
|
1330
|
+
else:
|
|
1331
|
+
# Always set set_as_new_context=False during serialization - let the workflow manager
|
|
1332
|
+
# that loads this serialized flow decide whether to push it to context or not
|
|
1333
|
+
create_flow_request = CreateFlowRequest(
|
|
1334
|
+
parent_flow_name=None, set_as_new_context=False, metadata=flow.metadata
|
|
1335
|
+
)
|
|
1120
1336
|
else:
|
|
1121
1337
|
create_flow_request = None
|
|
1122
1338
|
|
|
@@ -1169,7 +1385,7 @@ class FlowManager:
|
|
|
1169
1385
|
# when we're restored.
|
|
1170
1386
|
# Create all of the connections
|
|
1171
1387
|
create_connection_commands = []
|
|
1172
|
-
for connection in
|
|
1388
|
+
for connection in self._get_connections_for_flow(flow):
|
|
1173
1389
|
source_node_uuid = node_name_to_uuid[connection.source_node.name]
|
|
1174
1390
|
target_node_uuid = node_name_to_uuid[connection.target_node.name]
|
|
1175
1391
|
create_connection_command = SerializedFlowCommands.IndirectConnectionSerialization(
|
|
@@ -1197,27 +1413,57 @@ class FlowManager:
|
|
|
1197
1413
|
details = f"Attempted to serialize Flow '{flow_name}', but no Flow with that name could be found."
|
|
1198
1414
|
logger.error(details)
|
|
1199
1415
|
return SerializeFlowToCommandsResultFailure()
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1416
|
+
|
|
1417
|
+
# Check if this is a referenced workflow
|
|
1418
|
+
if self.is_referenced_workflow(flow):
|
|
1419
|
+
# For referenced workflows, create a minimal SerializedFlowCommands with just the import command
|
|
1420
|
+
referenced_workflow_name = self.get_referenced_workflow_name(flow)
|
|
1421
|
+
import_command = ImportWorkflowAsReferencedSubFlowRequest(
|
|
1422
|
+
workflow_name=referenced_workflow_name, # type: ignore[arg-type] # is_referenced_workflow() guarantees this is not None
|
|
1423
|
+
imported_flow_metadata=flow.metadata,
|
|
1424
|
+
)
|
|
1425
|
+
|
|
1426
|
+
serialized_flow = SerializedFlowCommands(
|
|
1427
|
+
node_libraries_used=set(),
|
|
1428
|
+
flow_initialization_command=import_command,
|
|
1429
|
+
serialized_node_commands=[],
|
|
1430
|
+
serialized_connections=[],
|
|
1431
|
+
unique_parameter_uuid_to_values={},
|
|
1432
|
+
set_parameter_value_commands={},
|
|
1433
|
+
sub_flows_commands=[],
|
|
1434
|
+
referenced_workflows={referenced_workflow_name}, # type: ignore[arg-type] # is_referenced_workflow() guarantees this is not None
|
|
1435
|
+
)
|
|
1208
1436
|
sub_flow_commands.append(serialized_flow)
|
|
1209
1437
|
|
|
1210
|
-
#
|
|
1211
|
-
|
|
1438
|
+
# Add this referenced workflow to our accumulation
|
|
1439
|
+
referenced_workflows_in_use.add(referenced_workflow_name) # type: ignore[arg-type] # is_referenced_workflow() guarantees this is not None
|
|
1440
|
+
else:
|
|
1441
|
+
# For standalone sub-flows, use the existing recursive serialization
|
|
1442
|
+
with GriptapeNodes.ContextManager().flow(flow=flow):
|
|
1443
|
+
child_flow_request = SerializeFlowToCommandsRequest()
|
|
1444
|
+
child_flow_result = GriptapeNodes().handle_request(child_flow_request)
|
|
1445
|
+
if not isinstance(child_flow_result, SerializeFlowToCommandsResultSuccess):
|
|
1446
|
+
details = f"Attempted to serialize parent flow '{flow_name}'. Failed while serializing child flow '{child_flow}'."
|
|
1447
|
+
logger.error(details)
|
|
1448
|
+
return SerializeFlowToCommandsResultFailure()
|
|
1449
|
+
serialized_flow = child_flow_result.serialized_flow_commands
|
|
1450
|
+
sub_flow_commands.append(serialized_flow)
|
|
1451
|
+
|
|
1452
|
+
# Merge in all child flow library details.
|
|
1453
|
+
node_libraries_in_use.union(serialized_flow.node_libraries_used)
|
|
1454
|
+
|
|
1455
|
+
# Merge in all child flow referenced workflows.
|
|
1456
|
+
referenced_workflows_in_use.union(serialized_flow.referenced_workflows)
|
|
1212
1457
|
|
|
1213
1458
|
serialized_flow = SerializedFlowCommands(
|
|
1214
|
-
|
|
1459
|
+
flow_initialization_command=create_flow_request,
|
|
1215
1460
|
serialized_node_commands=serialized_node_commands,
|
|
1216
1461
|
serialized_connections=create_connection_commands,
|
|
1217
1462
|
unique_parameter_uuid_to_values=unique_parameter_uuid_to_values,
|
|
1218
1463
|
set_parameter_value_commands=set_parameter_value_commands_per_node,
|
|
1219
1464
|
sub_flows_commands=sub_flow_commands,
|
|
1220
1465
|
node_libraries_used=node_libraries_in_use,
|
|
1466
|
+
referenced_workflows=referenced_workflows_in_use,
|
|
1221
1467
|
)
|
|
1222
1468
|
details = f"Successfully serialized Flow '{flow_name}' into commands."
|
|
1223
1469
|
result = SerializeFlowToCommandsResultSuccess(serialized_flow_commands=serialized_flow)
|
|
@@ -1225,7 +1471,7 @@ class FlowManager:
|
|
|
1225
1471
|
|
|
1226
1472
|
def on_deserialize_flow_from_commands(self, request: DeserializeFlowFromCommandsRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912, PLR0915 (I am big and complicated and have a lot of negative edge-cases)
|
|
1227
1473
|
# Do we want to create a NEW Flow to deserialize into, or use the one in the Current Context?
|
|
1228
|
-
if request.serialized_flow_commands.
|
|
1474
|
+
if request.serialized_flow_commands.flow_initialization_command is None:
|
|
1229
1475
|
if GriptapeNodes.ContextManager().has_current_flow():
|
|
1230
1476
|
flow = GriptapeNodes.ContextManager().get_current_flow()
|
|
1231
1477
|
flow_name = flow.name
|
|
@@ -1235,18 +1481,32 @@ class FlowManager:
|
|
|
1235
1481
|
return DeserializeFlowFromCommandsResultFailure()
|
|
1236
1482
|
else:
|
|
1237
1483
|
# Issue the creation command first.
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1484
|
+
flow_initialization_command = request.serialized_flow_commands.flow_initialization_command
|
|
1485
|
+
flow_initialization_result = GriptapeNodes.handle_request(flow_initialization_command)
|
|
1486
|
+
|
|
1487
|
+
# Handle different types of creation commands
|
|
1488
|
+
match flow_initialization_command:
|
|
1489
|
+
case CreateFlowRequest():
|
|
1490
|
+
if not isinstance(flow_initialization_result, CreateFlowResultSuccess):
|
|
1491
|
+
details = f"Attempted to deserialize a serialized set of Flow Creation commands. Failed to create flow '{flow_initialization_command.flow_name}'."
|
|
1492
|
+
logger.error(details)
|
|
1493
|
+
return DeserializeFlowFromCommandsResultFailure()
|
|
1494
|
+
flow_name = flow_initialization_result.flow_name
|
|
1495
|
+
case ImportWorkflowAsReferencedSubFlowRequest():
|
|
1496
|
+
if not isinstance(flow_initialization_result, ImportWorkflowAsReferencedSubFlowResultSuccess):
|
|
1497
|
+
details = f"Attempted to deserialize a serialized set of Flow Creation commands. Failed to import workflow '{flow_initialization_command.workflow_name}'."
|
|
1498
|
+
logger.error(details)
|
|
1499
|
+
return DeserializeFlowFromCommandsResultFailure()
|
|
1500
|
+
flow_name = flow_initialization_result.created_flow_name
|
|
1501
|
+
case _:
|
|
1502
|
+
details = f"Attempted to deserialize Flow Creation commands with unknown command type: {type(flow_initialization_command).__name__}."
|
|
1503
|
+
logger.error(details)
|
|
1504
|
+
return DeserializeFlowFromCommandsResultFailure()
|
|
1244
1505
|
|
|
1245
1506
|
# Adopt the newly-created flow as our current context.
|
|
1246
|
-
flow_name = create_flow_result.flow_name
|
|
1247
1507
|
flow = GriptapeNodes.ObjectManager().attempt_get_object_by_name_as_type(flow_name, ControlFlow)
|
|
1248
1508
|
if flow is None:
|
|
1249
|
-
details = f"Attempted to deserialize a serialized set of Flow Creation commands. Failed to
|
|
1509
|
+
details = f"Attempted to deserialize a serialized set of Flow Creation commands. Failed to find created flow '{flow_name}'."
|
|
1250
1510
|
logger.error(details)
|
|
1251
1511
|
return DeserializeFlowFromCommandsResultFailure()
|
|
1252
1512
|
GriptapeNodes.ContextManager().push_flow(flow=flow)
|
|
@@ -1358,3 +1618,428 @@ class FlowManager:
|
|
|
1358
1618
|
if node._tracked_parameters:
|
|
1359
1619
|
node.emit_parameter_changes()
|
|
1360
1620
|
return FlushParameterChangesResultSuccess()
|
|
1621
|
+
|
|
1622
|
+
def start_flow(self, flow: ControlFlow, start_node: BaseNode | None = None, debug_mode: bool = False) -> None: # noqa: FBT001, FBT002, ARG002
|
|
1623
|
+
if self.check_for_existing_running_flow():
|
|
1624
|
+
# If flow already exists, throw an error
|
|
1625
|
+
errormsg = "This workflow is already in progress. Please wait for the current process to finish before starting again."
|
|
1626
|
+
raise RuntimeError(errormsg)
|
|
1627
|
+
|
|
1628
|
+
if start_node is None:
|
|
1629
|
+
if self._global_flow_queue.empty():
|
|
1630
|
+
errormsg = "No Flow exists. You must create at least one control connection."
|
|
1631
|
+
raise RuntimeError(errormsg)
|
|
1632
|
+
start_node = self._global_flow_queue.get()
|
|
1633
|
+
self._global_flow_queue.task_done()
|
|
1634
|
+
|
|
1635
|
+
# Initialize global control flow machine if needed
|
|
1636
|
+
if self._global_control_flow_machine is None:
|
|
1637
|
+
self._global_control_flow_machine = ControlFlowMachine()
|
|
1638
|
+
|
|
1639
|
+
try:
|
|
1640
|
+
self._global_control_flow_machine.start_flow(start_node, debug_mode)
|
|
1641
|
+
except Exception:
|
|
1642
|
+
if self.check_for_existing_running_flow():
|
|
1643
|
+
self.cancel_flow_run()
|
|
1644
|
+
raise
|
|
1645
|
+
|
|
1646
|
+
def check_for_existing_running_flow(self) -> bool:
|
|
1647
|
+
if self._global_control_flow_machine is None:
|
|
1648
|
+
return False
|
|
1649
|
+
if (
|
|
1650
|
+
self._global_control_flow_machine._current_state is not CompleteState
|
|
1651
|
+
and self._global_control_flow_machine._current_state
|
|
1652
|
+
):
|
|
1653
|
+
# Flow already exists in progress
|
|
1654
|
+
return True
|
|
1655
|
+
return bool(
|
|
1656
|
+
not self._global_control_flow_machine._context.resolution_machine.is_complete()
|
|
1657
|
+
and self._global_control_flow_machine._context.resolution_machine.is_started()
|
|
1658
|
+
)
|
|
1659
|
+
|
|
1660
|
+
def cancel_flow_run(self) -> None:
|
|
1661
|
+
if not self.check_for_existing_running_flow():
|
|
1662
|
+
errormsg = "Flow has not yet been started. Cannot cancel flow that hasn't begun."
|
|
1663
|
+
raise RuntimeError(errormsg)
|
|
1664
|
+
self._global_flow_queue.queue.clear()
|
|
1665
|
+
if self._global_control_flow_machine is not None:
|
|
1666
|
+
self._global_control_flow_machine.reset_machine()
|
|
1667
|
+
# Reset control flow machine
|
|
1668
|
+
self._global_single_node_resolution = False
|
|
1669
|
+
logger.debug("Cancelling flow run")
|
|
1670
|
+
|
|
1671
|
+
EventBus.publish_event(
|
|
1672
|
+
ExecutionGriptapeNodeEvent(wrapped_event=ExecutionEvent(payload=ControlFlowCancelledEvent()))
|
|
1673
|
+
)
|
|
1674
|
+
|
|
1675
|
+
def reset_global_execution_state(self) -> None:
|
|
1676
|
+
"""Reset all global execution state - useful when clearing all workflows."""
|
|
1677
|
+
self._global_flow_queue.queue.clear()
|
|
1678
|
+
if self._global_control_flow_machine is not None:
|
|
1679
|
+
self._global_control_flow_machine.reset_machine()
|
|
1680
|
+
self._global_control_flow_machine = None
|
|
1681
|
+
self._global_single_node_resolution = False
|
|
1682
|
+
|
|
1683
|
+
# Clear all connections to prevent memory leaks and stale references
|
|
1684
|
+
self._connections.connections.clear()
|
|
1685
|
+
self._connections.outgoing_index.clear()
|
|
1686
|
+
self._connections.incoming_index.clear()
|
|
1687
|
+
|
|
1688
|
+
logger.debug("Reset global execution state")
|
|
1689
|
+
|
|
1690
|
+
# Public methods to replace private variable access from external classes
|
|
1691
|
+
def is_execution_queue_empty(self) -> bool:
|
|
1692
|
+
"""Check if the global execution queue is empty."""
|
|
1693
|
+
return self._global_flow_queue.empty()
|
|
1694
|
+
|
|
1695
|
+
def get_next_node_from_execution_queue(self) -> BaseNode | None:
|
|
1696
|
+
"""Get the next node from the global execution queue, or None if empty."""
|
|
1697
|
+
if self._global_flow_queue.empty():
|
|
1698
|
+
return None
|
|
1699
|
+
node = self._global_flow_queue.get()
|
|
1700
|
+
self._global_flow_queue.task_done()
|
|
1701
|
+
return node
|
|
1702
|
+
|
|
1703
|
+
def clear_execution_queue(self) -> None:
|
|
1704
|
+
"""Clear all nodes from the global execution queue."""
|
|
1705
|
+
self._global_flow_queue.queue.clear()
|
|
1706
|
+
|
|
1707
|
+
def has_connection(
|
|
1708
|
+
self,
|
|
1709
|
+
source_node: BaseNode,
|
|
1710
|
+
source_parameter: Parameter,
|
|
1711
|
+
target_node: BaseNode,
|
|
1712
|
+
target_parameter: Parameter,
|
|
1713
|
+
) -> bool:
|
|
1714
|
+
"""Check if a connection exists between the specified nodes and parameters."""
|
|
1715
|
+
return self._has_connection(source_node, source_parameter, target_node, target_parameter)
|
|
1716
|
+
|
|
1717
|
+
# Internal execution queue helper methods to consolidate redundant operations
|
|
1718
|
+
def _handle_flow_start_if_not_running(self, flow: ControlFlow, *, debug_mode: bool, error_message: str) -> None: # noqa: ARG002
|
|
1719
|
+
"""Common logic for starting flow execution if not already running."""
|
|
1720
|
+
if not self.check_for_existing_running_flow():
|
|
1721
|
+
if self._global_flow_queue.empty():
|
|
1722
|
+
raise RuntimeError(error_message)
|
|
1723
|
+
start_node = self._global_flow_queue.get()
|
|
1724
|
+
self._global_flow_queue.task_done()
|
|
1725
|
+
if self._global_control_flow_machine is None:
|
|
1726
|
+
self._global_control_flow_machine = ControlFlowMachine()
|
|
1727
|
+
self._global_control_flow_machine.start_flow(start_node, debug_mode)
|
|
1728
|
+
|
|
1729
|
+
def _handle_post_execution_queue_processing(self, *, debug_mode: bool) -> None:
|
|
1730
|
+
"""Handle execution queue processing after execution completes."""
|
|
1731
|
+
if not self.check_for_existing_running_flow() and not self._global_flow_queue.empty():
|
|
1732
|
+
start_node = self._global_flow_queue.get()
|
|
1733
|
+
self._global_flow_queue.task_done()
|
|
1734
|
+
if self._global_control_flow_machine is not None:
|
|
1735
|
+
self._global_control_flow_machine.start_flow(start_node, debug_mode)
|
|
1736
|
+
|
|
1737
|
+
def resolve_singular_node(self, flow: ControlFlow, node: BaseNode, debug_mode: bool = False) -> None: # noqa: FBT001, FBT002, ARG002
|
|
1738
|
+
# Set that we are only working on one node right now! no other stepping allowed
|
|
1739
|
+
if self.check_for_existing_running_flow():
|
|
1740
|
+
# If flow already exists, throw an error
|
|
1741
|
+
errormsg = f"This workflow is already in progress. Please wait for the current process to finish before starting {node.name} again."
|
|
1742
|
+
raise RuntimeError(errormsg)
|
|
1743
|
+
self._global_single_node_resolution = True
|
|
1744
|
+
# Initialize global control flow machine if needed
|
|
1745
|
+
if self._global_control_flow_machine is None:
|
|
1746
|
+
self._global_control_flow_machine = ControlFlowMachine()
|
|
1747
|
+
# Get the node resolution machine for the current flow!
|
|
1748
|
+
self._global_control_flow_machine._context.current_node = node
|
|
1749
|
+
resolution_machine = self._global_control_flow_machine._context.resolution_machine
|
|
1750
|
+
# Set debug mode
|
|
1751
|
+
resolution_machine.change_debug_mode(debug_mode)
|
|
1752
|
+
# Resolve the node.
|
|
1753
|
+
node.state = NodeResolutionState.UNRESOLVED
|
|
1754
|
+
resolution_machine.resolve_node(node)
|
|
1755
|
+
# decide if we can change it back to normal flow mode!
|
|
1756
|
+
if resolution_machine.is_complete():
|
|
1757
|
+
self._global_single_node_resolution = False
|
|
1758
|
+
self._global_control_flow_machine._context.current_node = None
|
|
1759
|
+
|
|
1760
|
+
def single_execution_step(self, flow: ControlFlow, change_debug_mode: bool) -> None: # noqa: FBT001
|
|
1761
|
+
# do a granular step
|
|
1762
|
+
self._handle_flow_start_if_not_running(
|
|
1763
|
+
flow, debug_mode=True, error_message="Flow has not yet been started. Cannot step while no flow has begun."
|
|
1764
|
+
)
|
|
1765
|
+
if not self.check_for_existing_running_flow():
|
|
1766
|
+
return
|
|
1767
|
+
if self._global_control_flow_machine is not None:
|
|
1768
|
+
self._global_control_flow_machine.granular_step(change_debug_mode)
|
|
1769
|
+
resolution_machine = self._global_control_flow_machine._context.resolution_machine
|
|
1770
|
+
if self._global_single_node_resolution:
|
|
1771
|
+
resolution_machine = self._global_control_flow_machine._context.resolution_machine
|
|
1772
|
+
if resolution_machine.is_complete():
|
|
1773
|
+
self._global_single_node_resolution = False
|
|
1774
|
+
|
|
1775
|
+
def single_node_step(self, flow: ControlFlow) -> None:
|
|
1776
|
+
# It won't call single_node_step without an existing flow running from US.
|
|
1777
|
+
self._handle_flow_start_if_not_running(
|
|
1778
|
+
flow, debug_mode=True, error_message="Flow has not yet been started. Cannot step while no flow has begun."
|
|
1779
|
+
)
|
|
1780
|
+
if not self.check_for_existing_running_flow():
|
|
1781
|
+
return
|
|
1782
|
+
# Step over a whole node
|
|
1783
|
+
if self._global_single_node_resolution:
|
|
1784
|
+
msg = "Cannot step through the Control Flow in Single Node Execution"
|
|
1785
|
+
raise RuntimeError(msg)
|
|
1786
|
+
if self._global_control_flow_machine is not None:
|
|
1787
|
+
self._global_control_flow_machine.node_step()
|
|
1788
|
+
# Start the next resolution step now please.
|
|
1789
|
+
self._handle_post_execution_queue_processing(debug_mode=True)
|
|
1790
|
+
|
|
1791
|
+
def continue_executing(self, flow: ControlFlow) -> None:
|
|
1792
|
+
self._handle_flow_start_if_not_running(
|
|
1793
|
+
flow, debug_mode=False, error_message="Flow has not yet been started. Cannot step while no flow has begun."
|
|
1794
|
+
)
|
|
1795
|
+
if not self.check_for_existing_running_flow():
|
|
1796
|
+
return
|
|
1797
|
+
# Turn all debugging to false and continue on
|
|
1798
|
+
if self._global_control_flow_machine is not None:
|
|
1799
|
+
self._global_control_flow_machine.change_debug_mode(False)
|
|
1800
|
+
if self._global_single_node_resolution:
|
|
1801
|
+
if self._global_control_flow_machine._context.resolution_machine.is_complete():
|
|
1802
|
+
self._global_single_node_resolution = False
|
|
1803
|
+
else:
|
|
1804
|
+
self._global_control_flow_machine._context.resolution_machine.update()
|
|
1805
|
+
else:
|
|
1806
|
+
self._global_control_flow_machine.node_step()
|
|
1807
|
+
# Now it is done executing. make sure it's actually done?
|
|
1808
|
+
self._handle_post_execution_queue_processing(debug_mode=False)
|
|
1809
|
+
|
|
1810
|
+
def unresolve_whole_flow(self, flow: ControlFlow) -> None:
|
|
1811
|
+
for node in flow.nodes.values():
|
|
1812
|
+
node.make_node_unresolved(current_states_to_trigger_change_event=None)
|
|
1813
|
+
|
|
1814
|
+
def flow_state(self, flow: ControlFlow) -> tuple[str | None, str | None]: # noqa: ARG002
|
|
1815
|
+
if not self.check_for_existing_running_flow():
|
|
1816
|
+
msg = "Flow hasn't started."
|
|
1817
|
+
raise RuntimeError(msg)
|
|
1818
|
+
if self._global_control_flow_machine is None:
|
|
1819
|
+
return None, None
|
|
1820
|
+
current_control_node = (
|
|
1821
|
+
self._global_control_flow_machine._context.current_node.name
|
|
1822
|
+
if self._global_control_flow_machine._context.current_node is not None
|
|
1823
|
+
else None
|
|
1824
|
+
)
|
|
1825
|
+
focus_stack_for_node = self._global_control_flow_machine._context.resolution_machine._context.focus_stack
|
|
1826
|
+
current_resolving_node = focus_stack_for_node[-1].node.name if len(focus_stack_for_node) else None
|
|
1827
|
+
return current_control_node, current_resolving_node
|
|
1828
|
+
|
|
1829
|
+
def get_start_node_from_node(self, flow: ControlFlow, node: BaseNode) -> BaseNode | None:
|
|
1830
|
+
# backwards chain in control outputs.
|
|
1831
|
+
if node not in flow.nodes.values():
|
|
1832
|
+
return None
|
|
1833
|
+
# Go back through incoming control connections to get the start node
|
|
1834
|
+
curr_node = node
|
|
1835
|
+
prev_node = self.get_prev_node(flow, curr_node)
|
|
1836
|
+
# Fencepost loop - get the first previous node name and then we go
|
|
1837
|
+
while prev_node:
|
|
1838
|
+
curr_node = prev_node
|
|
1839
|
+
prev_node = self.get_prev_node(flow, prev_node)
|
|
1840
|
+
return curr_node
|
|
1841
|
+
|
|
1842
|
+
def get_prev_node(self, flow: ControlFlow, node: BaseNode) -> BaseNode | None: # noqa: ARG002
|
|
1843
|
+
connections = self.get_connections()
|
|
1844
|
+
if node.name in connections.incoming_index:
|
|
1845
|
+
parameters = connections.incoming_index[node.name]
|
|
1846
|
+
for parameter_name in parameters:
|
|
1847
|
+
parameter = node.get_parameter_by_name(parameter_name)
|
|
1848
|
+
if parameter and ParameterTypeBuiltin.CONTROL_TYPE.value == parameter.output_type:
|
|
1849
|
+
# this is a control connection
|
|
1850
|
+
connection_ids = connections.incoming_index[node.name][parameter_name]
|
|
1851
|
+
for connection_id in connection_ids:
|
|
1852
|
+
connection = connections.connections[connection_id]
|
|
1853
|
+
return connection.get_source_node()
|
|
1854
|
+
return None
|
|
1855
|
+
|
|
1856
|
+
def get_start_node_queue(self) -> Queue | None: # noqa: C901, PLR0912
|
|
1857
|
+
# For cross-flow execution, we need to consider ALL nodes across ALL flows
|
|
1858
|
+
# Clear and use the global execution queue
|
|
1859
|
+
self._global_flow_queue.queue.clear()
|
|
1860
|
+
|
|
1861
|
+
# Get all flows and collect all nodes across all flows
|
|
1862
|
+
all_flows = GriptapeNodes.ObjectManager().get_filtered_subset(type=ControlFlow)
|
|
1863
|
+
all_nodes = []
|
|
1864
|
+
for current_flow in all_flows.values():
|
|
1865
|
+
all_nodes.extend(current_flow.nodes.values())
|
|
1866
|
+
|
|
1867
|
+
# if no nodes across all flows, no execution possible
|
|
1868
|
+
if not all_nodes:
|
|
1869
|
+
return None
|
|
1870
|
+
|
|
1871
|
+
data_nodes = []
|
|
1872
|
+
valid_data_nodes = []
|
|
1873
|
+
start_nodes = []
|
|
1874
|
+
control_nodes = []
|
|
1875
|
+
for node in all_nodes:
|
|
1876
|
+
# if it's a start node, start here! Return the first one!
|
|
1877
|
+
if isinstance(node, StartNode):
|
|
1878
|
+
start_nodes.append(node)
|
|
1879
|
+
continue
|
|
1880
|
+
# no start nodes. let's find the first control node.
|
|
1881
|
+
# if it's a control node, there could be a flow.
|
|
1882
|
+
control_param = False
|
|
1883
|
+
for parameter in node.parameters:
|
|
1884
|
+
if ParameterTypeBuiltin.CONTROL_TYPE.value == parameter.output_type:
|
|
1885
|
+
control_param = True
|
|
1886
|
+
break
|
|
1887
|
+
if not control_param:
|
|
1888
|
+
# saving this for later
|
|
1889
|
+
data_nodes.append(node)
|
|
1890
|
+
# If this node doesn't have a control connection..
|
|
1891
|
+
continue
|
|
1892
|
+
|
|
1893
|
+
cn_mgr = self.get_connections()
|
|
1894
|
+
# check if it has an incoming connection. If it does, it's not a start node
|
|
1895
|
+
has_control_connection = False
|
|
1896
|
+
if node.name in cn_mgr.incoming_index:
|
|
1897
|
+
for param_name in cn_mgr.incoming_index[node.name]:
|
|
1898
|
+
param = node.get_parameter_by_name(param_name)
|
|
1899
|
+
if param and ParameterTypeBuiltin.CONTROL_TYPE.value == param.output_type:
|
|
1900
|
+
# there is a control connection coming in
|
|
1901
|
+
has_control_connection = True
|
|
1902
|
+
break
|
|
1903
|
+
# if there is a connection coming in, isn't a start.
|
|
1904
|
+
if has_control_connection and not isinstance(node, StartLoopNode):
|
|
1905
|
+
continue
|
|
1906
|
+
# Does it have an outgoing connection?
|
|
1907
|
+
if node.name in cn_mgr.outgoing_index:
|
|
1908
|
+
# If one of the outgoing connections is control, add it. otherwise don't.
|
|
1909
|
+
for param_name in cn_mgr.outgoing_index[node.name]:
|
|
1910
|
+
param = node.get_parameter_by_name(param_name)
|
|
1911
|
+
if param and ParameterTypeBuiltin.CONTROL_TYPE.value == param.output_type:
|
|
1912
|
+
control_nodes.append(node)
|
|
1913
|
+
break
|
|
1914
|
+
else:
|
|
1915
|
+
control_nodes.append(node)
|
|
1916
|
+
|
|
1917
|
+
# If we've gotten to this point, there are no control parameters
|
|
1918
|
+
# Let's return a data node that has no OUTGOING data connections!
|
|
1919
|
+
for node in data_nodes:
|
|
1920
|
+
cn_mgr = self.get_connections()
|
|
1921
|
+
# check if it has an outgoing connection. We don't want it to (that means we get the most resolution)
|
|
1922
|
+
if node.name not in cn_mgr.outgoing_index:
|
|
1923
|
+
valid_data_nodes.append(node)
|
|
1924
|
+
# ok now - populate the global flow queue
|
|
1925
|
+
for node in start_nodes:
|
|
1926
|
+
self._global_flow_queue.put(node)
|
|
1927
|
+
for node in control_nodes:
|
|
1928
|
+
self._global_flow_queue.put(node)
|
|
1929
|
+
for node in valid_data_nodes:
|
|
1930
|
+
self._global_flow_queue.put(node)
|
|
1931
|
+
|
|
1932
|
+
return self._global_flow_queue
|
|
1933
|
+
|
|
1934
|
+
def get_connected_input_from_node(self, flow: ControlFlow, node: BaseNode) -> list[tuple[BaseNode, Parameter]]: # noqa: ARG002
|
|
1935
|
+
global_connections = self.get_connections()
|
|
1936
|
+
connections = []
|
|
1937
|
+
if node.name in global_connections.incoming_index:
|
|
1938
|
+
connection_ids = [
|
|
1939
|
+
item for value_list in global_connections.incoming_index[node.name].values() for item in value_list
|
|
1940
|
+
]
|
|
1941
|
+
for connection_id in connection_ids:
|
|
1942
|
+
connection = global_connections.connections[connection_id]
|
|
1943
|
+
connections.append((connection.source_node, connection.source_parameter))
|
|
1944
|
+
return connections
|
|
1945
|
+
|
|
1946
|
+
def get_connected_output_from_node(self, flow: ControlFlow, node: BaseNode) -> list[tuple[BaseNode, Parameter]]: # noqa: ARG002
|
|
1947
|
+
global_connections = self.get_connections()
|
|
1948
|
+
connections = []
|
|
1949
|
+
if node.name in global_connections.outgoing_index:
|
|
1950
|
+
connection_ids = [
|
|
1951
|
+
item for value_list in global_connections.outgoing_index[node.name].values() for item in value_list
|
|
1952
|
+
]
|
|
1953
|
+
for connection_id in connection_ids:
|
|
1954
|
+
connection = global_connections.connections[connection_id]
|
|
1955
|
+
connections.append((connection.target_node, connection.target_parameter))
|
|
1956
|
+
return connections
|
|
1957
|
+
|
|
1958
|
+
def get_connected_input_parameters(
|
|
1959
|
+
self,
|
|
1960
|
+
flow: ControlFlow, # noqa: ARG002
|
|
1961
|
+
node: BaseNode,
|
|
1962
|
+
param: Parameter,
|
|
1963
|
+
) -> list[tuple[BaseNode, Parameter]]:
|
|
1964
|
+
global_connections = self.get_connections()
|
|
1965
|
+
connections = []
|
|
1966
|
+
if node.name in global_connections.incoming_index:
|
|
1967
|
+
incoming_params = global_connections.incoming_index[node.name]
|
|
1968
|
+
if param.name in incoming_params:
|
|
1969
|
+
for connection_id in incoming_params[param.name]:
|
|
1970
|
+
connection = global_connections.connections[connection_id]
|
|
1971
|
+
connections.append((connection.source_node, connection.source_parameter))
|
|
1972
|
+
return connections
|
|
1973
|
+
|
|
1974
|
+
def get_connections_on_node(self, flow: ControlFlow, node: BaseNode) -> list[BaseNode] | None: # noqa: ARG002
|
|
1975
|
+
connections = self.get_connections()
|
|
1976
|
+
# get all of the connection ids
|
|
1977
|
+
connected_nodes = []
|
|
1978
|
+
# Handle outgoing connections
|
|
1979
|
+
if node.name in connections.outgoing_index:
|
|
1980
|
+
outgoing_params = connections.outgoing_index[node.name]
|
|
1981
|
+
outgoing_connection_ids = []
|
|
1982
|
+
for connection_ids in outgoing_params.values():
|
|
1983
|
+
outgoing_connection_ids = outgoing_connection_ids + connection_ids
|
|
1984
|
+
for connection_id in outgoing_connection_ids:
|
|
1985
|
+
connection = connections.connections[connection_id]
|
|
1986
|
+
if connection.source_node not in connected_nodes:
|
|
1987
|
+
connected_nodes.append(connection.target_node)
|
|
1988
|
+
# Handle incoming connections
|
|
1989
|
+
if node.name in connections.incoming_index:
|
|
1990
|
+
incoming_params = connections.incoming_index[node.name]
|
|
1991
|
+
incoming_connection_ids = []
|
|
1992
|
+
for connection_ids in incoming_params.values():
|
|
1993
|
+
incoming_connection_ids = incoming_connection_ids + connection_ids
|
|
1994
|
+
for connection_id in incoming_connection_ids:
|
|
1995
|
+
connection = connections.connections[connection_id]
|
|
1996
|
+
if connection.source_node not in connected_nodes:
|
|
1997
|
+
connected_nodes.append(connection.source_node)
|
|
1998
|
+
# Return all connected nodes. No duplicates
|
|
1999
|
+
return connected_nodes
|
|
2000
|
+
|
|
2001
|
+
def get_all_connected_nodes(self, flow: ControlFlow, node: BaseNode) -> list[BaseNode]:
|
|
2002
|
+
discovered = {}
|
|
2003
|
+
processed = {}
|
|
2004
|
+
queue = Queue()
|
|
2005
|
+
queue.put(node)
|
|
2006
|
+
discovered[node] = True
|
|
2007
|
+
while not queue.empty():
|
|
2008
|
+
curr_node = queue.get()
|
|
2009
|
+
processed[curr_node] = True
|
|
2010
|
+
next_nodes = self.get_connections_on_node(flow, curr_node)
|
|
2011
|
+
if next_nodes:
|
|
2012
|
+
for next_node in next_nodes:
|
|
2013
|
+
if next_node not in discovered:
|
|
2014
|
+
discovered[next_node] = True
|
|
2015
|
+
queue.put(next_node)
|
|
2016
|
+
return list(processed.keys())
|
|
2017
|
+
|
|
2018
|
+
def get_node_dependencies(self, flow: ControlFlow, node: BaseNode) -> list[BaseNode]:
|
|
2019
|
+
"""Get all upstream nodes that the given node depends on.
|
|
2020
|
+
|
|
2021
|
+
This method performs a breadth-first search starting from the given node and working backwards through its non-control input connections to identify all nodes that must run before this node can be resolved.
|
|
2022
|
+
It ignores control connections, since we're only focusing on node dependencies.
|
|
2023
|
+
|
|
2024
|
+
Args:
|
|
2025
|
+
flow (ControlFlow): The flow containing the node
|
|
2026
|
+
node (BaseNode): The node to find dependencies for
|
|
2027
|
+
|
|
2028
|
+
Returns:
|
|
2029
|
+
list[BaseNode]: A list of all nodes that the given node depends on, including the node itself (as the first element)
|
|
2030
|
+
"""
|
|
2031
|
+
node_list = [node]
|
|
2032
|
+
node_queue = Queue()
|
|
2033
|
+
node_queue.put(node)
|
|
2034
|
+
while not node_queue.empty():
|
|
2035
|
+
curr_node = node_queue.get()
|
|
2036
|
+
input_connections = self.get_connected_input_from_node(flow, curr_node)
|
|
2037
|
+
if input_connections:
|
|
2038
|
+
for input_node, input_parameter in input_connections:
|
|
2039
|
+
if (
|
|
2040
|
+
ParameterTypeBuiltin.CONTROL_TYPE.value != input_parameter.output_type
|
|
2041
|
+
and input_node not in node_list
|
|
2042
|
+
):
|
|
2043
|
+
node_list.append(input_node)
|
|
2044
|
+
node_queue.put(input_node)
|
|
2045
|
+
return node_list
|