griptape-nodes 0.64.11__py3-none-any.whl → 0.65.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 (55) hide show
  1. griptape_nodes/app/app.py +25 -5
  2. griptape_nodes/cli/commands/init.py +65 -54
  3. griptape_nodes/cli/commands/libraries.py +92 -85
  4. griptape_nodes/cli/commands/self.py +121 -0
  5. griptape_nodes/common/node_executor.py +2142 -101
  6. griptape_nodes/exe_types/base_iterative_nodes.py +1004 -0
  7. griptape_nodes/exe_types/connections.py +114 -19
  8. griptape_nodes/exe_types/core_types.py +225 -7
  9. griptape_nodes/exe_types/flow.py +3 -3
  10. griptape_nodes/exe_types/node_types.py +681 -225
  11. griptape_nodes/exe_types/param_components/README.md +414 -0
  12. griptape_nodes/exe_types/param_components/api_key_provider_parameter.py +200 -0
  13. griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py +2 -0
  14. griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py +79 -5
  15. griptape_nodes/exe_types/param_types/parameter_button.py +443 -0
  16. griptape_nodes/machines/control_flow.py +77 -38
  17. griptape_nodes/machines/dag_builder.py +148 -70
  18. griptape_nodes/machines/parallel_resolution.py +61 -35
  19. griptape_nodes/machines/sequential_resolution.py +11 -113
  20. griptape_nodes/retained_mode/events/app_events.py +1 -0
  21. griptape_nodes/retained_mode/events/base_events.py +16 -13
  22. griptape_nodes/retained_mode/events/connection_events.py +3 -0
  23. griptape_nodes/retained_mode/events/execution_events.py +35 -0
  24. griptape_nodes/retained_mode/events/flow_events.py +15 -2
  25. griptape_nodes/retained_mode/events/library_events.py +347 -0
  26. griptape_nodes/retained_mode/events/node_events.py +48 -0
  27. griptape_nodes/retained_mode/events/os_events.py +86 -3
  28. griptape_nodes/retained_mode/events/project_events.py +15 -1
  29. griptape_nodes/retained_mode/events/workflow_events.py +48 -1
  30. griptape_nodes/retained_mode/griptape_nodes.py +6 -2
  31. griptape_nodes/retained_mode/managers/config_manager.py +10 -8
  32. griptape_nodes/retained_mode/managers/event_manager.py +168 -0
  33. griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +2 -0
  34. griptape_nodes/retained_mode/managers/fitness_problems/libraries/old_xdg_location_warning_problem.py +43 -0
  35. griptape_nodes/retained_mode/managers/flow_manager.py +664 -123
  36. griptape_nodes/retained_mode/managers/library_manager.py +1134 -138
  37. griptape_nodes/retained_mode/managers/model_manager.py +2 -3
  38. griptape_nodes/retained_mode/managers/node_manager.py +148 -25
  39. griptape_nodes/retained_mode/managers/object_manager.py +3 -1
  40. griptape_nodes/retained_mode/managers/operation_manager.py +3 -1
  41. griptape_nodes/retained_mode/managers/os_manager.py +1158 -122
  42. griptape_nodes/retained_mode/managers/secrets_manager.py +2 -3
  43. griptape_nodes/retained_mode/managers/settings.py +21 -1
  44. griptape_nodes/retained_mode/managers/sync_manager.py +2 -3
  45. griptape_nodes/retained_mode/managers/workflow_manager.py +358 -104
  46. griptape_nodes/retained_mode/retained_mode.py +3 -3
  47. griptape_nodes/traits/button.py +44 -2
  48. griptape_nodes/traits/file_system_picker.py +2 -2
  49. griptape_nodes/utils/file_utils.py +101 -0
  50. griptape_nodes/utils/git_utils.py +1226 -0
  51. griptape_nodes/utils/library_utils.py +122 -0
  52. {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.0.dist-info}/METADATA +2 -1
  53. {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.0.dist-info}/RECORD +55 -47
  54. {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.0.dist-info}/WHEEL +1 -1
  55. {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.0.dist-info}/entry_points.txt +0 -0
@@ -928,7 +928,7 @@ class RetainedMode:
928
928
  )
929
929
  result = GriptapeNodes().handle_request(request)
930
930
 
931
- if not result.succeeded():
931
+ if result.failed():
932
932
  return False, result
933
933
 
934
934
  # Navigate through indices
