griptape-nodes 0.43.1__py3-none-any.whl → 0.44.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 (131) hide show
  1. griptape_nodes/__init__.py +41 -51
  2. griptape_nodes/app/.python-version +0 -0
  3. griptape_nodes/app/__init__.py +0 -0
  4. griptape_nodes/app/api.py +35 -6
  5. griptape_nodes/app/app.py +0 -0
  6. griptape_nodes/app/watch.py +0 -0
  7. griptape_nodes/bootstrap/__init__.py +0 -0
  8. griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
  9. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +7 -1
  10. griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +90 -0
  11. griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +7 -1
  12. griptape_nodes/drivers/__init__.py +0 -0
  13. griptape_nodes/drivers/storage/__init__.py +0 -0
  14. griptape_nodes/drivers/storage/base_storage_driver.py +53 -0
  15. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +49 -2
  16. griptape_nodes/drivers/storage/local_storage_driver.py +37 -0
  17. griptape_nodes/drivers/storage/storage_backend.py +0 -0
  18. griptape_nodes/exe_types/__init__.py +0 -0
  19. griptape_nodes/exe_types/connections.py +0 -0
  20. griptape_nodes/exe_types/core_types.py +113 -8
  21. griptape_nodes/exe_types/flow.py +0 -0
  22. griptape_nodes/exe_types/node_types.py +1 -0
  23. griptape_nodes/exe_types/type_validator.py +0 -0
  24. griptape_nodes/machines/__init__.py +0 -0
  25. griptape_nodes/machines/control_flow.py +5 -4
  26. griptape_nodes/machines/fsm.py +0 -0
  27. griptape_nodes/machines/node_resolution.py +110 -74
  28. griptape_nodes/mcp_server/__init__.py +0 -0
  29. griptape_nodes/mcp_server/server.py +16 -8
  30. griptape_nodes/mcp_server/ws_request_manager.py +0 -0
  31. griptape_nodes/node_library/__init__.py +0 -0
  32. griptape_nodes/node_library/advanced_node_library.py +0 -0
  33. griptape_nodes/node_library/library_registry.py +0 -0
  34. griptape_nodes/node_library/workflow_registry.py +0 -0
  35. griptape_nodes/py.typed +0 -0
  36. griptape_nodes/retained_mode/__init__.py +0 -0
  37. griptape_nodes/retained_mode/events/__init__.py +0 -0
  38. griptape_nodes/retained_mode/events/agent_events.py +0 -0
  39. griptape_nodes/retained_mode/events/app_events.py +0 -6
  40. griptape_nodes/retained_mode/events/arbitrary_python_events.py +0 -0
  41. griptape_nodes/retained_mode/events/base_events.py +6 -7
  42. griptape_nodes/retained_mode/events/config_events.py +0 -0
  43. griptape_nodes/retained_mode/events/connection_events.py +0 -0
  44. griptape_nodes/retained_mode/events/context_events.py +0 -0
  45. griptape_nodes/retained_mode/events/execution_events.py +0 -0
  46. griptape_nodes/retained_mode/events/flow_events.py +2 -1
  47. griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
  48. griptape_nodes/retained_mode/events/library_events.py +0 -0
  49. griptape_nodes/retained_mode/events/logger_events.py +0 -0
  50. griptape_nodes/retained_mode/events/node_events.py +36 -0
  51. griptape_nodes/retained_mode/events/object_events.py +0 -0
  52. griptape_nodes/retained_mode/events/os_events.py +98 -6
  53. griptape_nodes/retained_mode/events/parameter_events.py +0 -0
  54. griptape_nodes/retained_mode/events/payload_registry.py +0 -0
  55. griptape_nodes/retained_mode/events/secrets_events.py +0 -0
  56. griptape_nodes/retained_mode/events/static_file_events.py +0 -0
  57. griptape_nodes/retained_mode/events/validation_events.py +0 -0
  58. griptape_nodes/retained_mode/events/workflow_events.py +0 -0
  59. griptape_nodes/retained_mode/griptape_nodes.py +1 -4
  60. griptape_nodes/retained_mode/managers/__init__.py +0 -0
  61. griptape_nodes/retained_mode/managers/agent_manager.py +0 -0
  62. griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
  63. griptape_nodes/retained_mode/managers/config_manager.py +1 -1
  64. griptape_nodes/retained_mode/managers/context_manager.py +0 -0
  65. griptape_nodes/retained_mode/managers/engine_identity_manager.py +0 -0
  66. griptape_nodes/retained_mode/managers/event_manager.py +0 -0
  67. griptape_nodes/retained_mode/managers/flow_manager.py +6 -0
  68. griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +0 -0
  69. griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +0 -0
  70. griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +0 -0
  71. griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +0 -0
  72. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +0 -0
  73. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +0 -0
  74. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +0 -0
  75. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +0 -0
  76. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +0 -0
  77. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +0 -0
  78. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +0 -0
  79. griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +0 -0
  80. griptape_nodes/retained_mode/managers/library_manager.py +2 -8
  81. griptape_nodes/retained_mode/managers/node_manager.py +76 -5
  82. griptape_nodes/retained_mode/managers/object_manager.py +0 -0
  83. griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
  84. griptape_nodes/retained_mode/managers/os_manager.py +133 -8
  85. griptape_nodes/retained_mode/managers/secrets_manager.py +0 -0
  86. griptape_nodes/retained_mode/managers/session_manager.py +0 -0
  87. griptape_nodes/retained_mode/managers/settings.py +0 -0
  88. griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
  89. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +0 -0
  90. griptape_nodes/retained_mode/managers/workflow_manager.py +54 -5
  91. griptape_nodes/retained_mode/retained_mode.py +0 -0
  92. griptape_nodes/retained_mode/utils/__init__.py +0 -0
  93. griptape_nodes/retained_mode/utils/engine_identity.py +0 -0
  94. griptape_nodes/retained_mode/utils/name_generator.py +0 -0
  95. griptape_nodes/traits/__init__.py +0 -0
  96. griptape_nodes/traits/add_param_button.py +0 -0
  97. griptape_nodes/traits/button.py +0 -0
  98. griptape_nodes/traits/clamp.py +0 -0
  99. griptape_nodes/traits/compare.py +0 -0
  100. griptape_nodes/traits/compare_images.py +0 -0
  101. griptape_nodes/traits/file_system_picker.py +18 -0
  102. griptape_nodes/traits/minmax.py +0 -0
  103. griptape_nodes/traits/options.py +0 -0
  104. griptape_nodes/traits/slider.py +0 -0
  105. griptape_nodes/traits/trait_registry.py +0 -0
  106. griptape_nodes/traits/traits.json +0 -0
  107. griptape_nodes/updater/__init__.py +0 -0
  108. griptape_nodes/updater/__main__.py +0 -0
  109. griptape_nodes/utils/__init__.py +0 -0
  110. griptape_nodes/utils/dict_utils.py +0 -0
  111. griptape_nodes/utils/image_preview.py +0 -0
  112. griptape_nodes/utils/metaclasses.py +0 -0
  113. griptape_nodes/utils/version_utils.py +51 -0
  114. griptape_nodes/version_compatibility/__init__.py +0 -0
  115. griptape_nodes/version_compatibility/versions/__init__.py +0 -0
  116. griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
  117. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +0 -0
  118. {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.44.0.dist-info}/METADATA +1 -1
  119. {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.44.0.dist-info}/RECORD +31 -39
  120. {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.44.0.dist-info}/WHEEL +1 -1
  121. griptape_nodes/bootstrap/bootstrap_script.py +0 -54
  122. griptape_nodes/bootstrap/post_build_install_script.sh +0 -3
  123. griptape_nodes/bootstrap/pre_build_install_script.sh +0 -4
  124. griptape_nodes/bootstrap/register_libraries_script.py +0 -32
  125. griptape_nodes/bootstrap/structure_config.yaml +0 -15
  126. griptape_nodes/bootstrap/workflow_runners/__init__.py +0 -1
  127. griptape_nodes/bootstrap/workflow_runners/bootstrap_workflow_runner.py +0 -28
  128. griptape_nodes/bootstrap/workflow_runners/local_workflow_runner.py +0 -237
  129. griptape_nodes/bootstrap/workflow_runners/subprocess_workflow_runner.py +0 -62
  130. griptape_nodes/bootstrap/workflow_runners/workflow_runner.py +0 -11
  131. {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.44.0.dist-info}/entry_points.txt +0 -0
