griptape-nodes 0.58.1__py3-none-any.whl → 0.59.1__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.
Files changed (30) hide show
  1. griptape_nodes/bootstrap/utils/python_subprocess_executor.py +2 -2
  2. griptape_nodes/bootstrap/workflow_executors/local_session_workflow_executor.py +0 -5
  3. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +9 -5
  4. griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +0 -1
  5. griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +1 -3
  6. griptape_nodes/bootstrap/workflow_publishers/local_workflow_publisher.py +1 -1
  7. griptape_nodes/cli/commands/init.py +53 -7
  8. griptape_nodes/cli/shared.py +1 -0
  9. griptape_nodes/common/node_executor.py +218 -42
  10. griptape_nodes/exe_types/core_types.py +46 -0
  11. griptape_nodes/exe_types/node_types.py +272 -0
  12. griptape_nodes/machines/control_flow.py +222 -16
  13. griptape_nodes/machines/dag_builder.py +212 -1
  14. griptape_nodes/machines/parallel_resolution.py +237 -4
  15. griptape_nodes/node_library/workflow_registry.py +1 -1
  16. griptape_nodes/retained_mode/events/execution_events.py +5 -4
  17. griptape_nodes/retained_mode/events/flow_events.py +17 -67
  18. griptape_nodes/retained_mode/events/parameter_events.py +122 -1
  19. griptape_nodes/retained_mode/managers/event_manager.py +17 -13
  20. griptape_nodes/retained_mode/managers/flow_manager.py +316 -573
  21. griptape_nodes/retained_mode/managers/library_manager.py +32 -20
  22. griptape_nodes/retained_mode/managers/model_manager.py +19 -8
  23. griptape_nodes/retained_mode/managers/node_manager.py +463 -3
  24. griptape_nodes/retained_mode/managers/object_manager.py +2 -2
  25. griptape_nodes/retained_mode/managers/workflow_manager.py +37 -46
  26. griptape_nodes/retained_mode/retained_mode.py +297 -3
  27. {griptape_nodes-0.58.1.dist-info → griptape_nodes-0.59.1.dist-info}/METADATA +3 -2
  28. {griptape_nodes-0.58.1.dist-info → griptape_nodes-0.59.1.dist-info}/RECORD +30 -30
  29. {griptape_nodes-0.58.1.dist-info → griptape_nodes-0.59.1.dist-info}/WHEEL +1 -1
  30. {griptape_nodes-0.58.1.dist-info → griptape_nodes-0.59.1.dist-info}/entry_points.txt +0 -0
@@ -12,6 +12,7 @@ from inspect import getmodule, isclass
12
12
  from pathlib import Path
13
13
  from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, TypeVar, cast
14
14
 
15
+ import aiofiles
15
16
  import semver
16
17
  import tomlkit
17
18
  from rich.box import HEAVY_EDGE
@@ -503,7 +504,7 @@ class WorkflowManager:
503
504
  def should_squelch_workflow_altered(self) -> bool:
504
505
  return self._squelch_workflow_altered_count > 0
505
506
 
506
- def _ensure_workflow_context_established(self) -> None:
507
+ async def _ensure_workflow_context_established(self) -> None:
507
508
  """Ensure there's a current workflow and flow context after workflow execution."""
508
509
  context_manager = GriptapeNodes.ContextManager()
509
510
 
@@ -521,7 +522,7 @@ class WorkflowManager:
521
522
  )
522
523
 
523
524
  top_level_flow_request = GetTopLevelFlowRequest()
524
- top_level_flow_result = GriptapeNodes.handle_request(top_level_flow_request)
525
+ top_level_flow_result = await GriptapeNodes.ahandle_request(top_level_flow_request)
525
526
 
