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
@@ -303,6 +303,7 @@ class GetAllNodeInfoResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess)
303
303
 
304
304
  metadata: dict
305
305
  node_resolution_state: str
306
+ locked: bool
306
307
  connections: ListConnectionsForNodeResultSuccess
307
308
  element_id_to_value: dict[str, ParameterInfoValue]
308
309
  root_node_element: dict[str, Any]
@@ -317,6 +318,39 @@ class GetAllNodeInfoResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure)
317
318
  """
318
319
 
319
320
 
321
+ @dataclass
322
+ @PayloadRegistry.register
323
+ class SetLockNodeStateRequest(WorkflowNotAlteredMixin, RequestPayload):
324
+ """Lock a node.
325
+
326
+ Use when: Implementing locking functionality, preventing changes to nodes.
327
+
328
+ Args:
329
+ node_name: Name of the node to lock
330
+ lock: Whether to lock or unlock the node. If true, the node will be locked, otherwise it will be unlocked.
331
+
332
+ Results: SetLockNodeStateResultSuccess (node locked) | SetLockNodeStateResultFailure (node not found)
333
+ """
334
+
335
+ node_name: str | None
336
+ lock: bool
337
+
338
+
339
+ @dataclass
340
+ @PayloadRegistry.register
341
+ class SetLockNodeStateResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
342
+ """Node locked successfully."""
343
+
344
+ node_name: str
345
+ locked: bool
346
+
347
+
348
+ @dataclass
349
+ @PayloadRegistry.register
350
+ class SetLockNodeStateResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
351
+ """Node failed to lock."""
352
+
353
+
320
354
  # A Node's state can be serialized to a sequence of commands that the engine runs.
321
355
  @dataclass
322
356
  class SerializedNodeCommands:
@@ -354,6 +388,7 @@ class SerializedNodeCommands:
354
388
  create_node_command: CreateNodeRequest
355
389
  element_modification_commands: list[RequestPayload]
356
390
  node_library_details: LibraryNameAndVersion
391
+ lock_node_command: SetLockNodeStateRequest | None = None
357
392
  node_uuid: NodeUUID = field(default_factory=lambda: SerializedNodeCommands.NodeUUID(str(uuid4())))
358
393
 
359
394
 
@@ -477,6 +512,7 @@ class SerializedSelectedNodesCommands:
477
512
  set_parameter_value_commands: dict[
478
513
  SerializedNodeCommands.NodeUUID, list[SerializedNodeCommands.IndirectSetParameterValueCommand]
479
514
  ]
515
+ set_lock_commands_per_node: dict[SerializedNodeCommands.NodeUUID, SetLockNodeStateRequest]
480
516
  serialized_connection_commands: list[IndirectConnectionSerialization]
481
517
 
482
518
 
File without changes
@@ -18,21 +18,23 @@ class FileSystemEntry:
18
18
  is_dir: bool
19
19
  size: int
20
20
  modified_time: float
21
+ mime_type: str | None = None # None for directories, mimetype for files
21
22
 
22
23
 
23
24
  @dataclass
24
25
  @PayloadRegistry.register
25
26
  class OpenAssociatedFileRequest(RequestPayload):
26
- """Open a file using the operating system's associated application.
27
+ """Open a file or directory using the operating system's associated application.
27
28
 
28
29
  Use when: Opening generated files, launching external applications,
29
- providing file viewing capabilities, implementing file associations.
30
+ providing file viewing capabilities, implementing file associations,
31
+ opening folders in system explorer.
30
32
 
31
33
  Args:
32
- path_to_file: Path to the file to open (mutually exclusive with file_entry)
34
+ path_to_file: Path to the file or directory to open (mutually exclusive with file_entry)
33
35
  file_entry: FileSystemEntry object from directory listing (mutually exclusive with path_to_file)
34
36
 
35
- Results: OpenAssociatedFileResultSuccess | OpenAssociatedFileResultFailure (file not found, no association)
37
+ Results: OpenAssociatedFileResultSuccess | OpenAssociatedFileResultFailure (path not found, no association)
36
38
  """