@@ -13,6 +13,9 @@ from rich.console import Console
13
13
 
14
14
  from griptape_nodes.retained_mode.events.base_events import ResultPayload
15
15
  from griptape_nodes.retained_mode.events.os_events import (
16
+ CreateFileRequest,
17
+ CreateFileResultFailure,
18
+ CreateFileResultSuccess,
16
19
  FileSystemEntry,
17
20
  ListDirectoryRequest,
18
21
  ListDirectoryResultFailure,
@@ -23,6 +26,9 @@ from griptape_nodes.retained_mode.events.os_events import (
23
26
  ReadFileRequest,
24
27
  ReadFileResultFailure,
25
28
  ReadFileResultSuccess,
29
+ RenameFileRequest,
30
+ RenameFileResultFailure,
31
+ RenameFileResultSuccess,
26
32
  )
27
33
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes, logger
28
34
  from griptape_nodes.retained_mode.managers.event_manager import EventManager
@@ -59,6 +65,14 @@ class OSManager:
59
65
  request_type=ReadFileRequest, callback=self.on_read_file_request
60
66
  )
61
67
 
68
+ event_manager.assign_manager_to_request_type(
69
+ request_type=CreateFileRequest, callback=self.on_create_file_request
70
+ )
71
+
72
+ event_manager.assign_manager_to_request_type(
73
+ request_type=RenameFileRequest, callback=self.on_rename_file_request
74
+ )
75
+
62
76
  def _get_workspace_path(self) -> Path:
63
77
  """Get the workspace path from config."""
64
78
  return GriptapeNodes.ConfigManager().workspace_path
@@ -235,7 +249,7 @@ class OSManager:
235
249
  logger.error(msg)
236
250
  return OpenAssociatedFileResultFailure()
237
251
 
238
- # Sanitize and validate the file path
252
+ # Sanitize and validate the path (file or directory)
239
253
  try:
240
254
  # Resolve the path (no workspace fallback for open requests)
241
255
  path = self._resolve_file_path(file_path_str, workspace_only=False)
@@ -244,12 +258,12 @@ class OSManager:
244
258
  logger.info(details)
245
259
  return OpenAssociatedFileResultFailure()
246
260
 
247
- if not path.exists() or not path.is_file():
248
- details = f"File does not exist: '{path}'"
261
+ if not path.exists():
262
+ details = f"Path does not exist: '{path}'"
249
263
  logger.info(details)
250
264
  return OpenAssociatedFileResultFailure()
251
265
 
252
- logger.info("Attempting to open: %s on platform: %s", path, sys.platform)
266
+ logger.info("Attempting to open path: %s on platform: %s", path, sys.platform)
253
267
 
254
268
  try:
255
269
  platform_name = sys.platform
@@ -257,7 +271,7 @@ class OSManager:
257
271
  # Linter complains but this is the recommended way on Windows
258
272
  # We can ignore this warning as we've validated the path
259
273
  os.startfile(str(path)) # noqa: S606 # pyright: ignore[reportAttributeAccessIssue]
260
- logger.info("Started file on Windows: %s", path)
274
+ logger.info("Opened path on Windows: %s", path)
261
275
  elif self.is_mac():
262
276
  # On macOS, open should be in a standard location
263
277
  subprocess.run( # noqa: S603
@@ -266,7 +280,7 @@ class OSManager:
266
280
  capture_output=True,
267
281
  text=True,
268
282
  )
269
- logger.info("Started file on macOS: %s", path)
283
+ logger.info("Opened path on macOS: %s", path)
270
284
  elif self.is_linux():
271
285
  # Use full path to xdg-open to satisfy linter
272
286
  # Common locations for xdg-open:
@@ -283,7 +297,7 @@ class OSManager:
283
297
  capture_output=True,
284
298
  text=True,
285
299
  )
286
- logger.info("Started file on Linux: %s", path)
300
+ logger.info("Opened path on Linux: %s", path)
287
301
  else:
288
302
  details = f"Unsupported platform: '{platform_name}'"
289
303
  logger.info(details)
@@ -299,9 +313,24 @@ class OSManager:
299
313
  )
300
314
  return OpenAssociatedFileResultFailure()
301
315
  except Exception as e:
302
- logger.error("Exception occurred when trying to open file: %s", type(e).__name__)
316
+ logger.error("Exception occurred when trying to open path: %s", type(e).__name__)
303
317
  return OpenAssociatedFileResultFailure()
304
318
 
319
+ def _detect_mime_type(self, file_path: Path) -> str | None:
320
+ """Detect MIME type for a file. Returns None for directories or if detection fails."""
321
+ if file_path.is_dir():
322
+ return None
323
+
324
+ try:
325
+ mime_type, _ = mimetypes.guess_type(str(file_path), strict=True)
326
+ if mime_type is None:
327
+ mime_type = "text/plain"
328
+ return mime_type # noqa: TRY300
329
+ except Exception as e:
330
+ msg = f"MIME type detection failed for {file_path}: {e}"
331
+ logger.warning(msg)
332
+ return "text/plain"
333
+
305
334
  def on_list_directory_request(self, request: ListDirectoryRequest) -> ResultPayload: # noqa: C901, PLR0911
306
335
  """Handle a request to list directory contents."""
307
336
  try:
@@ -345,6 +374,7 @@ class OSManager:
345
374
  stat = entry.stat()
346
375
  # Get path relative to workspace if within workspace
347
376
  is_entry_in_workspace, entry_path = self._validate_workspace_path(entry)
377
+ mime_type = self._detect_mime_type(entry)
348
378
  entries.append(
349
379
  FileSystemEntry(
350
380
  name=entry.name,
@@ -352,6 +382,7 @@ class OSManager:
352
382
  is_dir=entry.is_dir(),
353
383
  size=stat.st_size,
354
384
  modified_time=stat.st_mtime,
385
+ mime_type=mime_type,
355
386
  )
356
387
  )
357
388
  except (OSError, PermissionError) as e:
@@ -703,3 +734,97 @@ class OSManager:
703
734
  logger.error("Attempted to clean up old files from %s, but no files could be deleted.")
704
735
 
705
736
  return removed_count > 0
737
+
738
+ def on_create_file_request(self, request: CreateFileRequest) -> ResultPayload:
739
+ """Handle a request to create a file or directory."""
740
+ try:
741
+ # Get the full path using the new method
742
+ full_path_str = request.get_full_path()
743
+
744
+ # Determine if path is absolute (not constrained to workspace)
745
+ is_absolute = Path(full_path_str).is_absolute()
746
+
747
+ # If workspace_only is True and path is absolute, it's outside workspace
748
+ if request.workspace_only and is_absolute:
749
+ msg = f"Absolute path is outside workspace: {full_path_str}"
750
+ logger.error(msg)
751
+ return CreateFileResultFailure()
752
+
753
+ # Resolve path - if absolute, use as-is; if relative, align to workspace
754
+ if is_absolute:
755
+ file_path = Path(full_path_str).resolve()
756
+ else:
757
+ file_path = (self._get_workspace_path() / full_path_str).resolve()
758
+
759
+ # Check if it already exists - warn but treat as success
760
+ if file_path.exists():
761
+ msg = f"Path already exists: {file_path}"
762
+ logger.warning(msg)
763
+ return CreateFileResultSuccess(created_path=str(file_path))
764
+
765
+ # Create parent directories if needed
766
+ file_path.parent.mkdir(parents=True, exist_ok=True)
767
+
768
+ if request.is_directory:
769
+ file_path.mkdir()
770
+ logger.info("Created directory: %s", file_path)
771
+ # Create file with optional content
772
+ elif request.content is not None:
773
+ with file_path.open("w", encoding=request.encoding) as f:
774
+ f.write(request.content)
775
+ logger.info("Created file with content: %s", file_path)
776
+ else:
777
+ file_path.touch()
778
+ logger.info("Created empty file: %s", file_path)
779
+
780
+ return CreateFileResultSuccess(created_path=str(file_path))
781
+
782
+ except Exception as e:
783
+ path_info = request.get_full_path() if hasattr(request, "get_full_path") else str(request.path)
784
+ msg = f"Failed to create {'directory' if request.is_directory else 'file'} at {path_info}: {e}"
785
+ logger.error(msg)
786
+ return CreateFileResultFailure()
787
+
788
+ def on_rename_file_request(self, request: RenameFileRequest) -> ResultPayload:
789
+ """Handle a request to rename a file or directory."""
790
+ try:
791
+ # Resolve and validate old path
792
+ old_path = self._resolve_file_path(request.old_path, workspace_only=request.workspace_only is True)
793
+
794
+ # Resolve and validate new path
795
+ new_path = self._resolve_file_path(request.new_path, workspace_only=request.workspace_only is True)
796
+
797
+ # Check if old path exists
798
+ if not old_path.exists():
799
+ msg = f"Source path does not exist: {old_path}"
800
+ logger.error(msg)
801
+ return RenameFileResultFailure()
802
+
803
+ # Check if new path already exists
804
+ if new_path.exists():
805
+ msg = f"Destination path already exists: {new_path}"
806
+ logger.error(msg)
807
+ return RenameFileResultFailure()
808
+
809
+ # Check workspace constraints for both paths
810
+ is_old_in_workspace, _ = self._validate_workspace_path(old_path)
811
+ is_new_in_workspace, _ = self._validate_workspace_path(new_path)
812
+
813
+ if request.workspace_only and (not is_old_in_workspace or not is_new_in_workspace):
814
+ msg = f"One or both paths are outside workspace: {old_path} -> {new_path}"
815
+ logger.error(msg)
816
+ return RenameFileResultFailure()
817
+
818
+ # Create parent directories for new path if needed
819
+ new_path.parent.mkdir(parents=True, exist_ok=True)
820
+
821
+ # Perform the rename operation
822
+ old_path.rename(new_path)
823
+ logger.info("Renamed: %s -> %s", old_path, new_path)
824
+
825
+ return RenameFileResultSuccess(old_path=str(old_path), new_path=str(new_path))
826
+
827
+ except Exception as e:
828
+ msg = f"Failed to rename {request.old_path} to {request.new_path}: {e}"
829
+ logger.error(msg)
830
+ return RenameFileResultFailure()
File without changes
File without changes
File without changes
@@ -91,6 +91,7 @@ if TYPE_CHECKING:
91
91
  from griptape_nodes.exe_types.core_types import Parameter