526
527
  if (
527
528
  isinstance(top_level_flow_result, GetTopLevelFlowResultSuccess)
@@ -539,7 +540,7 @@ class WorkflowManager:
539
540
  error_message = "Workflow execution completed but no current flow context could be established"
540
541
  raise RuntimeError(error_message)
541
542
 
542
- def run_workflow(self, relative_file_path: str) -> WorkflowExecutionResult:
543
+ async def run_workflow(self, relative_file_path: str) -> WorkflowExecutionResult:
543
544
  relative_file_path_obj = Path(relative_file_path)
544
545
  if relative_file_path_obj.is_absolute():
545
546
  complete_file_path = relative_file_path_obj
@@ -548,14 +549,14 @@ class WorkflowManager:
548
549
  try:
549
550
  # Libraries are now loaded only on app initialization and explicit reload requests
550
551
  # Now execute the workflow.
551
- with Path(complete_file_path).open(encoding="utf-8") as file:
552
- workflow_content = file.read()
552
+ async with aiofiles.open(Path(complete_file_path), encoding="utf-8") as file:
553
+ workflow_content = await file.read()
553
554
  exec(workflow_content) # noqa: S102
554
555
 
555
556
  # After workflow execution, ensure there's always a current context by pushing
556
557
  # the top-level flow if the context is empty. This fixes regressions where
557
558
  # with Workflow Schema version 0.6.0+ workflows expect context to be established.
558
- self._ensure_workflow_context_established()
559
+ await self._ensure_workflow_context_established()
559
560
 
560
561
  except Exception as e:
561
562
  return WorkflowManager.WorkflowExecutionResult(
@@ -567,7 +568,7 @@ class WorkflowManager:
567
568
  execution_details=f"Succeeded in running workflow on path '{complete_file_path}'.",
568
569
  )
569
570
 
570
- def on_run_workflow_from_scratch_request(self, request: RunWorkflowFromScratchRequest) -> ResultPayload:
571
+ async def on_run_workflow_from_scratch_request(self, request: RunWorkflowFromScratchRequest) -> ResultPayload:
571
572
  # Squelch any ResultPayloads that indicate the workflow was changed, because we are loading it into a blank slate.
572
573
  with WorkflowManager.WorkflowSquelchContext(self):
573
574
  # Check if file path exists
@@ -579,33 +580,35 @@ class WorkflowManager:
579
580
 
580
581
  # Start with a clean slate.
581
582
  clear_all_request = ClearAllObjectStateRequest(i_know_what_im_doing=True)
582
- clear_all_result = GriptapeNodes.handle_request(clear_all_request)
583
+ clear_all_result = await GriptapeNodes.ahandle_request(clear_all_request)
583
584
  if not clear_all_result.succeeded():
584
585
  details = f"Failed to clear the existing object state when trying to run '{complete_file_path}'."
585
586
  return RunWorkflowFromScratchResultFailure(result_details=details)
586
587
 
587
588
  # Run the file, goddamn it
588
- execution_result = self.run_workflow(relative_file_path=relative_file_path)
589
+ execution_result = await self.run_workflow(relative_file_path=relative_file_path)
589
590
  if execution_result.execution_successful:
590
591
  return RunWorkflowFromScratchResultSuccess(result_details=execution_result.execution_details)
591
592
 
592
593
  logger.error(execution_result.execution_details)
593
594
  return RunWorkflowFromScratchResultFailure(result_details=execution_result.execution_details)
594
595
 
595
- def on_run_workflow_with_current_state_request(self, request: RunWorkflowWithCurrentStateRequest) -> ResultPayload:
596
+ async def on_run_workflow_with_current_state_request(
597
+ self, request: RunWorkflowWithCurrentStateRequest
598
+ ) -> ResultPayload:
596
599
  relative_file_path = request.file_path
597
600
  complete_file_path = WorkflowRegistry.get_complete_file_path(relative_file_path=relative_file_path)
598
601
  if not Path(complete_file_path).is_file():
599
602
  details = f"Failed to find file. Path '{complete_file_path}' doesn't exist."
600
603
  return RunWorkflowWithCurrentStateResultFailure(result_details=details)
601
- execution_result = self.run_workflow(relative_file_path=relative_file_path)
604
+ execution_result = await self.run_workflow(relative_file_path=relative_file_path)
602
605
 
603
606
  if execution_result.execution_successful:
604
607
  return RunWorkflowWithCurrentStateResultSuccess(result_details=execution_result.execution_details)
605
608
  logger.error(execution_result.execution_details)
606
609
  return RunWorkflowWithCurrentStateResultFailure(result_details=execution_result.execution_details)
607
610
 
608
- def on_run_workflow_from_registry_request(self, request: RunWorkflowFromRegistryRequest) -> ResultPayload:
611
+ async def on_run_workflow_from_registry_request(self, request: RunWorkflowFromRegistryRequest) -> ResultPayload:
609
612
  # get workflow from registry
610
613
  try:
611
614
  workflow = WorkflowRegistry.get_workflow_by_name(request.workflow_name)
@@ -626,7 +629,7 @@ class WorkflowManager:
626
629
  if request.run_with_clean_slate:
627
630
  # Start with a clean slate.
628
631
  clear_all_request = ClearAllObjectStateRequest(i_know_what_im_doing=True)
629
- clear_all_result = GriptapeNodes.handle_request(clear_all_request)
632
+ clear_all_result = await GriptapeNodes.ahandle_request(clear_all_request)
630
633
  if not clear_all_result.succeeded():
631
634
  details = f"Failed to clear the existing object state when preparing to run workflow '{request.workflow_name}'."
632
635
  return RunWorkflowFromRegistryResultFailure(result_details=details)
@@ -634,7 +637,7 @@ class WorkflowManager:
634
637
  # Let's run under the assumption that this Workflow will become our Current Context; if we fail, it will revert.
635
638
  GriptapeNodes.ContextManager().push_workflow(request.workflow_name)
636
639
  # run file
637
- execution_result = self.run_workflow(relative_file_path=relative_file_path)
640
+ execution_result = await self.run_workflow(relative_file_path=relative_file_path)
638
641
 
639
642
  if not execution_result.execution_successful:
640
643
  result_messages = []
@@ -644,7 +647,7 @@ class WorkflowManager:
644
647
 
645
648
  # Attempt to clear everything out, as we modified the engine state getting here.
646
649
  clear_all_request = ClearAllObjectStateRequest(i_know_what_im_doing=True)
647
- clear_all_result = GriptapeNodes.handle_request(clear_all_request)
650
+ clear_all_result = await GriptapeNodes.ahandle_request(clear_all_request)
648
651
 
649
652
  # The clear-all above here wipes the ContextManager, so no need to do a pop_workflow().
650
653
  return RunWorkflowFromRegistryResultFailure(result_details=ResultDetails(*result_messages))
@@ -746,14 +749,16 @@ class WorkflowManager:
746
749
  result_details=ResultDetails(message=f"Successfully deleted workflow: {request.name}", level=logging.INFO)
747
750
  )