37
39
 
38
40
  path_to_file: str | None = None
@@ -42,13 +44,13 @@ class OpenAssociatedFileRequest(RequestPayload):
42
44
  @dataclass
43
45
  @PayloadRegistry.register
44
46
  class OpenAssociatedFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
45
- """File opened successfully with associated application."""
47
+ """File or directory opened successfully with associated application."""
46
48
 
47
49
 
48
50
  @dataclass
49
51
  @PayloadRegistry.register
50
52
  class OpenAssociatedFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
51
- """File opening failed. Common causes: file not found, no associated application, permission denied."""
53
+ """File or directory opening failed. Common causes: path not found, no associated application, permission denied."""
52
54
 
53
55
 
54
56
  @dataclass
@@ -138,3 +140,93 @@ class ReadFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
138
140
  @PayloadRegistry.register
139
141
  class ReadFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
140
142
  """File reading failed. Common causes: file not found, permission denied, encoding error."""
143
+
144
+
145
+ @dataclass
146
+ @PayloadRegistry.register
147
+ class CreateFileRequest(RequestPayload):
148
+ """Create a new file or directory.
149
+
150
+ Use when: Creating files/directories through file picker,
151
+ implementing file creation functionality.
152
+
153
+ Args:
154
+ path: Path where the file/directory should be created (legacy, use directory_path + name instead)
155
+ directory_path: Directory where to create the file/directory (mutually exclusive with path)
156
+ name: Name of the file/directory to create (mutually exclusive with path)
157
+ is_directory: True to create a directory, False for a file
158
+ content: Initial content for files (optional)
159
+ encoding: Text encoding for file content (default: 'utf-8')
160
+ workspace_only: If True, constrain to workspace directory
161
+
162
+ Results: CreateFileResultSuccess | CreateFileResultFailure
163
+ """
164
+
165
+ path: str | None = None
166
+ directory_path: str | None = None
167
+ name: str | None = None
168
+ is_directory: bool = False
169
+ content: str | None = None
170
+ encoding: str = "utf-8"
171
+ workspace_only: bool | None = True
172
+
173
+ def get_full_path(self) -> str:
174
+ """Get the full path, constructing from directory_path + name if path is not provided."""
175
+ if self.path is not None:
176
+ return self.path
177
+ if self.directory_path is not None and self.name is not None:
178
+ from pathlib import Path
179
+
180
+ return str(Path(self.directory_path) / self.name)
181
+ msg = "Either 'path' or both 'directory_path' and 'name' must be provided"
182
+ raise ValueError(msg)
183
+
184
+
185
+ @dataclass
186
+ @PayloadRegistry.register
187
+ class CreateFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
188
+ """File/directory created successfully."""
189
+
190
+ created_path: str
191
+
192
+
193
+ @dataclass
194
+ @PayloadRegistry.register
195
+ class CreateFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
196
+ """File/directory creation failed."""
197
+
198
+
199
+ @dataclass
200
+ @PayloadRegistry.register
201
+ class RenameFileRequest(RequestPayload):
202
+ """Rename a file or directory.
203
+
204
+ Use when: Renaming files/directories through file picker,
205
+ implementing file rename functionality.
206
+
207
+ Args:
208
+ old_path: Current path of the file/directory to rename
209
+ new_path: New path for the file/directory
210
+ workspace_only: If True, constrain to workspace directory
211
+
212
+ Results: RenameFileResultSuccess | RenameFileResultFailure
213
+ """
214
+
215
+ old_path: str
216
+ new_path: str
217
+ workspace_only: bool | None = True
218
+
219
+
220
+ @dataclass
221
+ @PayloadRegistry.register
222
+ class RenameFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
223
+ """File/directory renamed successfully."""
224
+
225
+ old_path: str
226
+ new_path: str
227
+
228
+
229
+ @dataclass
230
+ @PayloadRegistry.register
231
+ class RenameFileResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
232
+ """File/directory rename failed."""
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import importlib.metadata
4
3
  import logging
5
4
  import os
