griptape-nodes 0.37.1__py3-none-any.whl → 0.38.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 (38) hide show
  1. griptape_nodes/__init__.py +292 -132
  2. griptape_nodes/app/__init__.py +1 -6
  3. griptape_nodes/app/app.py +108 -76
  4. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +80 -5
  5. griptape_nodes/drivers/storage/local_storage_driver.py +5 -1
  6. griptape_nodes/exe_types/core_types.py +84 -3
  7. griptape_nodes/exe_types/node_types.py +260 -50
  8. griptape_nodes/machines/node_resolution.py +2 -14
  9. griptape_nodes/retained_mode/events/agent_events.py +7 -0
  10. griptape_nodes/retained_mode/events/base_events.py +16 -0
  11. griptape_nodes/retained_mode/events/library_events.py +26 -0
  12. griptape_nodes/retained_mode/events/parameter_events.py +31 -0
  13. griptape_nodes/retained_mode/griptape_nodes.py +32 -0
  14. griptape_nodes/retained_mode/managers/agent_manager.py +25 -12
  15. griptape_nodes/retained_mode/managers/config_manager.py +37 -4
  16. griptape_nodes/retained_mode/managers/event_manager.py +15 -0
  17. griptape_nodes/retained_mode/managers/flow_manager.py +64 -61
  18. griptape_nodes/retained_mode/managers/library_manager.py +215 -45
  19. griptape_nodes/retained_mode/managers/node_manager.py +344 -147
  20. griptape_nodes/retained_mode/managers/operation_manager.py +6 -0
  21. griptape_nodes/retained_mode/managers/os_manager.py +6 -1
  22. griptape_nodes/retained_mode/managers/secrets_manager.py +7 -2
  23. griptape_nodes/retained_mode/managers/settings.py +2 -11
  24. griptape_nodes/retained_mode/managers/static_files_manager.py +12 -3
  25. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +105 -0
  26. griptape_nodes/retained_mode/managers/workflow_manager.py +4 -4
  27. griptape_nodes/updater/__init__.py +14 -8
  28. griptape_nodes/version_compatibility/__init__.py +1 -0
  29. griptape_nodes/version_compatibility/versions/__init__.py +1 -0
  30. griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +1 -0
  31. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +77 -0
  32. {griptape_nodes-0.37.1.dist-info → griptape_nodes-0.38.0.dist-info}/METADATA +4 -1
  33. {griptape_nodes-0.37.1.dist-info → griptape_nodes-0.38.0.dist-info}/RECORD +36 -33
  34. griptape_nodes/app/app_websocket.py +0 -481
  35. griptape_nodes/app/nodes_api_socket_manager.py +0 -117
  36. {griptape_nodes-0.37.1.dist-info → griptape_nodes-0.38.0.dist-info}/WHEEL +0 -0
  37. {griptape_nodes-0.37.1.dist-info → griptape_nodes-0.38.0.dist-info}/entry_points.txt +0 -0
  38. {griptape_nodes-0.37.1.dist-info → griptape_nodes-0.38.0.dist-info}/licenses/LICENSE +0 -0
@@ -72,10 +72,14 @@ from griptape_nodes.retained_mode.events.library_events import (
72
72
  RegisterLibraryFromRequirementSpecifierRequest,
73
73
  RegisterLibraryFromRequirementSpecifierResultFailure,
74
74
  RegisterLibraryFromRequirementSpecifierResultSuccess,
75
+ ReloadAllLibrariesRequest,
76
+ ReloadAllLibrariesResultFailure,
77
+ ReloadAllLibrariesResultSuccess,
75
78
  UnloadLibraryFromRegistryRequest,
76
79
  UnloadLibraryFromRegistryResultFailure,
77
80
  UnloadLibraryFromRegistryResultSuccess,
78
81
  )
82
+ from griptape_nodes.retained_mode.events.object_events import ClearAllObjectStateRequest
79
83
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
80
84
  from griptape_nodes.retained_mode.managers.os_manager import OSManager
81
85
 
@@ -147,6 +151,7 @@ class LibraryManager:
147
151
  event_manager.assign_manager_to_request_type(
148
152
  UnloadLibraryFromRegistryRequest, self.unload_library_from_registry_request
149
153
  )
154
+ event_manager.assign_manager_to_request_type(ReloadAllLibrariesRequest, self.reload_all_libraries_request)
150
155
 