748
751
 
749
- def on_rename_workflow_request(self, request: RenameWorkflowRequest) -> ResultPayload:
750
- save_workflow_request = GriptapeNodes.handle_request(SaveWorkflowRequest(file_name=request.requested_name))
752
+ async def on_rename_workflow_request(self, request: RenameWorkflowRequest) -> ResultPayload:
753
+ save_workflow_request = await GriptapeNodes.ahandle_request(
754
+ SaveWorkflowRequest(file_name=request.requested_name)
755
+ )
751
756
 
752
757
  if isinstance(save_workflow_request, SaveWorkflowResultFailure):
753
758
  details = f"Attempted to rename workflow '{request.workflow_name}' to '{request.requested_name}'. Failed while attempting to save."
754
759
  return RenameWorkflowResultFailure(result_details=details)
755
760
 
756
- delete_workflow_result = GriptapeNodes.handle_request(DeleteWorkflowRequest(name=request.workflow_name))
761
+ delete_workflow_result = await GriptapeNodes.ahandle_request(DeleteWorkflowRequest(name=request.workflow_name))
757
762
  if isinstance(delete_workflow_result, DeleteWorkflowResultFailure):
758
763
  details = f"Attempted to rename workflow '{request.workflow_name}' to '{request.requested_name}'. Failed while attempting to remove the original file name from the registry."
759
764
  return RenameWorkflowResultFailure(result_details=details)
@@ -1156,7 +1161,7 @@ class WorkflowManager:
1156
1161
  success: bool
1157
1162
  error_details: str
1158
1163
 