92
92
  from griptape_nodes.node_library.library_registry import LibraryNameAndVersion
93
93
  from griptape_nodes.retained_mode.events.base_events import ResultPayload
94
+ from griptape_nodes.retained_mode.events.node_events import SetLockNodeStateRequest
94
95
  from griptape_nodes.retained_mode.managers.event_manager import EventManager
95
96
 
96
97
 
@@ -1064,6 +1065,7 @@ class WorkflowManager:
1064
1065
  or len(serialized_flow_commands.serialized_connections) > 0
1065
1066
  or len(serialized_flow_commands.set_parameter_value_commands) > 0
1066
1067
  or len(serialized_flow_commands.sub_flows_commands) > 0
1068
+ or len(serialized_flow_commands.set_lock_commands_per_node) > 0
1067
1069
  )
1068
1070
 
1069
1071
  if not is_referenced_workflow and has_content_to_serialize:
@@ -1110,6 +1112,7 @@ class WorkflowManager:
1110
1112
  # Now generate all the set parameter value code and add it to the flow context.
1111
1113
  set_parameter_value_asts = self._generate_set_parameter_value_code(
1112
1114
  set_parameter_value_commands=serialized_flow_commands.set_parameter_value_commands,
1115
+ lock_commands=serialized_flow_commands.set_lock_commands_per_node,
1113
1116
  node_uuid_to_node_variable_name=node_uuid_to_node_variable_name,
1114
1117
  unique_values_dict_name="top_level_unique_values_dict",
1115
1118
  import_recorder=import_recorder,
@@ -2503,6 +2506,7 @@ class WorkflowManager:
2503
2506
  set_parameter_value_commands: dict[
2504
2507
  SerializedNodeCommands.NodeUUID, list[SerializedNodeCommands.IndirectSetParameterValueCommand]
2505
2508
  ],
2509
+ lock_commands: dict[SerializedNodeCommands.NodeUUID, SetLockNodeStateRequest],
2506
2510
  node_uuid_to_node_variable_name: dict[SerializedNodeCommands.NodeUUID, str],
2507
2511
  unique_values_dict_name: str,
2508
2512
  import_recorder: ImportRecorder,
@@ -2510,9 +2514,14 @@ class WorkflowManager:
2510
2514
  parameter_value_asts = []
2511
2515
  for node_uuid, indirect_set_parameter_value_commands in set_parameter_value_commands.items():
2512
2516
  node_variable_name = node_uuid_to_node_variable_name[node_uuid]
2517
+ lock_node_command = lock_commands.get(node_uuid)
2513
2518
  parameter_value_asts.extend(
2514
2519
  self._generate_set_parameter_value_for_node(
2515
- node_variable_name, indirect_set_parameter_value_commands, unique_values_dict_name, import_recorder
2520
+ node_variable_name,
2521
+ indirect_set_parameter_value_commands,
2522
+ unique_values_dict_name,
2523
+ import_recorder,
2524
+ lock_node_command,
2516
2525
  )
2517
2526
  )
2518
2527
  return parameter_value_asts
@@ -2523,13 +2532,15 @@ class WorkflowManager:
2523
2532
  indirect_set_parameter_value_commands: list[SerializedNodeCommands.IndirectSetParameterValueCommand],
2524
2533
  unique_values_dict_name: str,
2525
2534
  import_recorder: ImportRecorder,
2535
+ lock_node_command: SetLockNodeStateRequest | None = None,
2526
2536
  ) -> list[ast.stmt]:
2527
- if not indirect_set_parameter_value_commands:
2537
+ if not indirect_set_parameter_value_commands and lock_node_command is None:
2528
2538
  return []
2529
2539
 
2530
- import_recorder.add_from_import(
2531
- "griptape_nodes.retained_mode.events.parameter_events", "SetParameterValueRequest"
2532
- )
2540
+ if indirect_set_parameter_value_commands:
2541
+ import_recorder.add_from_import(
2542
+ "griptape_nodes.retained_mode.events.parameter_events", "SetParameterValueRequest"
2543
+ )
2533
2544
 
2534
2545
  set_parameter_value_asts = []
2535
2546
  with_node_context = ast.With(
@@ -2613,6 +2624,44 @@ class WorkflowManager:
2613
2624
  )
2614
2625
  with_node_context.body.append(set_parameter_value_request_call)
2615
2626
 
2627
+ # Add lock command as the LAST command in the with context
2628
+ if lock_node_command is not None:
2629
+ import_recorder.add_from_import(
2630
+ "griptape_nodes.retained_mode.events.node_events", "SetLockNodeStateRequest"
2631
+ )
2632
+
2633
+ lock_node_call_ast = ast.Expr(
2634
+ value=ast.Call(
2635
+ func=ast.Attribute(
2636
+ value=ast.Name(id="GriptapeNodes", ctx=ast.Load(), lineno=1, col_offset=0),
2637
+ attr="handle_request",
2638
+ ctx=ast.Load(),
2639
+ lineno=1,
2640
+ col_offset=0,
2641
+ ),
2642
+ args=[
2643
+ ast.Call(
2644
+ func=ast.Name(id="SetLockNodeStateRequest", ctx=ast.Load(), lineno=1, col_offset=0),
2645
+ args=[],
2646
+ keywords=[
2647
+ ast.keyword(arg="node_name", value=ast.Constant(value=None, lineno=1, col_offset=0)),
2648
+ ast.keyword(
2649
+ arg="lock", value=ast.Constant(value=lock_node_command.lock, lineno=1, col_offset=0)
2650
+ ),
2651
+ ],
2652
+ lineno=1,
2653
+ col_offset=0,
2654
+ )
2655
+ ],
2656
+ keywords=[],
2657
+ lineno=1,
2658
+ col_offset=0,
2659
+ ),
2660
+ lineno=1,
2661
+ col_offset=0,
2662
+ )
2663
+ with_node_context.body.append(lock_node_call_ast)
2664
+
2616
2665
  set_parameter_value_asts.append(with_node_context)
2617
2666
  return set_parameter_value_asts
2618
2667
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -18,6 +18,8 @@ class FileSystemPicker(Trait):
18
18
  min_file_size: int | None = None
19
19
  workspace_only: bool = True
20
20
  initial_path: str | None = None
21
+ allow_create: bool = False
22
+ allow_rename: bool = False
21
23
  element_id: str = field(default_factory=lambda: "FileSystemPicker")
