griptape-nodes 0.58.0__py3-none-any.whl → 0.59.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.
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 +216 -40
  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.0.dist-info → griptape_nodes-0.59.0.dist-info}/METADATA +3 -2
  28. {griptape_nodes-0.58.0.dist-info → griptape_nodes-0.59.0.dist-info}/RECORD +30 -30
  29. {griptape_nodes-0.58.0.dist-info → griptape_nodes-0.59.0.dist-info}/WHEEL +1 -1
  30. {griptape_nodes-0.58.0.dist-info → griptape_nodes-0.59.0.dist-info}/entry_points.txt +0 -0
@@ -169,6 +169,8 @@ class LibraryManager:
169
169
  self._library_event_handler_mappings: dict[type[Payload], dict[str, LibraryManager.RegisteredEventHandler]] = {}
170
170
  # LibraryDirectory owns the FSMs and manages library lifecycle
171
171
  self._library_directory = LibraryDirectory()
172
+ # Lock for synchronizing sys.path modifications during parallel library installation
173
+ self._sys_path_lock = asyncio.Lock()
172
174
 
173
175
  event_manager.assign_manager_to_request_type(
174
176
  ListRegisteredLibrariesRequest, self.on_list_registered_libraries_request
@@ -724,7 +726,8 @@ class LibraryManager:
724
726
  # Get the directory containing the JSON file to resolve relative paths
725
727
  base_dir = json_path.parent.absolute()
726
728
  # Add the directory to the Python path to allow for relative imports
727
- sys.path.insert(0, str(base_dir))
729
+ async with self._sys_path_lock:
730
+ sys.path.insert(0, str(base_dir))
728
731
 
729
732
  # Load the advanced library module if specified
730
733
  advanced_library_instance = None
@@ -1057,7 +1060,8 @@ class LibraryManager:
1057
1060
  )
1058
1061
  )
1059
1062
  )
1060
- sys.path.insert(0, site_packages)
1063
+ async with self._sys_path_lock:
1064
+ sys.path.insert(0, site_packages)
1061
1065
 
1062
1066
  return library_venv_python_path
1063
1067
 
@@ -1499,6 +1503,27 @@ class LibraryManager:
1499
1503
 
1500
1504
  return node_class
1501
1505
 
1506
+ async def _register_single_library(self, library_result: LoadLibraryMetadataFromFileResultSuccess) -> None:
1507
+ """Register a single library (sandbox or config-based) and handle errors.
1508
+
1509
+ Args:
1510
+ library_result: The metadata result for the library to register
1511
+ """
1512
+ try:
1513
+ if library_result.library_schema.name == LibraryManager.SANDBOX_LIBRARY_NAME:
1514
+ await self._attempt_generate_sandbox_library_from_schema(
1515
+ library_schema=library_result.library_schema, sandbox_directory=library_result.file_path
1516
+ )
1517
+ else:
1518
+ register_request = RegisterLibraryFromFileRequest(
1519
+ file_path=library_result.file_path, load_as_default_library=False
1520
+ )
1521
+ register_result = await self.register_library_from_file_request(register_request)
1522
+ if isinstance(register_result, RegisterLibraryFromFileResultFailure):
1523
+ logger.warning("Failed to register library from %s", library_result.file_path)
1524
+ except Exception as e:
1525
+ logger.warning("Failed to register library from %s with exception: %s", library_result.file_path, e)
1526
+
1502
1527
  async def load_all_libraries_from_config(self) -> None:
1503
1528
  # Load metadata for all libraries to determine which ones can be safely loaded
1504
1529
  metadata_request = LoadMetadataForAllLibrariesRequest()
@@ -1518,23 +1543,10 @@ class LibraryManager:
1518
1543
  problems=failed_library.problems,
1519
1544
  )
1520
1545
 