6
5
  import re
@@ -46,6 +45,7 @@ from griptape_nodes.retained_mode.events.flow_events import (
46
45
  DeleteFlowRequest,
47
46
  )
48
47
  from griptape_nodes.utils.metaclasses import SingletonMeta
48
+ from griptape_nodes.utils.version_utils import engine_version
49
49
 
50
50
  if TYPE_CHECKING:
51
51
  from griptape_nodes.retained_mode.managers.agent_manager import AgentManager
@@ -78,9 +78,6 @@ if TYPE_CHECKING:
78
78
  logger = logging.getLogger("griptape_nodes")
79
79
 
80
80
 
81
- engine_version = importlib.metadata.version("griptape_nodes")
82
-
83
-
84
81
  @dataclass
85
82
  class Version:
86
83
  major: int
File without changes
File without changes
@@ -254,7 +254,7 @@ class ConfigManager:
254
254
  existing_workflows = self.get_config_value(config_loc)
255
255
  if not existing_workflows:
256
256
  existing_workflows = []
257
- existing_workflows.append(workflow_file_name)
257
+ existing_workflows.append(workflow_file_name) if workflow_file_name not in existing_workflows else None
258
258
  self.set_config_value(config_loc, existing_workflows)
259
259
 
260
260
  def delete_user_workflow(self, workflow_file_name: str) -> None:
File without changes
File without changes
@@ -1338,6 +1338,7 @@ class FlowManager:
1338
1338
 
1339
1339
  serialized_node_commands = []
1340
1340
  set_parameter_value_commands_per_node = {} # Maps a node UUID to a list of set parameter value commands
1341
+ set_lock_commands_per_node = {} # Maps a node UUID to a set Lock command, if it exists.
1341
1342
 
1342
1343
  # Now each of the child nodes in the flow.
1343
1344
  node_name_to_uuid = {}
@@ -1379,6 +1380,10 @@ class FlowManager:
1379
1380
  node_libraries_in_use.add(serialized_node.node_library_details)
1380
1381
  # Get the list of set value commands for THIS node.
1381
1382
  set_value_commands_list = serialize_node_result.set_parameter_value_commands
1383
+ if serialize_node_result.serialized_node_commands.lock_node_command is not None:
1384
+ set_lock_commands_per_node[serialized_node.node_uuid] = (
1385
+ serialize_node_result.serialized_node_commands.lock_node_command
1386
+ )
1382
1387
  set_parameter_value_commands_per_node[serialized_node.node_uuid] = set_value_commands_list
1383
1388
 
1384
1389
  # We'll have to do a patch-up of all the connections, since we can't predict all of the node names being accurate
@@ -1461,6 +1466,7 @@ class FlowManager:
1461
1466
  serialized_connections=create_connection_commands,
1462
1467
  unique_parameter_uuid_to_values=unique_parameter_uuid_to_values,
1463
1468
  set_parameter_value_commands=set_parameter_value_commands_per_node,
1469
+ set_lock_commands_per_node=set_lock_commands_per_node,
1464
1470
  sub_flows_commands=sub_flow_commands,
1465
1471
  node_libraries_used=node_libraries_in_use,
1466
1472
  referenced_workflows=referenced_workflows_in_use,
@@ -96,6 +96,7 @@ from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance.
96
96
  )
97
97
  from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
98
98
  from griptape_nodes.retained_mode.managers.os_manager import OSManager
99
+ from griptape_nodes.utils.version_utils import get_complete_version_string
99
100
 
100
101
  if TYPE_CHECKING:
101
102
  from collections.abc import Callable
@@ -1594,14 +1595,7 @@ class LibraryManager:
1594
1595
  GriptapeNodes.WorkflowManager().on_libraries_initialization_complete()
1595
1596
 
1596
1597
  # Print the engine ready message