@@ -983,7 +983,7 @@ class RetainedMode:
983
983
  )
984
984
  result = GriptapeNodes().handle_request(request)
985
985
 
986
- if not result.succeeded():
986
+ if result.failed():
987
987
  return result
988
988
 
989
989
  # Navigate to the proper location and set the value
@@ -1169,7 +1169,7 @@ class RetainedMode:
1169
1169
  )
1170
1170
  result = GriptapeNodes().handle_request(request)
1171
1171
 
1172
- if not result.succeeded():
1172
+ if result.failed():
1173
1173
  logger.error(
1174
1174
  'set_value failed for "%s.%s", failed to get value for container "%s".',
1175
1175
  node,
@@ -104,6 +104,7 @@ class Button(Trait):
104
104
  loading_icon: str | None = None
105
105
  loading_icon_class: str | None = None
106
106
  tooltip: str | None = None
107
+ button_link: str | None = None
107
108
 
108
109
  element_id: str = field(default_factory=lambda: "Button")
109
110
  on_click_callback: OnClickCallback | None = field(default=None, init=False)
@@ -124,6 +125,7 @@ class Button(Trait):
124
125
  loading_icon: str | None = None,
125
126
  loading_icon_class: str | None = None,
126
127
  tooltip: str | None = None,
128
+ button_link: str | None = None,
127
129
  on_click: OnClickCallback | None = None,
128
130
  get_button_state: GetButtonStateCallback | None = None,
129
131
  ) -> None:
@@ -140,9 +142,42 @@ class Button(Trait):
140
142
  self.loading_icon = loading_icon
141
143
  self.loading_icon_class = loading_icon_class
142
144
  self.tooltip = tooltip
143
- self.on_click_callback = on_click
145
+ self.button_link = button_link
146
+
147
+ # Validate that both button_link and on_click are not provided simultaneously
148
+ if button_link is not None and on_click is not None:
149
+ error_msg = (
150
+ "Cannot specify both 'button_link' and 'on_click' for Button. "
151
+ "Use 'button_link' for simple URL navigation or 'on_click' for custom behavior."
152
+ )
153
+ raise ValueError(error_msg)
154
+
155
+ # If button_link is provided and no custom on_click handler, create a default handler
156
+ if button_link is not None:
157
+ self.on_click_callback = self._create_button_link_handler(button_link)
158
+ else:
159
+ self.on_click_callback = on_click
144
160
  self.get_button_state_callback = get_button_state
145
161
 
162
+ def _create_button_link_handler(self, url: str) -> OnClickCallback:
163
+ """Create a default handler for button_link URLs."""
164
+
165
+ def handler(
166
+ button: Button, # noqa: ARG001
167
+ button_details: ButtonDetailsMessagePayload,
168
+ ) -> NodeMessageResult:
169
+ return NodeMessageResult(
170
+ success=True,
171
+ details="Opening URL",
172
+ response=OnClickMessageResultPayload(
173
+ button_details=button_details,
174
+ href=url,
175
+ ),
176
+ altered_workflow_state=False,
177
+ )
178
+
179
+ return handler
180
+
146
181
  @classmethod
147
182
  def get_trait_keys(cls) -> list[str]:
148
183
  return ["button", "addbutton"]
@@ -195,7 +230,7 @@ class Button(Trait):
195
230
 
196
231
  return options
197
232
 