151
156
  event_manager.add_listener_to_app_event(
152
157
  AppInitializationComplete,
@@ -462,35 +467,61 @@ class LibraryManager:
462
467
  pip_install_flags = []
463
468
  pip_dependencies = library_data.metadata.dependencies.pip_dependencies
464
469
 
465
- # Grab the python executable from the virtual environment so that we can pip install there
466
- library_venv_python_path = self._get_library_venv_python_path(library_data.name)
467
- subprocess.run( # noqa: S603
468
- [
469
- sys.executable,
470
- "-m",
471
- "uv",
472
- "pip",
473
- "install",
474
- *pip_dependencies,
475
- *pip_install_flags,
476
- "--python",
477
- str(library_venv_python_path),
478
- ],
479
- check=True,
480
- text=True,
481
- )
470
+ # Determine venv path for dependency installation
471
+ venv_path = self._get_library_venv_path(library_data.name, file_path)
472
+
473
+ # Only install dependencies if conditions are met
474
+ try:
475
+ library_venv_python_path = self._init_library_venv(venv_path)
476
+ except RuntimeError as e:
477
+ self._library_file_path_to_info[file_path] = LibraryManager.LibraryInfo(
478
+ library_path=file_path,
479
+ library_name=library_data.name,
480
+ library_version=library_version,
481
+ status=LibraryManager.LibraryStatus.UNUSABLE,
482
+ problems=[str(e)],
483
+ )
484
+ details = f"Attempted to load Library JSON file from '{json_path}'. Failed when creating the virtual environment: {e}."
485
+ logger.error(details)
486
+ return RegisterLibraryFromFileResultFailure()
487
+ if self._can_write_to_venv_location(library_venv_python_path):
488
+ # Grab the python executable from the virtual environment so that we can pip install there
489
+ logger.info(
490
+ "Installing dependencies for library '%s' with pip in venv at %s", library_data.name, venv_path
491
+ )
492
+ subprocess.run( # noqa: S603
493
+ [
494
+ sys.executable,
495
+ "-m",
496
+ "uv",
497
+ "pip",
498
+ "install",
499
+ *pip_dependencies,
500
+ *pip_install_flags,
501
+ "--python",
502
+ str(library_venv_python_path),
503
+ ],
504
+ check=True,
505
+ capture_output=True,
506
+ text=True,
507
+ )
508
+ else:
509
+ logger.debug(
510
+ "Skipping dependency installation for library '%s' - venv location at %s is not writable",
511
+ library_data.name,
512
+ venv_path,
513
+ )
482
514
  except subprocess.CalledProcessError as e:
483
515
  # Failed to create the library
516
+ error_details = f"return code={e.returncode}, stdout={e.stdout}, stderr={e.stderr}"
484
517
  self._library_file_path_to_info[file_path] = LibraryManager.LibraryInfo(
485
518
  library_path=file_path,
486
519
  library_name=library_data.name,
487
520
  library_version=library_version,
488
521
  status=LibraryManager.LibraryStatus.UNUSABLE,
489
- problems=[f"Failed to create the library: {e}"],
490
- )
491
- details = (
492
- f"Attempted to load Library JSON file from '{json_path}'. Failed when installing dependencies: {e}."
522
+ problems=[f"Dependency installation failed: {error_details}"],
493
523
  )
524
+ details = f"Attempted to load Library JSON file from '{json_path}'. Failed when installing dependencies: {error_details}"
494
525
  logger.error(details)
495
526
  return RegisterLibraryFromFileResultFailure()
496
527
 
@@ -564,21 +595,39 @@ class LibraryManager:
564
595
  ) -> ResultPayload:
565
596
  package_name = Requirement(request.requirement_specifier).name
566
597
  try:
567
- library_python_venv_path = self._get_library_venv_python_path(package_name)
568
- subprocess.run( # noqa: S603
569
- [
570
- uv.find_uv_bin(),
571
- "pip",
572
- "install",
573
- request.requirement_specifier,
574
- "--python",
575
- library_python_venv_path,
576
- ],
577
- check=True,
578
- text=True,
579
- )
598
+ # Determine venv path for dependency installation
599
+ venv_path = self._get_library_venv_path(package_name, None)
600
+
601
+ # Only install dependencies if conditions are met
602
+ try:
603
+ library_python_venv_path = self._init_library_venv(venv_path)
604
+ except RuntimeError as e:
605
+ details = f"Attempted to install library '{request.requirement_specifier}'. Failed when creating the virtual environment: {e}"
606
+ logger.error(details)
607
+ return RegisterLibraryFromRequirementSpecifierResultFailure()
608
+ if self._can_write_to_venv_location(library_python_venv_path):
609
+ logger.info("Installing dependency '%s' with pip in venv at %s", package_name, venv_path)
610
+ subprocess.run( # noqa: S603
611
+ [
612
+ uv.find_uv_bin(),
613
+ "pip",
614
+ "install",
615
+ request.requirement_specifier,
616
+ "--python",
617
+ library_python_venv_path,
618
+ ],
619
+ check=True,
620
+ capture_output=True,
621
+ text=True,
622
+ )
623
+ else:
624
+ logger.debug(
625
+ "Skipping dependency installation for package '%s' - venv location at %s is not writable",
626
+ package_name,
627
+ venv_path,
628
+ )
580
629
  except subprocess.CalledProcessError as e:
581
- details = f"Attempted to install library '{request.requirement_specifier}'. Failed due to {e}"
630
+ details = f"Attempted to install library '{request.requirement_specifier}'. Failed: return code={e.returncode}, stdout={e.stdout}, stderr={e.stderr}"
582
631
  logger.error(details)
583
632
  return RegisterLibraryFromRequirementSpecifierResultFailure()
584
633
 
@@ -592,20 +641,37 @@ class LibraryManager:
592
641
 
593
642
  return RegisterLibraryFromRequirementSpecifierResultSuccess(library_name=request.requirement_specifier)
594
643
 
595
- def _get_library_venv_python_path(self, library_name: str) -> Path:
644
+ def _init_library_venv(self, library_venv_path: Path) -> Path:
645
+ """Initialize a virtual environment for the library.
646
+
647
+ If the virtual environment already exists, it will not be recreated.
648
+
649
+ Args:
650
+ library_venv_path: Path to the virtual environment directory
651
+
652
+ Returns:
653
+ Path to the Python executable in the virtual environment
654
+
655
+ Raises:
656
+ RuntimeError: If the virtual environment cannot be created.
657
+ """
596
658
  # Create a virtual environment for the library
597
659
  python_version = platform.python_version()
598
- library_venv_path = (
599
- xdg_data_home() / "griptape_nodes" / "venvs" / python_version / library_name.replace(" ", "_").strip()
600
- )
660
+
601
661
  if library_venv_path.exists():
602
662
  logger.debug("Virtual environment already exists at %s", library_venv_path)
603
663
  else:
604
- subprocess.run( # noqa: S603
605
- [sys.executable, "-m", "uv", "venv", str(library_venv_path), "--python", python_version],
606
- check=True,
607
- text=True,
608
- )
664
+ try:
665
+ logger.info("Creating virtual environment at %s with Python %s", library_venv_path, python_version)
666
+ subprocess.run( # noqa: S603
667
+ [sys.executable, "-m", "uv", "venv", str(library_venv_path), "--python", python_version],
668
+ check=True,
669
+ capture_output=True,
670
+ text=True,
671
+ )
672
+ except subprocess.CalledProcessError as e:
673
+ msg = f"Failed to create virtual environment at {library_venv_path} with Python {python_version}: return code={e.returncode}, stdout={e.stdout}, stderr={e.stderr}"
674
+ raise RuntimeError(msg) from e
609
675
  logger.debug("Created virtual environment at %s", library_venv_path)
610
676
 
611
677
  # Grab the python executable from the virtual environment so that we can pip install there
@@ -627,6 +693,57 @@ class LibraryManager:
627
693
 
628
694
  return library_venv_python_path
629
695
 
696
+ def _get_library_venv_path(self, library_name: str, library_file_path: str | None = None) -> Path:
697
+ """Get the path to the virtual environment directory for a library.
698
+
699
+ Args:
700
+ library_name: Name of the library
701
+ library_file_path: Optional path to the library JSON file
702
+
703
+ Returns:
704
+ Path to the virtual environment directory
705
+ """
706
+ clean_library_name = library_name.replace(" ", "_").strip()
707
+
708
+ if library_file_path is not None:
709
+ # Create venv relative to the library.json file
710
+ library_dir = Path(library_file_path).parent.absolute()
711
+ return library_dir / ".venv"
712
+
713
+ # Create venv relative to the xdg data home
714
+ return xdg_data_home() / "griptape_nodes" / "libraries" / clean_library_name / ".venv"
715
+
716
+ def _can_write_to_venv_location(self, venv_python_path: Path) -> bool:
717
+ """Check if we can write to the venv location (either create it or modify existing).
718
+
719
+ Args:
720
+ venv_python_path: Path to the python executable in the virtual environment
721
+
722
+ Returns:
723
+ True if we can write to the location, False otherwise
724
+ """
725
+ # On Windows, permission checks are hard. Assume we can write
726
+ if OSManager.is_windows():
727
+ return True
728
+
729
+ venv_path = venv_python_path.parent.parent
730
+
731
+ # If venv doesn't exist, check if parent directory is writable
732
+ if not venv_path.exists():
733
+ parent_dir = venv_path.parent
734
+ try:
735
+ return parent_dir.exists() and os.access(parent_dir, os.W_OK)
736
+ except (OSError, AttributeError) as e:
737
+ logger.debug("Could not check parent directory permissions for %s: %s", parent_dir, e)
738
+ return False
739
+
740
+ # If venv exists, check if we can write to it
741
+ try:
742
+ return os.access(venv_path, os.W_OK)
743
+ except (OSError, AttributeError) as e:
744
+ logger.debug("Could not check venv write permissions for %s: %s", venv_path, e)
745
+ return False
746
+
630
747
  def unload_library_from_registry_request(self, request: UnloadLibraryFromRegistryRequest) -> ResultPayload:
631
748
  try:
632
749
  LibraryRegistry.unregister_library(library_name=request.library_name)
@@ -915,7 +1032,7 @@ class LibraryManager:
915
1032
  # Go tell the Workflow Manager that it's turn is now.
916
1033
  GriptapeNodes.WorkflowManager().on_libraries_initialization_complete()
917
1034
 
918
- def _attempt_load_nodes_from_library( # noqa: PLR0913
1035
+ def _attempt_load_nodes_from_library( # noqa: PLR0913, C901
919
1036
  self,
920
1037
  library_data: LibrarySchema,
921
1038
  library: Library,
@@ -925,6 +1042,25 @@ class LibraryManager:
925
1042
  problems: list[str],
926
1043
  ) -> LibraryManager.LibraryInfo:
927
1044
  any_nodes_loaded_successfully = False
1045
+
1046
+ # Check for version-based compatibility issues and add to problems
1047
+ version_issues = GriptapeNodes.VersionCompatibilityManager().check_library_version_compatibility(library_data)
1048
+ has_disqualifying_issues = False
1049
+ for issue in version_issues:
1050
+ problems.append(issue.message)
1051
+ if issue.severity == LibraryManager.LibraryStatus.UNUSABLE:
1052
+ has_disqualifying_issues = True
1053
+
1054
+ # Early exit if any version issues are disqualifying
1055
+ if has_disqualifying_issues:
1056
+ return LibraryManager.LibraryInfo(
1057
+ library_path=library_file_path,
1058
+ library_name=library_data.name,
1059
+ library_version=library_version,
1060
+ status=LibraryManager.LibraryStatus.UNUSABLE,
1061
+ problems=problems,
1062
+ )
1063
+
928
1064
  # Process each node in the metadata
929
1065
  for node_definition in library_data.nodes:
930
1066
  # Resolve relative path to absolute path
@@ -1142,3 +1278,37 @@ class LibraryManager:
1142
1278
  library for library in libraries_to_register_category if library.lower() not in paths_to_remove
1143
1279
  ]
1144
1280
  config_mgr.set_config_value(config_category, libraries_to_register_category)
1281
+
1282
+ def reload_all_libraries_request(self, request: ReloadAllLibrariesRequest) -> ResultPayload: # noqa: ARG002
1283
+ # Start with a clean slate.
1284
+ clear_all_request = ClearAllObjectStateRequest(i_know_what_im_doing=True)
1285
+ clear_all_result = GriptapeNodes.handle_request(clear_all_request)
1286
+ if not clear_all_result.succeeded():
1287
+ details = "Failed to clear the existing object state when preparing to reload all libraries."
1288
+ logger.error(details)
1289
+ return ReloadAllLibrariesResultFailure()
1290
+
1291
+ # Unload all libraries now.
1292
+ all_libraries_request = ListRegisteredLibrariesRequest()
1293
+ all_libraries_result = GriptapeNodes.handle_request(all_libraries_request)
1294
+ if not isinstance(all_libraries_result, ListRegisteredLibrariesResultSuccess):
1295
+ details = "When preparing to reload all libraries, failed to get registered libraries."
1296
+ logger.error(details)
1297
+ return ReloadAllLibrariesResultFailure()
1298
+
1299
+ for library_name in all_libraries_result.libraries:
1300
+ unload_library_request = UnloadLibraryFromRegistryRequest(library_name=library_name)
1301
+ unload_library_result = GriptapeNodes.handle_request(unload_library_request)
1302
+ if not unload_library_result.succeeded():
1303
+ details = f"When preparing to reload all libraries, failed to unload library '{library_name}'."
1304
+ logger.error(details)
1305
+ return ReloadAllLibrariesResultFailure()
1306
+
1307
+ # Load (or reload, which should trigger a hot reload) all libraries
1308
+ self.load_all_libraries_from_config()
1309
+
1310
+ details = (
1311
+ "Successfully reloaded all libraries. All object state was cleared and previous libraries were unloaded."
1312
+ )
1313
+ logger.info(details)
1314
+ return ReloadAllLibrariesResultSuccess()