1597
- engine_version_request = GetEngineVersionRequest()
1598
- engine_version_result = GriptapeNodes.get_instance().handle_engine_version_request(engine_version_request)
1599
- if isinstance(engine_version_result, GetEngineVersionResultSuccess):
1600
- engine_version = (
1601
- f"v{engine_version_result.major}.{engine_version_result.minor}.{engine_version_result.patch}"
1602
- )
1603
- else:
1604
- engine_version = "<UNKNOWN ENGINE VERSION>"
1598
+ engine_version = get_complete_version_string()
1605
1599
 
1606
1600
  # Get current session ID
1607
1601
  session_id = GriptapeNodes.get_session_id()
@@ -81,6 +81,9 @@ from griptape_nodes.retained_mode.events.node_events import (
81
81
  SerializeNodeToCommandsResultSuccess,
82
82
  SerializeSelectedNodesToCommandsRequest,
83
83
  SerializeSelectedNodesToCommandsResultSuccess,
84
+ SetLockNodeStateRequest,
85
+ SetLockNodeStateResultFailure,
86
+ SetLockNodeStateResultSuccess,
84
87
  SetNodeMetadataRequest,
85
88
  SetNodeMetadataResultFailure,
86
89
  SetNodeMetadataResultSuccess,
@@ -178,6 +181,7 @@ class NodeManager:
178
181
  DeserializeSelectedNodesFromCommandsRequest, self.on_deserialize_selected_nodes_from_commands
179
182
  )
180
183
  event_manager.assign_manager_to_request_type(DuplicateSelectedNodesRequest, self.on_duplicate_selected_nodes)
184
+ event_manager.assign_manager_to_request_type(SetLockNodeStateRequest, self.on_toggle_lock_node_request)
181
185
 
182
186
  def handle_node_rename(self, old_name: str, new_name: str) -> None:
183
187
  # Get the node itself
@@ -781,6 +785,13 @@ class NodeManager:
781
785
  result = AddParameterToNodeResultFailure()
782
786
  return result
783
787
 
788
+ # Check if node is locked
789
+ if node.lock:
790
+ details = f"Attempted to add Parameter '{request.parameter_name}' to Node '{node_name}'. Failed because the Node was locked."
791
+ logger.error(details)
792
+ result = AddParameterToNodeResultFailure()
793
+ return result
794
+
784
795
  if request.parent_container_name and not request.initial_setup:
785
796
  parameter = node.get_parameter_by_name(request.parent_container_name)
786
797
  if parameter is None:
@@ -923,7 +934,13 @@ class NodeManager:
923
934
 
924
935
  result = RemoveParameterFromNodeResultFailure()
925
936
  return result
937
+ # Check if the node is locked
938
+ if node.lock:
939
+ details = f"Attempted to remove Element '{request.parameter_name}' from Node '{node_name}'. Failed because the Node was locked."
940
+ logger.error(details)
926
941
 
942
+ result = RemoveParameterFromNodeResultFailure()
943
+ return result
927
944
  # Does the Element actually exist on the Node?
928
945
  element = node.get_element_by_name_and_type(request.parameter_name)
929
946
  if element is None:
@@ -1246,7 +1263,7 @@ class NodeManager:
1246
1263
 
1247
1264
  return None
1248
1265
 
1249
- def on_alter_parameter_details_request(self, request: AlterParameterDetailsRequest) -> ResultPayload: # noqa: C901
1266
+ def on_alter_parameter_details_request(self, request: AlterParameterDetailsRequest) -> ResultPayload: # noqa: C901, PLR0911
1250
1267
  node_name = request.node_name
1251
1268
  node = None
1252
1269
 
@@ -1269,6 +1286,12 @@ class NodeManager:
1269
1286
 
1270
1287
  return AlterParameterDetailsResultFailure()
1271
1288
 
1289
+ # Is the node locked?
1290
+ if node.lock:
1291
+ details = f"Attempted to alter details for Parameter '{request.parameter_name}' from Node '{node_name}'. Failed because the Node was locked."
1292
+ logger.error(details)
1293
+ return AlterParameterDetailsResultFailure()
1294
+
1272
1295
  # Does the Element actually exist on the Node?
1273
1296
  element = node.get_element_by_name_and_type(request.parameter_name)
1274
1297
  if element is None:
@@ -1397,6 +1420,12 @@ class NodeManager:
1397
1420
  logger.error(details)
1398
1421
  return SetParameterValueResultFailure()
1399
1422
 
1423
+ # Is the node locked?
1424
+ if node.lock:
1425
+ details = f"Attempted to set parameter '{param_name}' value on node '{node_name}'. Failed because the Node was locked."
1426
+ logger.error(details)
1427
+ return SetParameterValueResultFailure()
1428
+
1400
1429
  # Does the Parameter actually exist on the Node?
1401
1430
  parameter = node.get_parameter_by_name(param_name)
1402
1431
  if parameter is None:
@@ -1596,6 +1625,7 @@ class NodeManager:
1596
1625
  result = GetAllNodeInfoResultSuccess(
1597
1626
  metadata=get_metadata_success.metadata,
1598
1627
  node_resolution_state=get_resolution_state_success.state,
1628
+ locked=node.lock,
1599
1629
  connections=list_connections_success,
1600
1630
  element_id_to_value=element_id_to_value,
1601
1631
  root_node_element=element_details,
@@ -1837,7 +1867,7 @@ class NodeManager:
1837
1867
  validation_succeeded=(len(all_exceptions) == 0), exceptions=all_exceptions
1838
1868
  )
1839
1869
 
1840
- def on_serialize_node_to_commands(self, request: SerializeNodeToCommandsRequest) -> ResultPayload: # noqa: C901, PLR0912
1870
+ def on_serialize_node_to_commands(self, request: SerializeNodeToCommandsRequest) -> ResultPayload: # noqa: C901, PLR0912, PLR0915
1841
1871
  node_name = request.node_name
1842
1872
  node = None
1843
1873
 
@@ -1923,12 +1953,19 @@ class NodeManager:
1923
1953
  )
1924
1954
  if set_param_value_requests is not None:
1925
1955
  set_value_commands.extend(set_param_value_requests)
1926
-
1956
+ else:
1957
+ create_node_request.resolution = NodeResolutionState.UNRESOLVED.value
1958
+ # now check if locked
1959
+ if node.lock:
1960
+ lock_command = SetLockNodeStateRequest(node_name=None, lock=True)
1961
+ else:
1962
+ lock_command = None
1927
1963
  # Hooray
1928
1964
  serialized_node_commands = SerializedNodeCommands(
1929
1965
  create_node_command=create_node_request,
1930
1966
  element_modification_commands=element_modification_commands,
1931
1967
  node_library_details=library_details,
1968
+ lock_node_command=lock_command,
1932
1969
  )
1933
1970
  details = f"Successfully serialized node '{node_name}' into commands."
1934
1971
  logger.debug(details)
@@ -2073,7 +2110,6 @@ class NodeManager:
2073
2110
  details = f"Attempted to deserialize a serialized set of Node Creation commands. Failed to execute an element command for node '{node_name}'."
2074
2111
  logger.error(details)
2075
2112
  return DeserializeNodeFromCommandsResultFailure()
2076
-
2077
2113
  details = f"Successfully deserialized a serialized set of Node Creation commands for node '{node_name}'."
2078
2114
  logger.debug(details)
2079
2115
  return DeserializeNodeFromCommandsResultSuccess(node_name=node_name)
@@ -2091,6 +2127,8 @@ class NodeManager:
2091
2127
  connections_to_serialize = []
2092
2128
  # This is also node_uuid to the parameter serialization command.
2093
2129
  parameter_commands = {}
2130
+ # This is node_uuid to lock commands.
2131
+ lock_commands = {}
2094
2132
  # I need to store node names and parameter names to UUID
2095
2133
  unique_uuid_to_values = {}
2096
2134
  # And track how values map into that map.
@@ -2111,6 +2149,7 @@ class NodeManager:
2111
2149
  node_commands[node_name] = result.serialized_node_commands
2112
2150
  node_name_to_uuid[node_name] = result.serialized_node_commands.node_uuid
2113
2151
  parameter_commands[result.serialized_node_commands.node_uuid] = result.set_parameter_value_commands