22
24
 
23
25
  def __init__( # noqa: PLR0913
@@ -34,6 +36,8 @@ class FileSystemPicker(Trait):
34
36
  min_file_size: int | None = None,
35
37
  workspace_only: bool = True,
36
38
  initial_path: str | None = None,
39
+ allow_create: bool = False,
40
+ allow_rename: bool = False,
37
41
  ) -> None:
38
42
  super().__init__()
39
43
  self.allow_files = allow_files
@@ -47,6 +51,8 @@ class FileSystemPicker(Trait):
47
51
  self.min_file_size = min_file_size
48
52
  self.workspace_only = workspace_only
49
53
  self.initial_path = initial_path
54
+ self.allow_create = allow_create
55
+ self.allow_rename = allow_rename
50
56
 
51
57
  @classmethod
52
58
  def get_trait_keys(cls) -> list[str]:
@@ -59,6 +65,8 @@ class FileSystemPicker(Trait):
59
65
  "allowDirectories": self.allow_directories,
60
66
  "multiple": self.multiple,
61
67
  "workspaceOnly": self.workspace_only,
68
+ "allowCreate": self.allow_create,
69
+ "allowRename": self.allow_rename,
62
70
  }
63
71
 
64
72
  # Add file types/extensions
@@ -94,6 +102,16 @@ class FileSystemPicker(Trait):
94
102
  msg = "At least one of allow_files or allow_directories must be True"
95
103
  raise ValueError(msg)
96
104
 
105
+ # Validate that creation is only allowed when appropriate selection types are enabled
106
+ if self.allow_create and not self.allow_files and not self.allow_directories:
107
+ msg = "allow_create requires at least one of allow_files or allow_directories to be True"
108
+ raise ValueError(msg)
109
+
110
+ # Validate that rename is only allowed when appropriate selection types are enabled
111
+ if self.allow_rename and not self.allow_files and not self.allow_directories:
112
+ msg = "allow_rename requires at least one of allow_files or allow_directories to be True"
113
+ raise ValueError(msg)
114
+
97
115
  # Validate file size limits
98
116
  if (
99
117
  self.max_file_size is not None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,51 @@
1
+ """Version utilities for Griptape Nodes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.metadata
6
+ import json
7
+ from typing import Literal
8
+
9
+ engine_version = importlib.metadata.version("griptape_nodes")
10
+
11
+
12
+ def get_current_version() -> str:
13
+ """Returns the current version of the Griptape Nodes package."""
14
+ return f"v{engine_version}"
15
+
16
+
17
+ def get_install_source() -> tuple[Literal["git", "file", "pypi"], str | None]:
18
+ """Determines the install source of the Griptape Nodes package.
19
+
20
+ Returns:
21
+ tuple: A tuple containing the install source and commit ID (if applicable).
22
+ """
23
+ dist = importlib.metadata.distribution("griptape_nodes")
24
+ direct_url_text = dist.read_text("direct_url.json")
25
+ # installing from pypi doesn't have a direct_url.json file
26
+ if direct_url_text is None:
27
+ return "pypi", None
28
+
29
+ direct_url_info = json.loads(direct_url_text)
30
+ url = direct_url_info.get("url")
31
+ if url and url.startswith("file://"):
32
+ return "file", None
33
+ if "vcs_info" in direct_url_info:
34
+ return "git", direct_url_info["vcs_info"].get("commit_id")[:7]
35
+ # Fall back to pypi if no other source is found
36
+ return "pypi", None
37
+
38
+
39
+ def get_complete_version_string() -> str:
40
+ """Returns the complete version string including install source and commit ID.
41
+
42
+ Format: v1.2.3 (source) or v1.2.3 (source - commit_id)
43
+
44
+ Returns:
45
+ Complete version string with source and commit info.
46
+ """
47
+ version = get_current_version()
48
+ source, commit_id = get_install_source()
49
+ if commit_id is None:
50
+ return f"{version} ({source})"
51
+ return f"{version} ({source} - {commit_id})"
File without changes
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: griptape-nodes
3
- Version: 0.43.1
3
+ Version: 0.44.0
4
4
  Summary: Add your description here
5
5
  Requires-Dist: griptape>=1.8.0
6
6
  Requires-Dist: pydantic>=2.10.6