1159
- def _write_workflow_file(self, file_path: Path, content: str, file_name: str) -> WriteWorkflowFileResult:
1164
+ async def _write_workflow_file(self, file_path: Path, content: str, file_name: str) -> WriteWorkflowFileResult:
1160
1165
  """Write workflow content to file with proper validation and error handling.
1161
1166
 
1162
1167
  Args:
@@ -1184,15 +1189,15 @@ class WorkflowManager:
1184
1189
 
1185
1190
  # Write the file content
1186
1191
  try:
1187
- with file_path.open("w", encoding="utf-8") as file:
1188
- file.write(content)
1192
+ async with aiofiles.open(file_path, "w", encoding="utf-8") as file:
1193
+ await file.write(content)
1189
1194
  except OSError as e:
1190
1195
  details = f"Attempted to save workflow '{file_name}'. Failed when writing file content: {e}"
1191
1196
  return self.WriteWorkflowFileResult(success=False, error_details=details)
1192
1197
 
1193
1198
  return self.WriteWorkflowFileResult(success=True, error_details="")
1194
1199
 
1195
- def on_save_workflow_request(self, request: SaveWorkflowRequest) -> ResultPayload:
1200
+ async def on_save_workflow_request(self, request: SaveWorkflowRequest) -> ResultPayload:
1196
1201
  # Determine save target (file path, name, metadata)
1197
1202
  context_manager = GriptapeNodes.ContextManager()
1198
1203
  current_workflow_name = (
@@ -1224,7 +1229,7 @@ class WorkflowManager:
1224
1229
 
1225
1230
  # Serialize the current workflow state
1226
1231
  top_level_flow_request = GetTopLevelFlowRequest()
1227
- top_level_flow_result = GriptapeNodes.handle_request(top_level_flow_request)
1232
+ top_level_flow_result = await GriptapeNodes.ahandle_request(top_level_flow_request)
1228
1233
  if not isinstance(top_level_flow_result, GetTopLevelFlowResultSuccess):
1229
1234
  details = f"Attempted to save workflow '{relative_file_path}'. Failed when requesting top level flow."
1230
1235
  return SaveWorkflowResultFailure(result_details=details)
@@ -1233,7 +1238,7 @@ class WorkflowManager:
1233
1238
  serialized_flow_request = SerializeFlowToCommandsRequest(
1234
1239
  flow_name=top_level_flow_name, include_create_flow_command=True
1235
1240
  )
1236
- serialized_flow_result = GriptapeNodes.handle_request(serialized_flow_request)
1241
+ serialized_flow_result = await GriptapeNodes.ahandle_request(serialized_flow_request)
1237
1242
  if not isinstance(serialized_flow_result, SerializeFlowToCommandsResultSuccess):
1238
1243
  details = f"Attempted to save workflow '{relative_file_path}'. Failed when serializing flow."
1239
1244
  return SaveWorkflowResultFailure(result_details=details)
@@ -1265,7 +1270,7 @@ class WorkflowManager:
1265
1270
  file_path=str(file_path),
1266
1271
  pickle_control_flow_result=pickle_control_flow_result,
1267
1272
  )
1268
- save_file_result = self.on_save_workflow_file_from_serialized_flow_request(save_file_request)
1273
+ save_file_result = await self.on_save_workflow_file_from_serialized_flow_request(save_file_request)
1269
1274
 
1270
1275
  if not isinstance(save_file_result, SaveWorkflowFileFromSerializedFlowResultSuccess):
1271
1276
  details = f"Attempted to save workflow '{relative_file_path}'. Failed during file generation: {save_file_result.result_details}"
@@ -1384,7 +1389,7 @@ class WorkflowManager:
1384
1389
  branched_from=branched_from,
1385
1390
  )
1386
1391
 
1387
- def on_save_workflow_file_from_serialized_flow_request(
1392
+ async def on_save_workflow_file_from_serialized_flow_request(
1388
1393
  self, request: SaveWorkflowFileFromSerializedFlowRequest
1389
1394
  ) -> ResultPayload:
1390
1395
  """Save a workflow file from serialized flow commands without registry overhead."""
@@ -1426,7 +1431,6 @@ class WorkflowManager:
1426
1431
  final_code_output = self._generate_workflow_file_content(
1427
1432
  serialized_flow_commands=request.serialized_flow_commands,
1428
1433
  workflow_metadata=workflow_metadata,
1429
- execution_flow_name=execution_flow_name,
1430
1434
  pickle_control_flow_result=request.pickle_control_flow_result,
1431
1435
  )
1432
1436
  except Exception as err:
@@ -1434,7 +1438,7 @@ class WorkflowManager:
1434
1438
  return SaveWorkflowFileFromSerializedFlowResultFailure(result_details=details)
1435
1439
 
1436
1440
  # Write the workflow file
1437
- write_result = self._write_workflow_file(file_path, final_code_output, request.file_name)
1441
+ write_result = await self._write_workflow_file(file_path, final_code_output, request.file_name)
1438
1442
  if not write_result.success:
1439
1443
  return SaveWorkflowFileFromSerializedFlowResultFailure(result_details=write_result.error_details)
1440
1444
 
@@ -1488,7 +1492,6 @@ class WorkflowManager:
1488
1492
  self,
1489
1493
  serialized_flow_commands: SerializedFlowCommands,
1490
1494
  workflow_metadata: WorkflowMetadata,
1491
- execution_flow_name: str,
1492
1495
  *,
1493
1496
  pickle_control_flow_result: bool = False,
1494
1497
  ) -> str:
@@ -1618,7 +1621,6 @@ class WorkflowManager:
1618
1621
 
1619
1622
  # Generate workflow execution code
1620
1623
  workflow_execution_code = self._generate_workflow_execution(
1621
- flow_name=execution_flow_name,
1622
1624
  import_recorder=import_recorder,
1623
1625
  workflow_metadata=workflow_metadata,
1624
1626
  pickle_control_flow_result=pickle_control_flow_result,
@@ -1689,7 +1691,6 @@ class WorkflowManager:
1689
1691
 
1690
1692
  def _generate_workflow_execution(
1691
1693
  self,
1692
- flow_name: str,
1693
1694
  import_recorder: ImportRecorder,
1694
1695
  workflow_metadata: WorkflowMetadata,
1695
1696
  *,
@@ -1805,7 +1806,6 @@ class WorkflowManager:
1805
1806
  ),
1806
1807
  args=[],
1807
1808
  keywords=[
1808
- ast.keyword(arg="workflow_name", value=ast.Constant(flow_name)),
1809
1809
  ast.keyword(arg="flow_input", value=ast.Name(id="input", ctx=ast.Load())),
1810
1810
  ast.keyword(
1811
1811
  arg="pickle_control_flow_result",
@@ -3269,6 +3269,7 @@ class WorkflowManager:
3269
3269
  "ui_options",
3270
3270
  "settable",
3271
3271
  "is_user_defined",
3272
+ "parent_container_name",
3272
3273
  ]
3273
3274
  minimal_dict = {key: param_dict[key] for key in fields_to_include if key in param_dict}
3274
3275
  minimal_dict["settable"] = bool(getattr(parameter, "settable", True))
@@ -3365,16 +3366,6 @@ class WorkflowManager:
3365
3366
  Returns:
3366
3367
  Parameter info dict if relevant for workflow shape, None if should be excluded
3367
3368
  """