2152
+ lock_commands[result.serialized_node_commands.node_uuid] = result.serialized_node_commands.lock_node_command
2114
2153
  try:
2115
2154
  flow_name = self.get_node_parent_flow_by_name(node_name)
2116
2155
  GriptapeNodes.FlowManager().get_flow_by_name(flow_name)
@@ -2146,6 +2185,7 @@ class NodeManager:
2146
2185
  serialized_node_commands=list(node_commands.values()),
2147
2186
  serialized_connection_commands=serialized_connections,
2148
2187
  set_parameter_value_commands=parameter_commands,
2188
+ set_lock_commands_per_node=lock_commands,
2149
2189
  )
2150
2190
  # Set everything in the clipboard!
2151
2191
  GriptapeNodes.ContextManager()._clipboard.node_commands = final_result
@@ -2209,6 +2249,12 @@ class NodeManager:
2209
2249
  if not set_parameter_result.succeeded():
2210
2250
  details = f"Failed to set parameter value for {param_request.parameter_name} on node {param_request.node_name}"
2211
2251
  logger.warning(details)
2252
+ lock_command = commands.set_lock_commands_per_node[node_command.node_uuid]
2253
+ if lock_command is not None:
2254
+ lock_node_result = GriptapeNodes.handle_request(lock_command)
2255
+ if not lock_node_result.succeeded():
2256
+ details = f"Failed to lock node {lock_command.node_name}"
2257
+ logger.warning(details)
2212
2258
  # create Connections
2213
2259
  for connection_command in connections:
2214
2260
  connection_request = CreateConnectionRequest(
@@ -2389,7 +2435,7 @@ class NodeManager:
2389
2435
  commands.append(output_command)
2390
2436
  return commands if commands else None
2391
2437
 
2392
- def on_rename_parameter_request(self, request: RenameParameterRequest) -> ResultPayload: # noqa: C901, PLR0912
2438
+ def on_rename_parameter_request(self, request: RenameParameterRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912
2393
2439
  """Handle renaming a parameter on a node.
2394
2440
 
2395
2441
  Args:
@@ -2415,6 +2461,12 @@ class NodeManager:
2415
2461
  logger.error(details)
2416
2462
  return RenameParameterResultFailure()
2417
2463
 
2464
+ # Is the node locked?
2465
+ if node.lock:
2466
+ details = f"Attempted to rename Parameter '{request.parameter_name}' on Node '{node_name}'. Failed because the Node is locked."
2467
+ logger.error(details)
2468
+ return RenameParameterResultFailure()
2469
+
2418
2470
  # Get the parameter
2419
2471
  parameter = node.get_parameter_by_name(request.parameter_name)
2420
2472
  if parameter is None:
@@ -2469,3 +2521,22 @@ class NodeManager:
2469
2521
  return RenameParameterResultSuccess(
2470
2522
  old_parameter_name=old_name, new_parameter_name=request.new_parameter_name, node_name=node_name
2471
2523
  )
2524
+
2525
+ def on_toggle_lock_node_request(self, request: SetLockNodeStateRequest) -> ResultPayload:
2526
+ node_name = request.node_name
2527
+ if node_name is None:
2528
+ if not GriptapeNodes.ContextManager().has_current_node():
2529
+ details = "Attempted to lock node in the Current Context. Failed because the Current Context was empty."
2530
+ logger.error(details)
2531
+ return SetLockNodeStateResultFailure()
2532
+ node = GriptapeNodes.ContextManager().get_current_node()
2533
+ node_name = node.name
2534
+ else:
2535
+ try:
2536
+ node = self.get_node_by_name(node_name)
2537
+ except ValueError as err:
2538
+ details = f"Attempted to lock node '{request.node_name}'. Failed because the Node could not be found. Error: {err}"
2539
+ logger.error(details)
2540
+ return SetLockNodeStateResultFailure()
2541
+ node.lock = request.lock
2542
+ return SetLockNodeStateResultSuccess(node_name=node_name, locked=node.lock)
File without changes