griptape-nodes 0.41.0__py3-none-any.whl → 0.43.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 (133) hide show
  1. griptape_nodes/__init__.py +0 -0
  2. griptape_nodes/app/.python-version +0 -0
  3. griptape_nodes/app/__init__.py +1 -10
  4. griptape_nodes/app/api.py +199 -0
  5. griptape_nodes/app/app.py +140 -222
  6. griptape_nodes/app/watch.py +4 -2
  7. griptape_nodes/bootstrap/__init__.py +0 -0
  8. griptape_nodes/bootstrap/bootstrap_script.py +0 -0
  9. griptape_nodes/bootstrap/register_libraries_script.py +0 -0
  10. griptape_nodes/bootstrap/structure_config.yaml +0 -0
  11. griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
  12. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +0 -0
  13. griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +0 -0
  14. griptape_nodes/bootstrap/workflow_runners/__init__.py +0 -0
  15. griptape_nodes/bootstrap/workflow_runners/bootstrap_workflow_runner.py +0 -0
  16. griptape_nodes/bootstrap/workflow_runners/local_workflow_runner.py +0 -0
  17. griptape_nodes/bootstrap/workflow_runners/subprocess_workflow_runner.py +6 -2
  18. griptape_nodes/bootstrap/workflow_runners/workflow_runner.py +0 -0
  19. griptape_nodes/drivers/__init__.py +0 -0
  20. griptape_nodes/drivers/storage/__init__.py +0 -0
  21. griptape_nodes/drivers/storage/base_storage_driver.py +0 -0
  22. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +0 -0
  23. griptape_nodes/drivers/storage/local_storage_driver.py +5 -3
  24. griptape_nodes/drivers/storage/storage_backend.py +0 -0
  25. griptape_nodes/exe_types/__init__.py +0 -0
  26. griptape_nodes/exe_types/connections.py +0 -0
  27. griptape_nodes/exe_types/core_types.py +0 -0
  28. griptape_nodes/exe_types/flow.py +68 -368
  29. griptape_nodes/exe_types/node_types.py +17 -1
  30. griptape_nodes/exe_types/type_validator.py +0 -0
  31. griptape_nodes/machines/__init__.py +0 -0
  32. griptape_nodes/machines/control_flow.py +52 -20
  33. griptape_nodes/machines/fsm.py +16 -2
  34. griptape_nodes/machines/node_resolution.py +16 -14
  35. griptape_nodes/mcp_server/__init__.py +1 -0
  36. griptape_nodes/mcp_server/server.py +126 -0
  37. griptape_nodes/mcp_server/ws_request_manager.py +268 -0
  38. griptape_nodes/node_library/__init__.py +0 -0
  39. griptape_nodes/node_library/advanced_node_library.py +0 -0
  40. griptape_nodes/node_library/library_registry.py +0 -0
  41. griptape_nodes/node_library/workflow_registry.py +2 -2
  42. griptape_nodes/py.typed +0 -0
  43. griptape_nodes/retained_mode/__init__.py +0 -0
  44. griptape_nodes/retained_mode/events/__init__.py +0 -0
  45. griptape_nodes/retained_mode/events/agent_events.py +70 -8
  46. griptape_nodes/retained_mode/events/app_events.py +137 -12
  47. griptape_nodes/retained_mode/events/arbitrary_python_events.py +23 -0
  48. griptape_nodes/retained_mode/events/base_events.py +13 -31
  49. griptape_nodes/retained_mode/events/config_events.py +87 -11
  50. griptape_nodes/retained_mode/events/connection_events.py +56 -5
  51. griptape_nodes/retained_mode/events/context_events.py +27 -4
  52. griptape_nodes/retained_mode/events/execution_events.py +99 -14
  53. griptape_nodes/retained_mode/events/flow_events.py +165 -7
  54. griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
  55. griptape_nodes/retained_mode/events/library_events.py +195 -17
  56. griptape_nodes/retained_mode/events/logger_events.py +11 -0
  57. griptape_nodes/retained_mode/events/node_events.py +242 -22
  58. griptape_nodes/retained_mode/events/object_events.py +40 -4
  59. griptape_nodes/retained_mode/events/os_events.py +116 -3
  60. griptape_nodes/retained_mode/events/parameter_events.py +212 -8
  61. griptape_nodes/retained_mode/events/payload_registry.py +0 -0
  62. griptape_nodes/retained_mode/events/secrets_events.py +59 -7
  63. griptape_nodes/retained_mode/events/static_file_events.py +57 -4
  64. griptape_nodes/retained_mode/events/validation_events.py +39 -4
  65. griptape_nodes/retained_mode/events/workflow_events.py +188 -17
  66. griptape_nodes/retained_mode/griptape_nodes.py +89 -363
  67. griptape_nodes/retained_mode/managers/__init__.py +0 -0
  68. griptape_nodes/retained_mode/managers/agent_manager.py +49 -23
  69. griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
  70. griptape_nodes/retained_mode/managers/config_manager.py +0 -0
  71. griptape_nodes/retained_mode/managers/context_manager.py +0 -0
  72. griptape_nodes/retained_mode/managers/engine_identity_manager.py +146 -0
  73. griptape_nodes/retained_mode/managers/event_manager.py +14 -2
  74. griptape_nodes/retained_mode/managers/flow_manager.py +751 -64
  75. griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +45 -0
  76. griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +191 -0
  77. griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +346 -0
  78. griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +439 -0
  79. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +17 -0
  80. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +82 -0
  81. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +116 -0
  82. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +352 -0
  83. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +104 -0
  84. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +155 -0
  85. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +18 -0
  86. griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +12 -0
  87. griptape_nodes/retained_mode/managers/library_manager.py +255 -40
  88. griptape_nodes/retained_mode/managers/node_manager.py +120 -103
  89. griptape_nodes/retained_mode/managers/object_manager.py +11 -3
  90. griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
  91. griptape_nodes/retained_mode/managers/os_manager.py +582 -8
  92. griptape_nodes/retained_mode/managers/secrets_manager.py +4 -0
  93. griptape_nodes/retained_mode/managers/session_manager.py +328 -0
  94. griptape_nodes/retained_mode/managers/settings.py +7 -0
  95. griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
  96. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +2 -2
  97. griptape_nodes/retained_mode/managers/workflow_manager.py +722 -456
  98. griptape_nodes/retained_mode/retained_mode.py +44 -0
  99. griptape_nodes/retained_mode/utils/__init__.py +0 -0
  100. griptape_nodes/retained_mode/utils/engine_identity.py +141 -27
  101. griptape_nodes/retained_mode/utils/name_generator.py +0 -0
  102. griptape_nodes/traits/__init__.py +0 -0
  103. griptape_nodes/traits/add_param_button.py +0 -0
  104. griptape_nodes/traits/button.py +0 -0
  105. griptape_nodes/traits/clamp.py +0 -0
  106. griptape_nodes/traits/compare.py +0 -0
  107. griptape_nodes/traits/compare_images.py +0 -0
  108. griptape_nodes/traits/file_system_picker.py +127 -0
  109. griptape_nodes/traits/minmax.py +0 -0
  110. griptape_nodes/traits/options.py +0 -0
  111. griptape_nodes/traits/slider.py +0 -0
  112. griptape_nodes/traits/trait_registry.py +0 -0
  113. griptape_nodes/traits/traits.json +0 -0
  114. griptape_nodes/updater/__init__.py +2 -2
  115. griptape_nodes/updater/__main__.py +0 -0
  116. griptape_nodes/utils/__init__.py +0 -0
  117. griptape_nodes/utils/dict_utils.py +0 -0
  118. griptape_nodes/utils/image_preview.py +128 -0
  119. griptape_nodes/utils/metaclasses.py +0 -0
  120. griptape_nodes/version_compatibility/__init__.py +0 -0
  121. griptape_nodes/version_compatibility/versions/__init__.py +0 -0
  122. griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
  123. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +5 -5
  124. griptape_nodes-0.43.0.dist-info/METADATA +90 -0
  125. griptape_nodes-0.43.0.dist-info/RECORD +129 -0
  126. griptape_nodes-0.43.0.dist-info/WHEEL +4 -0
  127. {griptape_nodes-0.41.0.dist-info → griptape_nodes-0.43.0.dist-info}/entry_points.txt +1 -0
  128. griptape_nodes/app/app_sessions.py +0 -458
  129. griptape_nodes/retained_mode/utils/session_persistence.py +0 -105
  130. griptape_nodes-0.41.0.dist-info/METADATA +0 -78
  131. griptape_nodes-0.41.0.dist-info/RECORD +0 -112
  132. griptape_nodes-0.41.0.dist-info/WHEEL +0 -4
  133. griptape_nodes-0.41.0.dist-info/licenses/LICENSE +0 -201