3368
- # TODO (https://github.com/griptape-ai/griptape-nodes/issues/1090): This is a temporary solution until we know how to handle container types.
3369
- # Always exclude list types until container type handling is implemented
3370
- if parameter.type.startswith("list"):
3371
- logger.warning(
3372
- "Skipping list parameter '%s' of type '%s' in workflow shape - container types not yet supported",
3373
- parameter.name,
3374
- parameter.type,
3375
- )
3376
- return None
3377
-
3378
3369
  # Conditionally exclude control types
3379
3370
  if not include_control_params and parameter.type == ParameterTypeBuiltin.CONTROL_TYPE.value:
3380
3371
  return None
@@ -3546,7 +3537,7 @@ class WorkflowManager:
3546
3537
 
3547
3538
  return final_result
3548
3539
 
3549
- def on_import_workflow_as_referenced_sub_flow_request(
3540
+ async def on_import_workflow_as_referenced_sub_flow_request(
3550
3541
  self, request: ImportWorkflowAsReferencedSubFlowRequest
3551
3542
  ) -> ResultPayload:
3552
3543
  """Import a registered workflow as a new referenced sub flow in the current context."""
@@ -3565,7 +3556,7 @@ class WorkflowManager:
3565
3556
  flow_name = GriptapeNodes.ContextManager().get_current_flow().name
3566
3557
 
3567
3558
  # Execute the import
3568
- return self._execute_workflow_import(request, workflow, flow_name)
3559
+ return await self._execute_workflow_import(request, workflow, flow_name)
3569
3560
 
3570
3561
  def _validate_import_prerequisites(self, request: ImportWorkflowAsReferencedSubFlowRequest) -> ResultPayload | None:
3571
3562
  """Validate all prerequisites for import. Returns error result or None if valid."""
@@ -3609,7 +3600,7 @@ class WorkflowManager:
3609
3600
  """Get workflow by name from the registry."""
3610
3601
  return WorkflowRegistry.get_workflow_by_name(workflow_name)
3611
3602
 
3612
- def _execute_workflow_import(
3603
+ async def _execute_workflow_import(
3613
3604
  self, request: ImportWorkflowAsReferencedSubFlowRequest, workflow: Workflow, flow_name: str
3614
3605
  ) -> ResultPayload:
3615
3606
  """Execute the actual workflow import."""
@@ -3620,7 +3611,7 @@ class WorkflowManager:
3620
3611
  # Execute the workflow within the target flow context and referenced context
3621
3612
  with GriptapeNodes.ContextManager().flow(flow_name): # noqa: SIM117
3622
3613
  with self.ReferencedWorkflowContext(self, request.workflow_name):
3623
- workflow_result = self.run_workflow(workflow.file_path)
3614
+ workflow_result = await self.run_workflow(workflow.file_path)
3624
3615
 
3625
3616
  if not workflow_result.execution_successful:
3626
3617
  details = f"Attempted to import workflow '{request.workflow_name}' as referenced sub flow. Failed because workflow execution failed: {workflow_result.execution_details}"