griptape-nodes 0.70.1__py3-none-any.whl → 0.72.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.
- griptape_nodes/api_client/client.py +8 -5
- griptape_nodes/app/app.py +4 -0
- griptape_nodes/bootstrap/utils/python_subprocess_executor.py +48 -9
- griptape_nodes/bootstrap/utils/subprocess_websocket_base.py +88 -0
- griptape_nodes/bootstrap/utils/subprocess_websocket_listener.py +126 -0
- griptape_nodes/bootstrap/utils/subprocess_websocket_sender.py +121 -0
- griptape_nodes/bootstrap/workflow_executors/local_session_workflow_executor.py +17 -170
- griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +10 -1
- griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +13 -117
- griptape_nodes/bootstrap/workflow_executors/utils/subprocess_script.py +4 -0
- griptape_nodes/bootstrap/workflow_publishers/local_session_workflow_publisher.py +206 -0
- griptape_nodes/bootstrap/workflow_publishers/subprocess_workflow_publisher.py +22 -3
- griptape_nodes/bootstrap/workflow_publishers/utils/subprocess_script.py +49 -25
- griptape_nodes/common/node_executor.py +61 -14
- griptape_nodes/drivers/image_metadata/__init__.py +21 -0
- griptape_nodes/drivers/image_metadata/base_image_metadata_driver.py +63 -0
- griptape_nodes/drivers/image_metadata/exif_metadata_driver.py +218 -0
- griptape_nodes/drivers/image_metadata/image_metadata_driver_registry.py +55 -0
- griptape_nodes/drivers/image_metadata/png_metadata_driver.py +71 -0
- griptape_nodes/drivers/storage/base_storage_driver.py +32 -0
- griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +384 -10
- griptape_nodes/drivers/storage/local_storage_driver.py +65 -4
- griptape_nodes/drivers/thread_storage/local_thread_storage_driver.py +1 -0
- griptape_nodes/exe_types/base_iterative_nodes.py +1 -1
- griptape_nodes/exe_types/node_groups/base_node_group.py +3 -0
- griptape_nodes/exe_types/node_groups/subflow_node_group.py +18 -0
- griptape_nodes/exe_types/node_types.py +13 -0
- griptape_nodes/exe_types/param_components/log_parameter.py +4 -4
- griptape_nodes/exe_types/param_components/subflow_execution_component.py +329 -0
- griptape_nodes/exe_types/param_types/parameter_audio.py +17 -2
- griptape_nodes/exe_types/param_types/parameter_float.py +4 -4
- griptape_nodes/exe_types/param_types/parameter_image.py +14 -1
- griptape_nodes/exe_types/param_types/parameter_int.py +4 -4
- griptape_nodes/exe_types/param_types/parameter_number.py +12 -14
- griptape_nodes/exe_types/param_types/parameter_three_d.py +14 -1
- griptape_nodes/exe_types/param_types/parameter_video.py +17 -2
- griptape_nodes/node_library/workflow_registry.py +5 -8
- griptape_nodes/retained_mode/events/app_events.py +1 -0
- griptape_nodes/retained_mode/events/base_events.py +42 -26
- griptape_nodes/retained_mode/events/flow_events.py +67 -0
- griptape_nodes/retained_mode/events/library_events.py +1 -1
- griptape_nodes/retained_mode/events/node_events.py +1 -0
- griptape_nodes/retained_mode/events/os_events.py +22 -0
- griptape_nodes/retained_mode/events/static_file_events.py +28 -4
- griptape_nodes/retained_mode/managers/flow_manager.py +134 -0
- griptape_nodes/retained_mode/managers/image_metadata_injector.py +339 -0
- griptape_nodes/retained_mode/managers/library_manager.py +71 -41
- griptape_nodes/retained_mode/managers/model_manager.py +1 -0
- griptape_nodes/retained_mode/managers/node_manager.py +8 -5
- griptape_nodes/retained_mode/managers/os_manager.py +270 -33
- griptape_nodes/retained_mode/managers/project_manager.py +3 -7
- griptape_nodes/retained_mode/managers/session_manager.py +1 -0
- griptape_nodes/retained_mode/managers/settings.py +5 -0
- griptape_nodes/retained_mode/managers/static_files_manager.py +83 -17
- griptape_nodes/retained_mode/managers/workflow_manager.py +71 -41
- griptape_nodes/servers/static.py +31 -0
- griptape_nodes/utils/__init__.py +9 -1
- griptape_nodes/utils/artifact_normalization.py +245 -0
- griptape_nodes/utils/file_utils.py +13 -13
- griptape_nodes/utils/http_file_patch.py +613 -0
- griptape_nodes/utils/image_preview.py +27 -0
- griptape_nodes/utils/path_utils.py +58 -0
- griptape_nodes/utils/url_utils.py +106 -0
- {griptape_nodes-0.70.1.dist-info → griptape_nodes-0.72.0.dist-info}/METADATA +2 -1
- {griptape_nodes-0.70.1.dist-info → griptape_nodes-0.72.0.dist-info}/RECORD +67 -52
- {griptape_nodes-0.70.1.dist-info → griptape_nodes-0.72.0.dist-info}/WHEEL +1 -1
- {griptape_nodes-0.70.1.dist-info → griptape_nodes-0.72.0.dist-info}/entry_points.txt +0 -0
|
@@ -63,6 +63,7 @@ from griptape_nodes.retained_mode.events.project_events import (
|
|
|
63
63
|
SetCurrentProjectResultSuccess,
|
|
64
64
|
)
|
|
65
65
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
66
|
+
from griptape_nodes.utils.path_utils import resolve_workspace_path
|
|
66
67
|
|
|
67
68
|
if TYPE_CHECKING:
|
|
68
69
|
from griptape_nodes.retained_mode.managers.config_manager import ConfigManager
|
|
@@ -522,10 +523,7 @@ class ProjectManager:
|
|
|
522
523
|
resolved_path = Path(resolved_string)
|
|
523
524
|
|
|
524
525
|
# Make absolute path by resolving against project base directory
|
|
525
|
-
|
|
526
|
-
absolute_path = resolved_path
|
|
527
|
-
else:
|
|
528
|
-
absolute_path = project_info.project_base_dir / resolved_path
|
|
526
|
+
absolute_path = resolve_workspace_path(resolved_path, project_info.project_base_dir)
|
|
529
527
|
|
|
530
528
|
return GetPathForMacroResultSuccess(
|
|
531
529
|
resolved_path=resolved_path,
|
|
@@ -964,9 +962,7 @@ class ProjectManager:
|
|
|
964
962
|
raise RuntimeError(msg) from e
|
|
965
963
|
|
|
966
964
|
# Make absolute (resolve relative paths against project base directory)
|
|
967
|
-
resolved_dir_path = Path(resolved_path_str)
|
|
968
|
-
if not resolved_dir_path.is_absolute():
|
|
969
|
-
resolved_dir_path = project_base_dir / resolved_dir_path
|
|
965
|
+
resolved_dir_path = resolve_workspace_path(Path(resolved_path_str), project_base_dir)
|
|
970
966
|
# Normalize for consistent cross-platform comparison
|
|
971
967
|
resolved_dir_path = os_manager.resolve_path_safely(resolved_dir_path)
|
|
972
968
|
|
|
@@ -175,6 +175,7 @@ class SessionManager:
|
|
|
175
175
|
session_state_file = self._get_session_state_file(engine_id)
|
|
176
176
|
if session_state_file.exists():
|
|
177
177
|
try:
|
|
178
|
+
# TODO: Replace with DeleteFileRequest https://github.com/griptape-ai/griptape-nodes/issues/3765
|
|
178
179
|
session_state_file.unlink()
|
|
179
180
|
logger.info("Cleared all saved session data for engine: %s", engine_id)
|
|
180
181
|
except OSError:
|
|
@@ -225,6 +225,11 @@ class Settings(BaseModel):
|
|
|
225
225
|
description="Maximum number of nodes executing at a time for parallel execution.",
|
|
226
226
|
)
|
|
227
227
|
storage_backend: Literal["local", "gtc"] = Field(category=STORAGE, default="local")
|
|
228
|
+
auto_inject_workflow_metadata: bool = Field(
|
|
229
|
+
category=STORAGE,
|
|
230
|
+
default=True,
|
|
231
|
+
description="Automatically inject workflow metadata into saved images (JPEG, PNG, TIFF, MPO)",
|
|
232
|
+
)
|
|
228
233
|
minimum_disk_space_gb_libraries: float = Field(
|
|
229
234
|
category=SYSTEM_REQUIREMENTS,
|
|
230
235
|
default=10.0,
|
|
@@ -10,9 +10,11 @@ from xdg_base_dirs import xdg_config_home
|
|
|
10
10
|
from griptape_nodes.drivers.storage import StorageBackend
|
|
11
11
|
from griptape_nodes.drivers.storage.griptape_cloud_storage_driver import GriptapeCloudStorageDriver
|
|
12
12
|
from griptape_nodes.drivers.storage.local_storage_driver import LocalStorageDriver
|
|
13
|
+
from griptape_nodes.node_library.workflow_registry import WorkflowRegistry
|
|
13
14
|
from griptape_nodes.retained_mode.events.app_events import AppInitializationComplete
|
|
14
15
|
from griptape_nodes.retained_mode.events.os_events import ExistingFilePolicy
|
|
15
16
|
from griptape_nodes.retained_mode.events.static_file_events import (
|
|
17
|
+
CreateStaticFileDownloadUrlFromPathRequest,
|
|
16
18
|
CreateStaticFileDownloadUrlRequest,
|
|
17
19
|
CreateStaticFileDownloadUrlResultFailure,
|
|
18
20
|
CreateStaticFileDownloadUrlResultSuccess,
|
|
@@ -23,10 +25,13 @@ from griptape_nodes.retained_mode.events.static_file_events import (
|
|
|
23
25
|
CreateStaticFileUploadUrlResultFailure,
|
|
24
26
|
CreateStaticFileUploadUrlResultSuccess,
|
|
25
27
|
)
|
|
28
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
26
29
|
from griptape_nodes.retained_mode.managers.config_manager import ConfigManager
|
|
27
30
|
from griptape_nodes.retained_mode.managers.event_manager import EventManager
|
|
31
|
+
from griptape_nodes.retained_mode.managers.image_metadata_injector import inject_workflow_metadata_if_image
|
|
28
32
|
from griptape_nodes.retained_mode.managers.secrets_manager import SecretsManager
|
|
29
|
-
from griptape_nodes.servers.static import start_static_server
|
|
33
|
+
from griptape_nodes.servers.static import STATIC_SERVER_URL, start_static_server
|
|
34
|
+
from griptape_nodes.utils.url_utils import uri_to_path
|
|
30
35
|
|
|
31
36
|
logger = logging.getLogger("griptape_nodes")
|
|
32
37
|
|
|
@@ -55,8 +60,6 @@ class StaticFilesManager:
|
|
|
55
60
|
workspace_directory = Path(config_manager.get_config_value("workspace_directory"))
|
|
56
61
|
|
|
57
62
|
# Build base URL for LocalStorageDriver from configured base URL
|
|
58
|
-
from griptape_nodes.servers.static import STATIC_SERVER_URL
|
|
59
|
-
|
|
60
63
|
base_url_config = config_manager.get_config_value("static_server_base_url")
|
|
61
64
|
base_url = f"{base_url_config}{STATIC_SERVER_URL}"
|
|
62
65
|
|
|
@@ -95,6 +98,10 @@ class StaticFilesManager:
|
|
|
95
98
|
event_manager.assign_manager_to_request_type(
|
|
96
99
|
CreateStaticFileDownloadUrlRequest, self.on_handle_create_static_file_download_url_request
|
|
97
100
|
)
|
|
101
|
+
event_manager.assign_manager_to_request_type(
|
|
102
|
+
CreateStaticFileDownloadUrlFromPathRequest,
|
|
103
|
+
self.on_handle_create_static_file_download_url_from_path_request,
|
|
104
|
+
)
|
|
98
105
|
event_manager.add_listener_to_app_event(
|
|
99
106
|
AppInitializationComplete,
|
|
100
107
|
self.on_app_initialization_complete,
|
|
@@ -148,6 +155,7 @@ class StaticFilesManager:
|
|
|
148
155
|
url=response["url"],
|
|
149
156
|
headers=response["headers"],
|
|
150
157
|
method=response["method"],
|
|
158
|
+
file_url=self.storage_driver.get_asset_url(Path(response["file_path"])),
|
|
151
159
|
result_details="Successfully created static file upload URL",
|
|
152
160
|
)
|
|
153
161
|
|
|
@@ -155,7 +163,7 @@ class StaticFilesManager:
|
|
|
155
163
|
self,
|
|
156
164
|
request: CreateStaticFileDownloadUrlRequest,
|
|
157
165
|
) -> CreateStaticFileDownloadUrlResultSuccess | CreateStaticFileDownloadUrlResultFailure:
|
|
158
|
-
"""Handle the request to create a presigned URL for downloading a static file.
|
|
166
|
+
"""Handle the request to create a presigned URL for downloading a static file from the staticfiles directory.
|
|
159
167
|
|
|
160
168
|
Args:
|
|
161
169
|
request: The request object containing the file name.
|
|
@@ -163,19 +171,46 @@ class StaticFilesManager:
|
|
|
163
171
|
Returns:
|
|
164
172
|
A result object indicating success or failure.
|
|
165
173
|
"""
|
|
166
|
-
file_name = request.file_name
|
|
167
|
-
|
|
168
174
|
resolved_directory = self._get_static_files_directory()
|
|
169
|
-
full_file_path = Path(resolved_directory) / file_name
|
|
175
|
+
full_file_path = Path(resolved_directory) / request.file_name
|
|
170
176
|
|
|
171
177
|
try:
|
|
172
178
|
url = self.storage_driver.create_signed_download_url(full_file_path)
|
|
173
179
|
except Exception as e:
|
|
174
|
-
msg = f"Failed to create presigned URL for file {file_name}: {e}"
|
|
180
|
+
msg = f"Failed to create presigned URL for file {request.file_name}: {e}"
|
|
175
181
|
return CreateStaticFileDownloadUrlResultFailure(error=msg, result_details=msg)
|
|
176
182
|
|
|
177
183
|
return CreateStaticFileDownloadUrlResultSuccess(
|
|
178
|
-
url=url,
|
|
184
|
+
url=url,
|
|
185
|
+
file_url=self.storage_driver.get_asset_url(full_file_path),
|
|
186
|
+
result_details="Successfully created static file download URL",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def on_handle_create_static_file_download_url_from_path_request(
|
|
190
|
+
self,
|
|
191
|
+
request: CreateStaticFileDownloadUrlFromPathRequest,
|
|
192
|
+
) -> CreateStaticFileDownloadUrlResultSuccess | CreateStaticFileDownloadUrlResultFailure:
|
|
193
|
+
"""Handle request to create download URL from arbitrary file path.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
request: Request containing file_path parameter.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Result with download URL or failure message.
|
|
200
|
+
"""
|
|
201
|
+
full_file_path = Path(uri_to_path(request.file_path))
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
# TODO: use the driver appropriate for the file format. i.e. If local path use LocalStorageDriver, if GTC path use GriptapeCloudStorageDriver https://github.com/griptape-ai/griptape-nodes/issues/3739
|
|
205
|
+
url = self.storage_driver.create_signed_download_url(full_file_path)
|
|
206
|
+
except Exception as e:
|
|
207
|
+
msg = f"Failed to create presigned URL for file {request.file_path}: {e}"
|
|
208
|
+
return CreateStaticFileDownloadUrlResultFailure(error=msg, result_details=msg)
|
|
209
|
+
|
|
210
|
+
return CreateStaticFileDownloadUrlResultSuccess(
|
|
211
|
+
url=url,
|
|
212
|
+
file_url=self.storage_driver.get_asset_url(full_file_path),
|
|
213
|
+
result_details="Successfully created static file download URL",
|
|
179
214
|
)
|
|
180
215
|
|
|
181
216
|
def on_app_initialization_complete(self, _payload: AppInitializationComplete) -> None:
|
|
@@ -184,7 +219,13 @@ class StaticFilesManager:
|
|
|
184
219
|
threading.Thread(target=start_static_server, daemon=True, name="static-server").start()
|
|
185
220
|
|
|
186
221
|
def save_static_file(
|
|
187
|
-
self,
|
|
222
|
+
self,
|
|
223
|
+
data: bytes,
|
|
224
|
+
file_name: str,
|
|
225
|
+
existing_file_policy: ExistingFilePolicy = ExistingFilePolicy.OVERWRITE,
|
|
226
|
+
*,
|
|
227
|
+
use_direct_save: bool = False,
|
|
228
|
+
skip_metadata_injection: bool = False,
|
|
188
229
|
) -> str:
|
|
189
230
|
"""Saves a static file to the workspace directory.
|
|
190
231
|
|
|
@@ -197,20 +238,48 @@ class StaticFilesManager:
|
|
|
197
238
|
- OVERWRITE: Replace existing file content (default)
|
|
198
239
|
- CREATE_NEW: Auto-generate unique filename (e.g., file_1.txt, file_2.txt)
|
|
199
240
|
- FAIL: Raise FileExistsError if file exists
|
|
241
|
+
use_direct_save: If True, use direct storage driver save (new behavior).
|
|
242
|
+
If False, use presigned URL upload (legacy behavior). Defaults to False for backward compatibility.
|
|
243
|
+
skip_metadata_injection: If True, skip automatic workflow metadata injection.
|
|
244
|
+
Defaults to False. Used by nodes that handle metadata explicitly (e.g., WriteImageMetadataNode).
|
|
200
245
|
|
|
201
246
|
Returns:
|
|
202
|
-
The URL of the saved file. Note: the actual filename
|
|
203
|
-
file_name when using CREATE_NEW policy.
|
|
247
|
+
The URL of the saved file for UI display (with cache-busting). Note: the actual filename
|
|
248
|
+
may differ from the requested file_name when using CREATE_NEW policy.
|
|
204
249
|
|
|
205
250
|
Raises:
|
|
206
251
|
FileExistsError: When existing_file_policy is FAIL and file already exists.
|
|
252
|
+
RuntimeError: If file write fails (new behavior).
|
|
253
|
+
ValueError: If file upload fails (legacy behavior).
|
|
207
254
|
"""
|
|
208
255
|
resolved_directory = self._get_static_files_directory()
|
|
209
256
|
file_path = Path(resolved_directory) / file_name
|
|
210
257
|
|
|
211
|
-
#
|
|
212
|
-
|
|
258
|
+
# Inject workflow metadata if enabled (only when not using direct save)
|
|
259
|
+
if (
|
|
260
|
+
not use_direct_save
|
|
261
|
+
and self.config_manager.get_config_value("auto_inject_workflow_metadata", default=True)
|
|
262
|
+
and not skip_metadata_injection
|
|
263
|
+
):
|
|
264
|
+
try:
|
|
265
|
+
data = inject_workflow_metadata_if_image(data, file_name)
|
|
266
|
+
except Exception as e:
|
|
267
|
+
logger.warning("Failed to inject workflow metadata into %s: %s", file_name, e)
|
|
213
268
|
|
|
269
|
+
# NEW BEHAVIOR: Direct save via storage driver
|
|
270
|
+
if use_direct_save:
|
|
271
|
+
try:
|
|
272
|
+
saved_path = self.storage_driver.save_file(file_path, data, existing_file_policy)
|
|
273
|
+
except FileExistsError:
|
|
274
|
+
raise
|
|
275
|
+
except Exception as e:
|
|
276
|
+
msg = f"Failed to save static file {file_name}: {e}"
|
|
277
|
+
logger.error(msg)
|
|
278
|
+
raise RuntimeError(msg) from e
|
|
279
|
+
return saved_path
|
|
280
|
+
|
|
281
|
+
# OLD BEHAVIOR: Presigned URL upload
|
|
282
|
+
response = self.storage_driver.create_signed_upload_url(file_path, existing_file_policy)
|
|
214
283
|
resolved_file_path = Path(response["file_path"])
|
|
215
284
|
|
|
216
285
|
try:
|
|
@@ -235,9 +304,6 @@ class StaticFilesManager:
|
|
|
235
304
|
workflow's directory relative to workspace. Otherwise, returns the staticfiles
|
|
236
305
|
subdirectory relative to workspace.
|
|
237
306
|
"""
|
|
238
|
-
from griptape_nodes.node_library.workflow_registry import WorkflowRegistry
|
|
239
|
-
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
240
|
-
|
|
241
307
|
workspace_path = self.config_manager.workspace_path
|
|
242
308
|
static_files_subdir = self.config_manager.get_config_value("static_files_directory", default="staticfiles")
|
|
243
309
|
|
|
@@ -60,6 +60,9 @@ from griptape_nodes.retained_mode.events.library_events import (
|
|
|
60
60
|
)
|
|
61
61
|
from griptape_nodes.retained_mode.events.object_events import ClearAllObjectStateRequest
|
|
62
62
|
from griptape_nodes.retained_mode.events.os_events import (
|
|
63
|
+
DeleteFileRequest,
|
|
64
|
+
DeleteFileResultFailure,
|
|
65
|
+
DeletionBehavior,
|
|
63
66
|
ExistingFilePolicy,
|
|
64
67
|
FileIOFailureReason,
|
|
65
68
|
WriteFileRequest,
|
|
@@ -150,6 +153,7 @@ from griptape_nodes.retained_mode.managers.fitness_problems.workflows import (
|
|
|
150
153
|
WorkflowNotFoundProblem,
|
|
151
154
|
)
|
|
152
155
|
from griptape_nodes.retained_mode.managers.os_manager import OSManager
|
|
156
|
+
from griptape_nodes.utils.path_utils import resolve_workspace_path
|
|
153
157
|
|
|
154
158
|
if TYPE_CHECKING:
|
|
155
159
|
from collections.abc import Callable, Sequence
|
|
@@ -392,7 +396,7 @@ class WorkflowManager:
|
|
|
392
396
|
"""
|
|
393
397
|
return self._referenced_workflow_stack[-1]
|
|
394
398
|
|
|
395
|
-
def on_libraries_initialization_complete(self) -> None:
|
|
399
|
+
def on_libraries_initialization_complete(self, workflows_to_register: list[str] | None = None) -> None:
|
|
396
400
|
# All of the libraries have loaded, and any workflows they came with have been registered.
|
|
397
401
|
# Discover workflows from both config and workspace.
|
|
398
402
|
self._workflows_loading_complete.clear()
|
|
@@ -401,15 +405,16 @@ class WorkflowManager:
|
|
|
401
405
|
default_workflow_section = "app_events.on_app_initialization_complete.workflows_to_register"
|
|
402
406
|
config_mgr = GriptapeNodes.ConfigManager()
|
|
403
407
|
|
|
404
|
-
workflows_to_register
|
|
408
|
+
if workflows_to_register is None:
|
|
409
|
+
workflows_to_register = []
|
|
405
410
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
411
|
+
# Add from config
|
|
412
|
+
config_workflows = config_mgr.get_config_value(default_workflow_section, default=[])
|
|
413
|
+
workflows_to_register.extend(config_workflows)
|
|
409
414
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
415
|
+
# Add from workspace (avoiding duplicates)
|
|
416
|
+
workspace_path = config_mgr.workspace_path
|
|
417
|
+
workflows_to_register.extend([str(workspace_path)])
|
|
413
418
|
|
|
414
419
|
# Register all discovered workflows at once if any were found
|
|
415
420
|
self._process_workflows_for_registration(workflows_to_register)
|
|
@@ -631,11 +636,9 @@ class WorkflowManager:
|
|
|
631
636
|
raise RuntimeError(error_message)
|
|
632
637
|
|
|
633
638
|
async def run_workflow(self, relative_file_path: str) -> WorkflowExecutionResult:
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
else:
|
|
638
|
-
complete_file_path = WorkflowRegistry.get_complete_file_path(relative_file_path=relative_file_path)
|
|
639
|
+
# Resolve path using utility function
|
|
640
|
+
workspace_path = GriptapeNodes.ConfigManager().workspace_path
|
|
641
|
+
complete_file_path = resolve_workspace_path(Path(relative_file_path), workspace_path)
|
|
639
642
|
try:
|
|
640
643
|
# Libraries are now loaded only on app initialization and explicit reload requests
|
|
641
644
|
# Now execute the workflow.
|
|
@@ -820,7 +823,7 @@ class WorkflowManager:
|
|
|
820
823
|
workflows=workflows, result_details=f"Successfully retrieved {len(workflows)} workflows."
|
|
821
824
|
)
|
|
822
825
|
|
|
823
|
-
def on_delete_workflows_request(self, request: DeleteWorkflowRequest) -> ResultPayload:
|
|
826
|
+
async def on_delete_workflows_request(self, request: DeleteWorkflowRequest) -> ResultPayload:
|
|
824
827
|
try:
|
|
825
828
|
workflow = WorkflowRegistry.delete_workflow_by_name(request.name)
|
|
826
829
|
except Exception as e:
|
|
@@ -834,10 +837,15 @@ class WorkflowManager:
|
|
|
834
837
|
return DeleteWorkflowResultFailure(result_details=details)
|
|
835
838
|
# delete the actual file
|
|
836
839
|
full_path = config_manager.workspace_path.joinpath(workflow.file_path)
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
840
|
+
|
|
841
|
+
delete_request = DeleteFileRequest(
|
|
842
|
+
path=str(full_path),
|
|
843
|
+
workspace_only=False,
|
|
844
|
+
deletion_behavior=DeletionBehavior.PREFER_RECYCLE_BIN,
|
|
845
|
+
)
|
|
846
|
+
delete_result = await GriptapeNodes.ahandle_request(delete_request)
|
|
847
|
+
if isinstance(delete_result, DeleteFileResultFailure):
|
|
848
|
+
details = f"Failed to delete workflow file with path '{workflow.file_path}'. {delete_result.result_details}"
|
|
841
849
|
return DeleteWorkflowResultFailure(result_details=details)
|
|
842
850
|
return DeleteWorkflowResultSuccess(
|
|
843
851
|
result_details=ResultDetails(message=f"Successfully deleted workflow: {request.name}", level=logging.INFO)
|
|
@@ -1826,8 +1834,11 @@ class WorkflowManager:
|
|
|
1826
1834
|
|
|
1827
1835
|
ast_container = ASTContainer()
|
|
1828
1836
|
|
|
1837
|
+
# Extract library names from workflow metadata
|
|
1838
|
+
library_names = [lib.library_name for lib in workflow_metadata.node_libraries_referenced]
|
|
1839
|
+
|
|
1829
1840
|
prereq_code = self._generate_workflow_run_prerequisite_code(
|
|
1830
|
-
workflow_name=workflow_metadata.name, import_recorder=import_recorder
|
|
1841
|
+
workflow_name=workflow_metadata.name, import_recorder=import_recorder, library_names=library_names
|
|
1831
1842
|
)
|
|
1832
1843
|
for node in prereq_code:
|
|
1833
1844
|
ast_container.add_node(node)
|
|
@@ -2145,7 +2156,8 @@ class WorkflowManager:
|
|
|
2145
2156
|
),
|
|
2146
2157
|
)
|
|
2147
2158
|
|
|
2148
|
-
# Create conditional logic: workflow_executor = workflow_executor or LocalWorkflowExecutor(storage_backend=storage_backend_enum)
|
|
2159
|
+
# Create conditional logic: workflow_executor = workflow_executor or LocalWorkflowExecutor(storage_backend=storage_backend_enum, skip_library_loading=True, workflows_to_register=[__file__])
|
|
2160
|
+
# TODO: https://github.com/griptape-ai/griptape-nodes/issues/3771 Update for workflows that call other workflows - need to include referenced workflows in the list
|
|
2149
2161
|
executor_assign = ast.Assign(
|
|
2150
2162
|
targets=[ast.Name(id="workflow_executor", ctx=ast.Store())],
|
|
2151
2163
|
value=ast.BoolOp(
|
|
@@ -2159,6 +2171,11 @@ class WorkflowManager:
|
|
|
2159
2171
|
ast.keyword(
|
|
2160
2172
|
arg="storage_backend", value=ast.Name(id="storage_backend_enum", ctx=ast.Load())
|
|
2161
2173
|
),
|
|
2174
|
+
ast.keyword(arg="skip_library_loading", value=ast.Constant(value=True)),
|
|
2175
|
+
ast.keyword(
|
|
2176
|
+
arg="workflows_to_register",
|
|
2177
|
+
value=ast.List(elts=[ast.Name(id="__file__", ctx=ast.Load())], ctx=ast.Load()),
|
|
2178
|
+
),
|
|
2162
2179
|
],
|
|
2163
2180
|
),
|
|
2164
2181
|
],
|
|
@@ -2750,32 +2767,44 @@ class WorkflowManager:
|
|
|
2750
2767
|
self,
|
|
2751
2768
|
workflow_name: str,
|
|
2752
2769
|
import_recorder: ImportRecorder,
|
|
2770
|
+
library_names: list[str],
|
|
2753
2771
|
) -> list[ast.AST]:
|
|
2754
|
-
import_recorder.add_from_import(
|
|
2772
|
+
import_recorder.add_from_import(
|
|
2773
|
+
"griptape_nodes.retained_mode.events.library_events", "RegisterLibraryFromFileRequest"
|
|
2774
|
+
)
|
|
2755
2775
|
|
|
2756
2776
|
code_blocks: list[ast.AST] = []
|
|
2757
2777
|
|
|
2758
|
-
# Generate
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2778
|
+
# Generate one RegisterLibraryFromFileRequest call per library
|
|
2779
|
+
for library_name in library_names:
|
|
2780
|
+
register_call = ast.Expr(
|
|
2781
|
+
value=ast.Call(
|
|
2782
|
+
func=ast.Attribute(
|
|
2783
|
+
value=ast.Name(id="GriptapeNodes", ctx=ast.Load()),
|
|
2784
|
+
attr="handle_request",
|
|
2785
|
+
ctx=ast.Load(),
|
|
2786
|
+
),
|
|
2787
|
+
args=[
|
|
2788
|
+
ast.Call(
|
|
2789
|
+
func=ast.Name(id="RegisterLibraryFromFileRequest", ctx=ast.Load()),
|
|
2790
|
+
args=[],
|
|
2791
|
+
keywords=[
|
|
2792
|
+
ast.keyword(
|
|
2793
|
+
arg="library_name",
|
|
2794
|
+
value=ast.Constant(value=library_name),
|
|
2795
|
+
),
|
|
2796
|
+
ast.keyword(
|
|
2797
|
+
arg="perform_discovery_if_not_found",
|
|
2798
|
+
value=ast.Constant(value=True),
|
|
2799
|
+
),
|
|
2800
|
+
],
|
|
2801
|
+
)
|
|
2802
|
+
],
|
|
2803
|
+
keywords=[],
|
|
2804
|
+
)
|
|
2775
2805
|
)
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
code_blocks.append(load_call)
|
|
2806
|
+
ast.fix_missing_locations(register_call)
|
|
2807
|
+
code_blocks.append(register_call)
|
|
2779
2808
|
|
|
2780
2809
|
# Generate context manager assignment
|
|
2781
2810
|
assign_context_manager = ast.Assign(
|
|
@@ -4100,6 +4129,7 @@ class WorkflowManager:
|
|
|
4100
4129
|
result_messages = []
|
|
4101
4130
|
try:
|
|
4102
4131
|
WorkflowRegistry.delete_workflow_by_name(request.workflow_name)
|
|
4132
|
+
# TODO: Replace with DeleteFileRequest https://github.com/griptape-ai/griptape-nodes/issues/3765
|
|
4103
4133
|
Path(branch_content_file_path).unlink()
|
|
4104
4134
|
cleanup_message = f"Deleted branch workflow file and registry entry for '{request.workflow_name}'"
|
|
4105
4135
|
result_messages.append(ResultDetail(message=cleanup_message, level=logging.INFO))
|
griptape_nodes/servers/static.py
CHANGED
|
@@ -9,6 +9,7 @@ from urllib.parse import urljoin
|
|
|
9
9
|
import uvicorn
|
|
10
10
|
from fastapi import FastAPI, HTTPException, Request
|
|
11
11
|
from fastapi.middleware.cors import CORSMiddleware
|
|
12
|
+
from fastapi.responses import FileResponse
|
|
12
13
|
from fastapi.staticfiles import StaticFiles
|
|
13
14
|
from rich.logging import RichHandler
|
|
14
15
|
|
|
@@ -122,6 +123,7 @@ async def _delete_static_file(file_path: str) -> dict:
|
|
|
122
123
|
raise HTTPException(status_code=400, detail=msg)
|
|
123
124
|
|
|
124
125
|
try:
|
|
126
|
+
# TODO: Replace with DeleteFileRequest https://github.com/griptape-ai/griptape-nodes/issues/3765
|
|
125
127
|
file_full_path.unlink()
|
|
126
128
|
except (OSError, PermissionError) as e:
|
|
127
129
|
msg = f"Failed to delete file {file_path}: {e}"
|
|
@@ -132,6 +134,34 @@ async def _delete_static_file(file_path: str) -> dict:
|
|
|
132
134
|
return {"message": f"File {file_path} deleted successfully"}
|
|
133
135
|
|
|
134
136
|
|
|
137
|
+
async def _serve_external_file(file_path: str) -> FileResponse:
|
|
138
|
+
"""Serve a file from outside the workspace.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
file_path: The file path without leading slash (e.g., "tmp/video.mp4" for "/tmp/video.mp4")
|
|
142
|
+
"""
|
|
143
|
+
if not STATIC_SERVER_ENABLED:
|
|
144
|
+
msg = "Static server is not enabled. Please set STATIC_SERVER_ENABLED to True."
|
|
145
|
+
raise HTTPException(status_code=500, detail=msg)
|
|
146
|
+
|
|
147
|
+
# Reconstruct absolute path by adding leading slash
|
|
148
|
+
absolute_path = Path(f"/{file_path}")
|
|
149
|
+
|
|
150
|
+
# Check if file exists
|
|
151
|
+
if not absolute_path.exists():
|
|
152
|
+
logger.warning("External file not found: %s", absolute_path)
|
|
153
|
+
raise HTTPException(status_code=404, detail=f"File {absolute_path} not found")
|
|
154
|
+
|
|
155
|
+
# Check if it's actually a file (not a directory)
|
|
156
|
+
if not absolute_path.is_file():
|
|
157
|
+
msg = f"Path {absolute_path} is not a file"
|
|
158
|
+
logger.error(msg)
|
|
159
|
+
raise HTTPException(status_code=400, detail=msg)
|
|
160
|
+
|
|
161
|
+
# Serve the file
|
|
162
|
+
return FileResponse(absolute_path)
|
|
163
|
+
|
|
164
|
+
|
|
135
165
|
def start_static_server() -> None:
|
|
136
166
|
"""Run uvicorn server synchronously using uvicorn.run."""
|
|
137
167
|
logger.debug("Starting static server...")
|
|
@@ -145,6 +175,7 @@ def start_static_server() -> None:
|
|
|
145
175
|
app.add_api_route("/static-uploads/{file_path_prefix:path}", _list_static_files, methods=["GET"])
|
|
146
176
|
app.add_api_route("/static-uploads/", _list_static_files, methods=["GET"])
|
|
147
177
|
app.add_api_route("/static-files/{file_path:path}", _delete_static_file, methods=["DELETE"])
|
|
178
|
+
app.add_api_route("/external/{file_path:path}", _serve_external_file, methods=["GET"])
|
|
148
179
|
|
|
149
180
|
# Build CORS allowed origins list
|
|
150
181
|
allowed_origins = [
|
griptape_nodes/utils/__init__.py
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
"""Various utility functions."""
|
|
2
2
|
|
|
3
3
|
from griptape_nodes.utils.async_utils import call_function
|
|
4
|
+
from griptape_nodes.utils.http_file_patch import install_file_url_support
|
|
5
|
+
from griptape_nodes.utils.path_utils import resolve_workspace_path
|
|
6
|
+
from griptape_nodes.utils.url_utils import get_content_type_from_extension
|
|
4
7
|
|
|
5
|
-
__all__ = [
|
|
8
|
+
__all__ = [
|
|
9
|
+
"call_function",
|
|
10
|
+
"get_content_type_from_extension",
|
|
11
|
+
"install_file_url_support",
|
|
12
|
+
"resolve_workspace_path",
|
|
13
|
+
]
|