1521
- # Use metadata results to selectively load libraries
1522
- for library_result in metadata_result.successful_libraries:
1523
- if library_result.library_schema.name == LibraryManager.SANDBOX_LIBRARY_NAME:
1524
- # Handle sandbox library - use the schema we already have
1525
- await self._attempt_generate_sandbox_library_from_schema(
1526
- library_schema=library_result.library_schema, sandbox_directory=library_result.file_path
1527
- )
1528
- else:
1529
- # Handle config-based library - register it directly using the file path
1530
- register_request = RegisterLibraryFromFileRequest(
1531
- file_path=library_result.file_path, load_as_default_library=False
1532
- )
1533
- register_result = await self.register_library_from_file_request(register_request)
1534
- if isinstance(register_result, RegisterLibraryFromFileResultFailure):
1535
- # Registration failed - the failure info is already recorded in _library_file_path_to_info
1536
- # by register_library_from_file_request, so we just log it here for visibility
1537
- logger.warning("Failed to register library from %s", library_result.file_path)
1546
+ # Use task group for parallel library loading
1547
+ async with asyncio.TaskGroup() as tg:
1548
+ for library_result in metadata_result.successful_libraries:
1549
+ tg.create_task(self._register_single_library(library_result))
1538
1550
 
1539
1551
  # Print 'em all pretty
1540
1552
  self.print_library_load_status()
@@ -2004,7 +2016,7 @@ class LibraryManager:
2004
2016
  async def reload_all_libraries_request(self, request: ReloadAllLibrariesRequest) -> ResultPayload: # noqa: ARG002
2005
2017
  # Start with a clean slate.
2006
2018
  clear_all_request = ClearAllObjectStateRequest(i_know_what_im_doing=True)
2007
- clear_all_result = GriptapeNodes.handle_request(clear_all_request)
2019
+ clear_all_result = await GriptapeNodes.ahandle_request(clear_all_request)
2008
2020
  if not clear_all_result.succeeded():
2009
2021
  details = "Failed to clear the existing object state when preparing to reload all libraries."
2010
2022
  logger.error(details)
@@ -121,6 +121,7 @@ class ModelDownloadTracker(tqdm):
121
121
  def close(self) -> None:
122
122
  """Override close to log download completion."""
123
123
  super().close()
124
+ self._update_status_file() # Write final state to status file
124
125
  logger.debug(
125
126
  "ModelDownloadTracker close - model_id: %s, self.n: %s, total: %s", self.model_id, self.n, self.total
126
127
  )
@@ -190,13 +191,21 @@ class ModelDownloadTracker(tqdm):
190
191
  progress_percent,
191
192
  )
192
193
 
193
- data.update(
194
- {
195
- "downloaded_files": self.n,
196
- "progress_percent": progress_percent,
197
- "updated_at": current_time,
198
- }
199
- )
194
+ # Check if download is complete
195
+ is_complete = self.total > 0 and self.n >= self.total
196
+
197
+ update_data = {
198
+ "downloaded_files": self.n,
199
+ "progress_percent": progress_percent,
200
+ "updated_at": current_time,
201
+ }
202
+
203
+ # Update status to completed if all files are downloaded
204
+ if is_complete:
205
+ update_data["status"] = "completed"
206
+ update_data["completed_at"] = current_time
207
+
208
+ data.update(update_data)
200
209
 
201
210
  with status_file.open("w") as f:
202
211
  json.dump(data, f, indent=2)
@@ -426,9 +435,11 @@ class ModelManager:
426
435
  # Store process for cancellation
427
436
  self._download_processes[model_id] = process
428
437
 
429
- stdout, _ = await process.communicate()
438
+ stdout, stderr = await process.communicate()
430
439
 
431
440
  if process.returncode == 0:
441
+ logger.debug(stdout.decode().strip())
442
+ logger.debug(stderr.decode().strip())
432
443
  logger.info("Successfully downloaded model '%s'", model_id)
433
444
  else:
434
445
  raise ValueError(stdout.decode().strip())