@@ -32,8 +32,10 @@ from griptape_nodes.retained_mode.events.execution_events import (
32
32
  from griptape_nodes.retained_mode.events.flow_events import (
33
33
  CreateFlowRequest,
34
34
  DeleteFlowRequest,
35
+ GetFlowMetadataRequest,
35
36
  ListFlowsInFlowRequest,
36
37
  ListNodesInFlowRequest,
38
+ SetFlowMetadataRequest,
37
39
  )
38
40
  from griptape_nodes.retained_mode.events.library_events import (
39
41
  GetNodeMetadataFromLibraryRequest,
@@ -1247,6 +1249,48 @@ class RetainedMode:
1247
1249
  request = GetFlowStateRequest(flow_name=flow_name)
1248
1250
  return GriptapeNodes().handle_request(request)
1249
1251
 
1252
+ @classmethod
1253
+ def get_metadata_for_flow(cls, flow_name: str) -> ResultPayload:
1254
+ """Retrieves metadata associated with a flow.
1255
+
1256
+ Flow metadata can include UI position, display name, tags, and other custom properties.
1257
+
1258
+ Args:
1259
+ flow_name (str): Name of the flow to get metadata for.
1260
+
1261
+ Returns:
1262
+ ResultPayload: Contains the flow's metadata.
1263
+
1264
+ Example:
1265
+ # Get flow metadata
1266
+ result = cmd.get_metadata_for_flow("my_flow")
1267
+ """
1268
+ request = GetFlowMetadataRequest(flow_name=flow_name)
1269
+ result = GriptapeNodes().handle_request(request)
1270
+ return result
1271
+
1272
+ @classmethod
1273
+ def set_metadata_for_flow(cls, flow_name: str, metadata: dict[Any, Any]) -> ResultPayload:
1274
+ """Sets metadata for a flow.
1275
+
1276
+ Args:
1277
+ flow_name (str): Name of the flow to set metadata for.
1278
+ metadata (dict): Dictionary containing the metadata to set.
1279
+
1280
+ Returns:
1281
+ ResultPayload: Contains the result of the metadata update operation.
1282
+
1283
+ Example:
1284
+ # Set flow position
1285
+ metadata = {
1286
+ "position": {"x": 100, "y": 200}
1287
+ }
1288
+ result = cmd.set_metadata_for_flow("my_flow", metadata)
1289
+ """
1290
+ request = SetFlowMetadataRequest(flow_name=flow_name, metadata=metadata)
1291
+ result = GriptapeNodes().handle_request(request)
1292
+ return result
1293
+
1250
1294
  # ARBITRARY PYTHON EXECUTION
1251
1295
  @classmethod
1252
1296
  def run_arbitrary_python(cls, python_str: str) -> ResultPayload:
File without changes
@@ -1,6 +1,7 @@
1
- """Manages engine identity for a single engine per machine.
1
+ """Manages engine identity for multiple engines per machine.
2
2
 
3
3
  Handles engine ID, name storage, and generation for unique engine identification.
4
+ Supports multiple engines with selection via GTN_ENGINE_ID environment variable.
4
5
  """
5
6
 
6
7
  import json
@@ -15,9 +16,9 @@ from .name_generator import generate_engine_name
15
16
 
16
17
 
17
18
  class EngineIdentity:
18
- """Manages engine identity for a single engine per machine."""
19
+ """Manages engine identity for multiple engines per machine."""
19
20
 
20
- _ENGINE_DATA_FILE = "engine.json"
21
+ _ENGINE_DATA_FILE = "engines.json"
21
22
 
22
23
  @classmethod
23
24
  def _get_engine_data_dir(cls) -> Path:
@@ -30,11 +31,11 @@ class EngineIdentity:
30
31
  return cls._get_engine_data_dir() / cls._ENGINE_DATA_FILE
31
32
 
32
33
  @classmethod
33
- def _load_engine_data(cls) -> dict:
34
- """Load engine data from storage.
34
+ def _load_engines_data(cls) -> dict:
35
+ """Load engines data from storage.
35
36
 
36
37
  Returns:
37
- dict: Engine data including id, name, timestamps
38
+ dict: Engines data structure with engines array and default_engine_id
38
39
  """
39
40
  engine_data_file = cls._get_engine_data_file()
40
41
 
@@ -42,48 +43,124 @@ class EngineIdentity:
42
43
  try:
43
44
  with engine_data_file.open("r") as f:
44
45
  data = json.load(f)
45
- if isinstance(data, dict):
46
+ if isinstance(data, dict) and "engines" in data:
46
47
  return data
47
48
  except (json.JSONDecodeError, OSError):
48
- # If file is corrupted, return empty dict
49
49
  pass
50
50
 
51
- return {}
51
+ return {"engines": [], "default_engine_id": None}
52
52
 
53
53
  @classmethod
54
- def _save_engine_data(cls, engine_data: dict) -> None:
55
- """Save engine data to storage.
54
+ def _save_engines_data(cls, engines_data: dict) -> None:
55
+ """Save engines data to storage.
56
56
 
57
57
  Args:
58
- engine_data: Engine data to save
58
+ engines_data: Engines data structure to save
59
59
  """
60
60
  engine_data_dir = cls._get_engine_data_dir()
61
61
  engine_data_dir.mkdir(parents=True, exist_ok=True)
62
62
 
63
63
  engine_data_file = cls._get_engine_data_file()
64
64
  with engine_data_file.open("w") as f:
65
- json.dump(engine_data, f, indent=2)
65
+ json.dump(engines_data, f, indent=2)
66
+
67
+ @classmethod
68
+ def _get_selected_engine_id(cls) -> str | None:
69
+ """Get the selected engine ID from environment variable or default.
70
+
71
+ Returns:
72
+ str | None: The selected engine ID or None if not specified
73
+ """
74
+ return os.getenv("GTN_ENGINE_ID")
75
+
76
+ @classmethod
77
+ def _find_engine_by_id(cls, engines_data: dict, engine_id: str) -> dict | None:
78
+ """Find an engine by ID in the engines data.
79
+
80
+ Args:
81
+ engines_data: The engines data structure
82
+ engine_id: The engine ID to find
83
+
84
+ Returns:
85
+ dict | None: The engine data if found, None otherwise
86
+ """
87
+ for engine in engines_data.get("engines", []):
88
+ if engine.get("id") == engine_id:
89
+ return engine
90
+ return None
66
91
 
67
92
  @classmethod
68
93
  def get_engine_data(cls) -> dict:
69
- """Get the engine data, creating default if it doesn't exist.
94
+ """Get the current engine data, creating default if it doesn't exist.
70
95
 
71
96
  Returns:
72
- dict: The engine data
97
+ dict: The current engine data
73
98
  """
74
- engine_data = cls._load_engine_data()
99
+ engines_data = cls._load_engines_data()
100
+
101
+ # Determine which engine to use
102
+ selected_engine_id = cls._get_selected_engine_id()
75
103
 
76
- if not engine_data or "id" not in engine_data:
77
- # Create default engine data
104
+ if selected_engine_id:
105
+ # Use specified engine ID
106
+ engine_data = cls._find_engine_by_id(engines_data, selected_engine_id)
107
+ if engine_data:
108
+ return engine_data
109
+ # If specified engine not found, create it
110
+ engine_data = {
111
+ "id": selected_engine_id,
112
+ "name": generate_engine_name(),
113
+ "created_at": datetime.now(tz=UTC).isoformat(),
114
+ }
115
+ else:
116
+ # Use default engine (first one) or create new one
117
+ if engines_data.get("engines"):
118
+ default_id = engines_data.get("default_engine_id")
119
+ if default_id:
120
+ engine_data = cls._find_engine_by_id(engines_data, default_id)
121
+ if engine_data:
122
+ return engine_data
123
+ # Fall back to first engine
124
+ return engines_data["engines"][0]
125
+
126
+ # Create new engine
78
127
  engine_data = {
79
- "id": os.getenv("GTN_ENGINE_ID") or str(uuid.uuid4()),
128
+ "id": str(uuid.uuid4()),
80
129
  "name": generate_engine_name(),
81
130
  "created_at": datetime.now(tz=UTC).isoformat(),
82
131
  }
83
- cls._save_engine_data(engine_data)
84
132
 
133
+ # Add or update engine in the data structure
134
+ cls._add_or_update_engine(engine_data)
85
135
  return engine_data
86
136
 
137
+ @classmethod
138
+ def _add_or_update_engine(cls, engine_data: dict) -> None:
139
+ """Add or update an engine in the engines data structure.
140
+
141
+ Args:
142
+ engine_data: The engine data to add or update
143
+ """
144
+ engines_data = cls._load_engines_data()
145
+
146
+ # Find existing engine
147
+ engine_id = engine_data["id"]
148
+ existing_engine = cls._find_engine_by_id(engines_data, engine_id)
149
+
150
+ if existing_engine:
151
+ # Update existing engine
152
+ existing_engine.update(engine_data)
153
+ existing_engine["updated_at"] = datetime.now(tz=UTC).isoformat()
154
+ else:
155
+ # Add new engine
156
+ engines_data.setdefault("engines", []).append(engine_data)
157
+
158
+ # Set as default if it's the first engine
159
+ if not engines_data.get("default_engine_id") and len(engines_data["engines"]) == 1:
160
+ engines_data["default_engine_id"] = engine_id
161
+
162
+ cls._save_engines_data(engines_data)
163
+
87
164
  @classmethod
88
165
  def get_engine_id(cls) -> str:
89
166
  """Get the engine ID.
@@ -106,20 +183,57 @@ class EngineIdentity:
106
183
 
107
184
  @classmethod
108
185
  def set_engine_name(cls, engine_name: str) -> None:
109
- """Set and persist the engine name.
186
+ """Set and persist the current engine name.
110
187
 
111
188
  Args:
112
189
  engine_name: The new engine name to set
113
190
  """
114
- engine_data = cls._load_engine_data()
115
-
116
- # Ensure we have basic engine data
117
- if not engine_data or "id" not in engine_data:
118
- engine_data = cls.get_engine_data()
191
+ # Get current engine data
192
+ engine_data = cls.get_engine_data()
119
193
 
194
+ # Update the name
120
195
  engine_data["name"] = engine_name
121
196
  engine_data["updated_at"] = datetime.now(tz=UTC).isoformat()
122
- cls._save_engine_data(engine_data)
197
+
198
+ # Save updated engine data
199
+ cls._add_or_update_engine(engine_data)
200
+
201
+ @classmethod
202
+ def get_all_engines(cls) -> list[dict]:
203
+ """Get all registered engines.
204
+
205
+ Returns:
206
+ list[dict]: List of all engine data
207
+ """
208
+ engines_data = cls._load_engines_data()
209
+ return engines_data.get("engines", [])
210
+
211
+ @classmethod
212
+ def get_default_engine_id(cls) -> str | None:
213
+ """Get the default engine ID.
214
+
215
+ Returns:
216
+ str | None: The default engine ID or None if not set
217
+ """
218
+ engines_data = cls._load_engines_data()
219
+ return engines_data.get("default_engine_id")
220
+
221
+ @classmethod
222
+ def set_default_engine_id(cls, engine_id: str) -> None:
223
+ """Set the default engine ID.
224
+
225
+ Args:
226
+ engine_id: The engine ID to set as default
227
+ """
228
+ engines_data = cls._load_engines_data()
229
+
230
+ # Verify the engine exists
231
+ if cls._find_engine_by_id(engines_data, engine_id):
232
+ engines_data["default_engine_id"] = engine_id
233
+ cls._save_engines_data(engines_data)
234
+ else:
235
+ msg = f"Engine with ID {engine_id} not found"
236
+ raise ValueError(msg)
123
237
 
124
238
  @classmethod
125
239
  def get_engine_data_file_path(cls) -> Path:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,127 @@
1
+ from collections.abc import Callable
2
+ from dataclasses import dataclass, field
3
+ from typing import Any
4
+
5
+ from griptape_nodes.exe_types.core_types import Parameter, Trait
6
+
7
+
8
+ @dataclass(eq=False)
9
+ class FileSystemPicker(Trait):
10
+ allow_files: bool = False
11
+ allow_directories: bool = True
12
+ multiple: bool = False
13
+ file_types: list[str] = field(default_factory=list)
14
+ file_extensions: list[str] = field(default_factory=list)
15
+ exclude_patterns: list[str] = field(default_factory=list)
16
+ include_patterns: list[str] = field(default_factory=list)
17
+ max_file_size: int | None = None
18
+ min_file_size: int | None = None
19
+ workspace_only: bool = True
20
+ initial_path: str | None = None
21
+ element_id: str = field(default_factory=lambda: "FileSystemPicker")
22
+
23
+ def __init__( # noqa: PLR0913
24
+ self,
25
+ *,
26
+ allow_files: bool = False,
27
+ allow_directories: bool = True,
28
+ multiple: bool = False,
29
+ file_types: list[str] | None = None,
30
+ file_extensions: list[str] | None = None,
31
+ exclude_patterns: list[str] | None = None,
32
+ include_patterns: list[str] | None = None,
33
+ max_file_size: int | None = None,
34
+ min_file_size: int | None = None,
35
+ workspace_only: bool = True,
36
+ initial_path: str | None = None,
37
+ ) -> None:
38
+ super().__init__()
39
+ self.allow_files = allow_files
40
+ self.allow_directories = allow_directories
41
+ self.multiple = multiple
42
+ self.file_types = file_types or []
43
+ self.file_extensions = file_extensions or []
44
+ self.exclude_patterns = exclude_patterns or []
45
+ self.include_patterns = include_patterns or []
46
+ self.max_file_size = max_file_size
47
+ self.min_file_size = min_file_size
48
+ self.workspace_only = workspace_only
49
+ self.initial_path = initial_path
50
+
51
+ @classmethod
52
+ def get_trait_keys(cls) -> list[str]:
53
+ return ["fileSystemPicker", "file_picker", "folder_picker"]
54
+
55
+ def ui_options_for_trait(self) -> dict[str, Any]:
56
+ """Generate the fileSystemPicker UI options dictionary."""
57
+ options: dict[str, Any] = {
58
+ "allowFiles": self.allow_files,
59
+ "allowDirectories": self.allow_directories,
60
+ "multiple": self.multiple,
61
+ "workspaceOnly": self.workspace_only,
62
+ }
63
+
64
+ # Add file types/extensions
65
+ if self.file_types:
66
+ options["fileTypes"] = self.file_types
67
+ elif self.file_extensions:
68
+ options["fileExtensions"] = self.file_extensions
69
+
70
+ # Add patterns
71
+ if self.exclude_patterns:
72
+ options["excludePatterns"] = self.exclude_patterns
73
+ if self.include_patterns:
74
+ options["includePatterns"] = self.include_patterns
75
+
76
+ # Add size limits
77
+ if self.max_file_size is not None:
78
+ options["maxFileSize"] = self.max_file_size
79
+ if self.min_file_size is not None:
80
+ options["minFileSize"] = self.min_file_size
81
+
82
+ # Add initial path
83
+ if self.initial_path:
84
+ options["initialPath"] = self.initial_path
85
+
86
+ return {"fileSystemPicker": options}
87
+
88
+ def validators_for_trait(self) -> list[Callable[[Parameter, Any], Any]]:
89
+ """Validate file system picker configuration."""
90
+
91
+ def validate(param: Parameter, value: Any) -> None: # noqa: ARG001
92
+ # Validate that at least one selection type is enabled
93
+ if not self.allow_files and not self.allow_directories:
94
+ msg = "At least one of allow_files or allow_directories must be True"
95
+ raise ValueError(msg)
96
+
97
+ # Validate file size limits
98
+ if (
99
+ self.max_file_size is not None
100
+ and self.min_file_size is not None
101
+ and self.max_file_size < self.min_file_size
102
+ ):
103
+ msg = "max_file_size cannot be less than min_file_size"
104
+ raise ValueError(msg)
105
+
106
+ # Validate that file types/extensions are valid
107
+ all_file_types = self.file_types + self.file_extensions
108
+ for file_type in all_file_types:
109
+ if not file_type.startswith("."):
110
+ msg = f"File type '{file_type}' must start with a dot (e.g., '.py')"
111
+ raise ValueError(msg)
112
+
113
+ return [validate]
114
+
115
+ def converters_for_trait(self) -> list[Callable]:
116
+ """Convert file system picker values if needed."""
117
+
118
+ def converter(value: Any) -> Any:
119
+ # If value is a string and we expect a list, convert it
120
+ if isinstance(value, str) and self.multiple:
121
+ return [value] if value else []
122
+ return value
123
+
124
+ return [converter]
125
+
126
+
127
+ # These Traits get added to a list on the parameter. When they are added they apply their functions to the parameter.
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -38,7 +38,7 @@ def _download_and_run_installer() -> None:
38
38
  """Runs the update commands for the engine."""
39
39
  console.print("[bold green]Updating self...[/bold green]")
40
40
  try:
41
- subprocess.run( # noqa: S603
41
+ subprocess.run(
42
42
  ["uv", "tool", "upgrade", "griptape-nodes"], # noqa: S607
43
43
  text=True,
44
44
  capture_output=True,
@@ -57,7 +57,7 @@ def _sync_libraries() -> None:
57
57
  """Syncs the libraries for the engine."""
58
58
  console.print("[bold green]Syncing libraries...[/bold green]")
59
59
  try:
60
- subprocess.run( # noqa: S603
60
+ subprocess.run(
61
61
  ["griptape-nodes", "libraries", "sync"], # noqa: S607
62
62
  text=True,
63
63
  capture_output=True,
File without changes
File without changes
File without changes
@@ -0,0 +1,128 @@
1
+ """Image preview utilities for generating thumbnails and previews."""
2
+
3
+ import base64
4
+ import io
5
+ from pathlib import Path
6
+
7
+ from PIL import Image
8
+
9
+ from griptape_nodes.retained_mode.griptape_nodes import logger
10
+
11
+
12
+ def create_image_preview(
13
+ image_path: Path, max_width: int = 512, max_height: int = 512, quality: int = 85, image_format: str = "WEBP"
14
+ ) -> str | None:
15
+ """Create a small preview image from a file path.
16
+
17
+ Args:
18
+ image_path: Path to the image file
19
+ max_width: Maximum width for the preview
20
+ max_height: Maximum height for the preview
21
+ quality: WebP quality (1-100)
22
+ image_format: Output format (WEBP, JPEG, PNG, etc.)
23
+
24
+ Returns:
25
+ Base64 encoded data URL of the preview, or None if failed
26
+ """
27
+ try:
28
+ # Open and resize the image
29
+ with Image.open(image_path) as img:
30
+ # Convert to RGB if necessary (for WebP/JPEG output)
31
+ if image_format.upper() in ("WEBP", "JPEG") and img.mode in ("RGBA", "LA", "P"):
32
+ converted_img = img.convert("RGB")
33
+ else:
34
+ converted_img = img
35
+
36
+ # Calculate new size maintaining aspect ratio
37
+ converted_img.thumbnail((max_width, max_height), Image.Resampling.LANCZOS)
38
+
39
+ # Save to bytes buffer
40
+ buffer = io.BytesIO()
41
+ converted_img.save(buffer, format=image_format, quality=quality, optimize=True)
42
+ buffer.seek(0)
43
+
44
+ # Convert to base64
45
+ image_bytes = buffer.getvalue()
46
+ base64_data = base64.b64encode(image_bytes).decode("utf-8")
47
+
48
+ # Create data URL
49
+ mime_type = f"image/{image_format.lower()}"
50
+ data_url = f"data:{mime_type};base64,{base64_data}"
51
+
52
+ logger.debug(f"Created preview for {image_path}: {img.size} -> {len(image_bytes)} bytes")
53
+ return data_url
54
+
55
+ except Exception as e:
56
+ logger.warning(f"Failed to create preview for {image_path}: {e}")
57
+ return None
58
+
59
+
60
+ def create_image_preview_from_bytes(
61
+ image_bytes: bytes, max_width: int = 512, max_height: int = 512, quality: int = 85, image_format: str = "WEBP"
62
+ ) -> str | None:
63
+ """Create a small preview image from bytes.
64
+
65
+ Args:
66
+ image_bytes: Raw image bytes
67
+ max_width: Maximum width for the preview
68
+ max_height: Maximum height for the preview
69
+ quality: WebP quality (1-100)
70
+ image_format: Output format (WEBP, JPEG, PNG, etc.)
71
+
72
+ Returns:
73
+ Base64 encoded data URL of the preview, or None if failed
74
+ """
75
+ try:
76
+ # Open image from bytes
77
+ with Image.open(io.BytesIO(image_bytes)) as img:
78
+ # Convert to RGB if necessary (for WebP/JPEG output)
79
+ if image_format.upper() in ("WEBP", "JPEG") and img.mode in ("RGBA", "LA", "P"):
80
+ converted_img = img.convert("RGB")
81
+ else:
82
+ converted_img = img
83
+
84
+ # Calculate new size maintaining aspect ratio
85
+ converted_img.thumbnail((max_width, max_height), Image.Resampling.LANCZOS)
86
+
87
+ # Save to bytes buffer
88
+ buffer = io.BytesIO()
89
+ converted_img.save(buffer, format=image_format, quality=quality, optimize=True)
90
+ buffer.seek(0)
91
+
92
+ # Convert to base64
93
+ preview_bytes = buffer.getvalue()
94
+ base64_data = base64.b64encode(preview_bytes).decode("utf-8")
95
+
96
+ # Create data URL
97
+ mime_type = f"image/{image_format.lower()}"
98
+ data_url = f"data:{mime_type};base64,{base64_data}"
99
+
100
+ logger.debug(f"Created preview from bytes: {len(image_bytes)} -> {len(preview_bytes)} bytes")
101
+ return data_url
102
+
103
+ except Exception as e:
104
+ logger.warning(f"Failed to create preview from bytes: {e}")
105
+ return None
106
+
107
+
108
+ def get_image_info(image_path: Path) -> dict | None:
109
+ """Get basic information about an image file.
110
+
111
+ Args:
112
+ image_path: Path to the image file
113
+
114
+ Returns:
115
+ Dictionary with image info (width, height, format, mode), or None if failed
116
+ """
117
+ try:
118
+ with Image.open(image_path) as img:
119
+ return {
120
+ "width": img.width,
121
+ "height": img.height,
122
+ "format": img.format,
123
+ "mode": img.mode,
124
+ "size_bytes": image_path.stat().st_size,
125
+ }
126
+ except Exception as e:
127
+ logger.warning(f"Failed to get image info for {image_path}: {e}")
128
+ return None
File without changes
File without changes
File without changes
@@ -7,7 +7,7 @@ from griptape_nodes.retained_mode.events.app_events import (
7
7
  GetEngineVersionResultSuccess,
8
8
  )
9
9
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes, Version
10
- from griptape_nodes.retained_mode.managers.library_manager import LibraryManager
10
+ from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
11
11
  from griptape_nodes.retained_mode.managers.version_compatibility_manager import (
12
12
  LibraryVersionCompatibilityCheck,
13
13
  LibraryVersionCompatibilityIssue,
@@ -47,13 +47,13 @@ class ModifiedParametersSetRemovalCheck(LibraryVersionCompatibilityCheck):
47
47
  message=f"This library (built for engine version {library_version_str}) is incompatible with Griptape Nodes 0.39+. "
48
48
  "The 'modified_parameters_set' parameter has been removed from BaseNode methods: 'after_incoming_connection', 'after_outgoing_connection', 'after_incoming_connection_removed', 'after_outgoing_connection_removed', 'before_value_set', and 'after_value_set'. "
49
49
  "If this library overrides any of these methods, it will not load or function properly. Please update to a newer version of this library or contact the library author immediately.",
50
- severity=LibraryManager.LibraryStatus.UNUSABLE,
50
+ severity=LibraryStatus.UNUSABLE,
51
51
  ),
52
52
  LibraryVersionCompatibilityIssue(
53
53
  message=f"This library (built for engine version {library_version_str}) is incompatible with Griptape Nodes 0.39+."
54
54
  "The 'ui_options' field has been modified on all Elements. In order to function properly, all nodes must update ui_options by setting its value to a new dictionary. Updating ui_options by accessing the private field _ui_options will no longer create UI updates in the editor."
55
55
  "If this library accesses the private _ui_options field, it will not update the editor properly. Please update to a newer version of this library or contact the library author immediately.",
56
- severity=LibraryManager.LibraryStatus.UNUSABLE,
56
+ severity=LibraryStatus.UNUSABLE,
57
57
  ),
58
58
  ]
59
59
  if current_engine_version >= Version(0, 38, 0):
@@ -63,13 +63,13 @@ class ModifiedParametersSetRemovalCheck(LibraryVersionCompatibilityCheck):
63
63
  message=f"WARNING: The 'modified_parameters_set' parameter will be removed in Griptape Nodes 0.39 from BaseNode methods: 'after_incoming_connection', 'after_outgoing_connection', 'after_incoming_connection_removed', 'after_outgoing_connection_removed', 'before_value_set', and 'after_value_set'. "
64
64
  f"This library (built for engine version {library_version_str}) must be updated before the 0.39 release. "
65
65
  "If this library overrides any of these methods, it will fail to load in 0.39. If not, no action is necessary. Please contact the library author to confirm whether this library is impacted.",
66
- severity=LibraryManager.LibraryStatus.FLAWED,
66
+ severity=LibraryStatus.FLAWED,
67
67
  ),
68
68
  LibraryVersionCompatibilityIssue(
69
69
  message="WARNING: The 'ui_options' field has been modified in Griptape Nodes 0.38 on all BaseNodeElements."
70
70
  "In order to function properly, all nodes must update ui_options by setting its value to a new dictionary. Updating ui_options by accessing the private field _ui_options will no longer create UI updates in the editor."
71
71
  "If this library accesses the private _ui_options field, it will not update the editor properly. Please update to a newer version of this library or contact the library author immediately.",
72
- severity=LibraryManager.LibraryStatus.FLAWED,
72
+ severity=LibraryStatus.FLAWED,
73
73
  ),
74
74
  ]
75
75