198
- def on_message_received(self, message_type: str, message: NodeMessagePayload | None) -> NodeMessageResult | None: # noqa: PLR0911
233
+ def on_message_received(self, message_type: str, message: NodeMessagePayload | None) -> NodeMessageResult | None: # noqa: C901, PLR0911, PLR0912
199
234
  """Handle messages sent to this button trait.
200
235
 
201
236
  Args:
@@ -211,6 +246,13 @@ class Button(Trait):
211
246
  try:
212
247
  # Pre-fill button details with current state and pass to callback
213
248
  button_details = self.get_button_details()
249
+ # Include original message's data if present (for payloadData support)
250
+ if message is not None:
251
+ # Handle both NodeMessagePayload objects and dict messages
252
+ if isinstance(message, NodeMessagePayload) and message.data is not None:
253
+ button_details.data = message.data
254
+ elif isinstance(message, dict) and "data" in message:
255
+ button_details.data = message["data"]
214
256
  result = self.on_click_callback(self, button_details)
215
257
 
216
258
  # If callback returns None, provide optimistic success result
@@ -16,7 +16,7 @@ class FileSystemPicker(Trait):
16
16
  include_patterns: list[str] = field(default_factory=list)
17
17
  max_file_size: int | None = None
18
18
  min_file_size: int | None = None
19
- workspace_only: bool = True
19
+ workspace_only: bool = False
20
20
  initial_path: str | None = None
21
21
  allow_create: bool = False
22
22
  allow_rename: bool = False
@@ -34,7 +34,7 @@ class FileSystemPicker(Trait):
34
34
  include_patterns: list[str] | None = None,
35
35
  max_file_size: int | None = None,
36
36
  min_file_size: int | None = None,
37
- workspace_only: bool = True,
37
+ workspace_only: bool = False,
38
38
  initial_path: str | None = None,
39
39
  allow_create: bool = False,
40
40
  allow_rename: bool = False,
@@ -0,0 +1,101 @@
1
+ """Utilities for file and directory operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import os
7
+ from fnmatch import fnmatch
8
+ from pathlib import Path
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def find_file_in_directory(directory: Path, pattern: str) -> Path | None:
14
+ """Search directory recursively for a file matching the given pattern.
15
+
16
+ Args:
17
+ directory: Directory to search in
18
+ pattern: Glob pattern to match files against (e.g., '*.json', '*library*.json')
19
+
20
+ Returns:
21
+ Path to the first matching file if found, None otherwise.
22
+ Logs a warning if multiple files match the pattern.
23
+
24
+ Examples:
25
+ >>> find_file_in_directory(Path("/workspace"), "config.json")
26
+ Path("/workspace/subdir/config.json")
27
+ >>> find_file_in_directory(Path("/workspace"), "*library*.json")
28
+ Path("/workspace/libs/my_library.json")
29
+ >>> find_file_in_directory(Path("/empty"), "missing.txt")
30
+ None
31
+ """
32
+ if not directory.exists():
33
+ logger.debug("Directory does not exist: %s", directory)
34
+ return None
35
+
36
+ if not directory.is_dir():
37
+ logger.debug("Path is not a directory: %s", directory)
38
+ return None
39
+
40
+ matches = []
41
+ for root, _, files_found in os.walk(directory):
42
+ for file in files_found:
43
+ if fnmatch(file, pattern):
44
+ found_path = Path(root) / file
45
+ matches.append(found_path)
46
+
47
+ if not matches:
48
+ logger.debug("No files matching pattern '%s' found in directory: %s", pattern, directory)
49
+ return None
50
+
51
+ if len(matches) > 1:
52
+ for _match in matches:
53
+ pass
54
+ logger.warning(
55
+ "Found multiple files matching pattern '%s' in %s, using first one at %s",
56
+ pattern,
57
+ directory,
58
+ matches[0],
59
+ )
60
+
61
+ logger.debug("Found file matching pattern '%s' at: %s", pattern, matches[0])
62
+ return matches[0]
63
+
64
+
65
+ def find_all_files_in_directory(directory: Path, pattern: str) -> list[Path]:
66
+ """Search directory recursively for all files matching the given pattern.
67
+
68
+ Args:
69
+ directory: Directory to search in
70
+ pattern: Glob pattern to match files against (e.g., '*.json', '*library*.json')
71
+
72
+ Returns:
73
+ List of all matching file paths. Returns empty list if none found.
74
+
75
+ Examples:
76
+ >>> find_all_files_in_directory(Path("/workspace"), "*.json")
77
+ [Path("/workspace/a.json"), Path("/workspace/sub/b.json")]
78
+ >>> find_all_files_in_directory(Path("/empty"), "*.txt")
79
+ []
80
+ """
81
+ if not directory.exists():
82
+ logger.debug("Directory does not exist: %s", directory)
83
+ return []
84
+
85
+ if not directory.is_dir():
86
+ logger.debug("Path is not a directory: %s", directory)
87
+ return []
88
+
89
+ matches = []
90
+ for root, _, files_found in os.walk(directory):
91
+ for file in files_found:
92
+ if fnmatch(file, pattern):
93
+ found_path = Path(root) / file
94
+ matches.append(found_path)
95
+
96
+ if not matches:
97
+ logger.debug("No files matching pattern '%s' found in directory: %s", pattern, directory)
98
+ else:
99
+ logger.debug("Found %d file(s) matching pattern '%s' in directory: %s", len(matches), pattern, directory)
100
+
101
+ return matches