griptape-nodes 0.71.0__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.
Files changed (50) hide show
  1. griptape_nodes/app/app.py +4 -0
  2. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +10 -1
  3. griptape_nodes/bootstrap/workflow_executors/utils/subprocess_script.py +4 -0
  4. griptape_nodes/bootstrap/workflow_publishers/utils/subprocess_script.py +4 -0
  5. griptape_nodes/common/node_executor.py +1 -1
  6. griptape_nodes/drivers/image_metadata/__init__.py +21 -0
  7. griptape_nodes/drivers/image_metadata/base_image_metadata_driver.py +63 -0
  8. griptape_nodes/drivers/image_metadata/exif_metadata_driver.py +218 -0
  9. griptape_nodes/drivers/image_metadata/image_metadata_driver_registry.py +55 -0
  10. griptape_nodes/drivers/image_metadata/png_metadata_driver.py +71 -0
  11. griptape_nodes/drivers/storage/base_storage_driver.py +32 -0
  12. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +384 -10
  13. griptape_nodes/drivers/storage/local_storage_driver.py +65 -4
  14. griptape_nodes/drivers/thread_storage/local_thread_storage_driver.py +1 -0
  15. griptape_nodes/exe_types/node_groups/base_node_group.py +3 -0
  16. griptape_nodes/exe_types/node_types.py +13 -0
  17. griptape_nodes/exe_types/param_components/log_parameter.py +3 -2
  18. griptape_nodes/exe_types/param_types/parameter_float.py +4 -4
  19. griptape_nodes/exe_types/param_types/parameter_int.py +4 -4
  20. griptape_nodes/exe_types/param_types/parameter_number.py +34 -30
  21. griptape_nodes/node_library/workflow_registry.py +5 -8
  22. griptape_nodes/retained_mode/events/app_events.py +1 -0
  23. griptape_nodes/retained_mode/events/base_events.py +42 -26
  24. griptape_nodes/retained_mode/events/flow_events.py +67 -0
  25. griptape_nodes/retained_mode/events/library_events.py +1 -1
  26. griptape_nodes/retained_mode/events/node_events.py +1 -0
  27. griptape_nodes/retained_mode/events/os_events.py +22 -0
  28. griptape_nodes/retained_mode/events/static_file_events.py +28 -4
  29. griptape_nodes/retained_mode/managers/flow_manager.py +134 -0
  30. griptape_nodes/retained_mode/managers/image_metadata_injector.py +339 -0
  31. griptape_nodes/retained_mode/managers/library_manager.py +71 -41
  32. griptape_nodes/retained_mode/managers/model_manager.py +1 -0
  33. griptape_nodes/retained_mode/managers/node_manager.py +8 -5
  34. griptape_nodes/retained_mode/managers/os_manager.py +269 -32
  35. griptape_nodes/retained_mode/managers/project_manager.py +3 -7
  36. griptape_nodes/retained_mode/managers/session_manager.py +1 -0
  37. griptape_nodes/retained_mode/managers/settings.py +5 -0
  38. griptape_nodes/retained_mode/managers/static_files_manager.py +83 -17
  39. griptape_nodes/retained_mode/managers/workflow_manager.py +71 -41
  40. griptape_nodes/servers/static.py +31 -0
  41. griptape_nodes/traits/clamp.py +52 -9
  42. griptape_nodes/utils/__init__.py +9 -1
  43. griptape_nodes/utils/file_utils.py +13 -13
  44. griptape_nodes/utils/http_file_patch.py +613 -0
  45. griptape_nodes/utils/path_utils.py +58 -0
  46. griptape_nodes/utils/url_utils.py +106 -0
  47. {griptape_nodes-0.71.0.dist-info → griptape_nodes-0.72.0.dist-info}/METADATA +2 -1
  48. {griptape_nodes-0.71.0.dist-info → griptape_nodes-0.72.0.dist-info}/RECORD +50 -41
  49. {griptape_nodes-0.71.0.dist-info → griptape_nodes-0.72.0.dist-info}/WHEEL +1 -1
  50. {griptape_nodes-0.71.0.dist-info → griptape_nodes-0.72.0.dist-info}/entry_points.txt +0 -0
@@ -37,8 +37,8 @@ class ParameterNumber(Parameter):
37
37
  ui_options: dict | None = None,
38
38
  step: float | None = None,
39
39
  slider: bool = False,
40
- min_val: float = 0,
41
- max_val: float = 100,
40
+ min_val: float | None = None,
41
+ max_val: float | None = None,
42
42
  validate_min_max: bool = False,
43
43
  accept_any: bool = True,
44
44
  hide: bool | None = None,
@@ -74,8 +74,8 @@ class ParameterNumber(Parameter):
74
74
  ui_options: Dictionary of UI options
75
75
  step: Step size for numeric input controls
76
76
  slider: Whether to use slider trait
77
- min_val: Minimum value for constraints
78
- max_val: Maximum value for constraints
77
+ min_val: Minimum value for constraints (None to disable constraints)
78
+ max_val: Maximum value for constraints (None to disable constraints)
79
79
  validate_min_max: Whether to validate min/max with error
80
80
  accept_any: Whether to accept any input type and convert to number (default: True)
81
81
  hide: Whether to hide the entire parameter
@@ -220,8 +220,8 @@ class ParameterNumber(Parameter):
220
220
  traits: set[type[Trait] | Trait] | None,
221
221
  *,
222
222
  slider: bool,
223
- min_val: float,
224
- max_val: float,
223
+ min_val: float | None,
224
+ max_val: float | None,
225
225
  validate_min_max: bool,
226
226
  ) -> None:
227
227
  """Set up constraint traits based on parameters.
@@ -235,12 +235,6 @@ class ParameterNumber(Parameter):
235
235
  validate_min_max: Whether to validate min/max with error
236
236
  """
237
237
  # Validation rules
238
- if min_val is not None and max_val is None:
239
- msg = f"{name}: If min_val is provided, max_val must also be provided"
240
- raise ValueError(msg)
241
- if max_val is not None and min_val is None:
242
- msg = f"{name}: If max_val is provided, min_val must also be provided"
243
- raise ValueError(msg)
244
238
  if slider and (min_val is None or max_val is None):
245
239
  msg = f"{name}: If slider is True, both min_val and max_val must be provided"
246
240
  raise ValueError(msg)
@@ -259,7 +253,7 @@ class ParameterNumber(Parameter):
259
253
  traits.add(Slider(min_val=min_val, max_val=max_val))
260
254
  elif validate_min_max and min_val is not None and max_val is not None:
261
255
  traits.add(MinMax(min_val=min_val, max_val=max_val))
262
- elif min_val is not None and max_val is not None:
256
+ elif min_val is not None or max_val is not None:
263
257
  traits.add(Clamp(min_val=min_val, max_val=max_val))
264
258
 
265
259
  # Store traits for later use
@@ -278,14 +272,14 @@ class ParameterNumber(Parameter):
278
272
  def slider(self, value: bool) -> None:
279
273
  """Set slider trait."""
280
274
  if value:
281
- if not hasattr(self, "_constraint_traits") or not self._constraint_traits:
275
+ # Get min/max from stored values
276
+ min_val = getattr(self, "_min_val", None)
277
+ max_val = getattr(self, "_max_val", None)
278
+ if min_val is None or max_val is None:
282
279
  msg = f"{self.name}: Cannot enable slider without min_val and max_val"
283
280
  raise ValueError(msg)
284
281
  # Find existing constraint traits and replace with slider
285
282
  self._remove_constraint_traits()
286
- # Get min/max from existing traits or use defaults
287
- min_val = getattr(self, "_min_val", 0)
288
- max_val = getattr(self, "_max_val", 100)
289
283
  self.add_trait(Slider(min_val=min_val, max_val=max_val))
290
284
  else:
291
285
  # Remove slider trait
@@ -304,9 +298,6 @@ class ParameterNumber(Parameter):
304
298
  @min_val.setter
305
299
  def min_val(self, value: float | None) -> None:
306
300
  """Set minimum value and update constraint traits."""
307
- if value is not None and self.max_val is None:
308
- msg = f"{self.name}: Cannot set min_val without max_val"
309
- raise ValueError(msg)
310
301
  self._min_val = value
311
302
  self._update_constraint_traits()
312
303
 
@@ -321,9 +312,6 @@ class ParameterNumber(Parameter):
321
312
  @max_val.setter
322
313
  def max_val(self, value: float | None) -> None:
323
314
  """Set maximum value and update constraint traits."""
324
- if value is not None and self.min_val is None:
325
- msg = f"{self.name}: Cannot set max_val without min_val"
326
- raise ValueError(msg)
327
315
  self._max_val = value
328
316
  self._update_constraint_traits()
329
317
 
@@ -337,14 +325,12 @@ class ParameterNumber(Parameter):
337
325
  """Set MinMax validation."""
338
326
  if value:
339
327
  # Check if we have stored min/max values
340
- min_val = getattr(self, "_min_val", None)
341
- max_val = getattr(self, "_max_val", None)
342
- if min_val is None or max_val is None:
328
+ if self.min_val is None or self.max_val is None:
343
329
  msg = f"{self.name}: Cannot enable validate_min_max without min_val and max_val"
344
330
  raise ValueError(msg)
345
331
  # Replace existing constraint traits with MinMax
346
332
  self._remove_constraint_traits()
347
- self.add_trait(MinMax(min_val=min_val, max_val=max_val))
333
+ self.add_trait(MinMax(min_val=self.min_val, max_val=self.max_val))
348
334
  else:
349
335
  # Remove MinMax trait and replace with Clamp if we have min/max
350
336
  min_max_traits = self.find_elements_by_type(MinMax)
@@ -367,17 +353,35 @@ class ParameterNumber(Parameter):
367
353
  min_val = getattr(self, "_min_val", None)
368
354
  max_val = getattr(self, "_max_val", None)
369
355
 
370
- if min_val is None or max_val is None:
356
+ if min_val is None and max_val is None:
371
357
  self._remove_constraint_traits()
372
358
  return
373
359
 
360
+ if self.slider and (min_val is None or max_val is None):
361
+ msg = f"{self.name}: Cannot enable slider without min_val and max_val"
362
+ raise ValueError(msg)
363
+
364
+ if self.validate_min_max and (min_val is None or max_val is None):
365
+ msg = f"{self.name}: Cannot enable validate_min_max without min_val and max_val"
366
+ raise ValueError(msg)
367
+
374
368
  # Determine which trait to use based on current state
375
369
  if self.slider:
370
+ # Checked above: both min_val and max_val are available when slider is enabled.
371
+ # Type narrowing: assign to variables after None check so type checker understands
372
+ min_val_float: float = min_val # type: ignore[assignment]
373
+ max_val_float: float = max_val # type: ignore[assignment]
374
+ # Python will naturally coerce int to float if needed (no precision loss for reasonable values)
376
375
  self._remove_constraint_traits()
377
- self.add_trait(Slider(min_val=min_val, max_val=max_val))
376
+ self.add_trait(Slider(min_val=min_val_float, max_val=max_val_float))
378
377
  elif self.validate_min_max:
378
+ # Checked above: both min_val and max_val are available when validate_min_max is enabled.
379
+ # Type narrowing: assign to variables after None check so type checker understands
380
+ min_val_float: float = min_val # type: ignore[assignment]
381
+ max_val_float: float = max_val # type: ignore[assignment]
382
+ # Python will naturally coerce int to float if needed (no precision loss for reasonable values)
379
383
  self._remove_constraint_traits()
380
- self.add_trait(MinMax(min_val=min_val, max_val=max_val))
384
+ self.add_trait(MinMax(min_val=min_val_float, max_val=max_val_float))
381
385
  else:
382
386
  self._remove_constraint_traits()
383
387
  self.add_trait(Clamp(min_val=min_val, max_val=max_val))
@@ -12,6 +12,7 @@ from griptape_nodes.node_library.library_registry import (
12
12
  LibraryNameAndVersion, # noqa: TC001 (putting this into type checking causes it to not be defined)
13
13
  )
14
14
  from griptape_nodes.utils.metaclasses import SingletonMeta
15
+ from griptape_nodes.utils.path_utils import resolve_workspace_path
15
16
 
16
17
  logger = logging.getLogger("griptape_nodes")
17
18
 
@@ -45,7 +46,7 @@ class WorkflowShape(BaseModel):
45
46
 
46
47
 
47
48
  class WorkflowMetadata(BaseModel):
48
- LATEST_SCHEMA_VERSION: ClassVar[str] = "0.14.0"
49
+ LATEST_SCHEMA_VERSION: ClassVar[str] = "0.15.0"
49
50
 
50
51
  name: str
51
52
  schema_version: str
@@ -175,15 +176,11 @@ class WorkflowRegistry(metaclass=SingletonMeta):
175
176
  def get_complete_file_path(cls, relative_file_path: str) -> str:
176
177
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
177
178
 
178
- # If the path is already absolute, return it as-is
179
- if Path(relative_file_path).is_absolute():
180
- return relative_file_path
181
-
182
- # Otherwise, resolve it relative to the workspace
179
+ # Resolve path using utility function
183
180
  config_mgr = GriptapeNodes.ConfigManager()
184
181
  workspace_path = config_mgr.workspace_path
185
- complete_file_path = workspace_path / relative_file_path
186
- return str(complete_file_path)
182
+ resolved_path = resolve_workspace_path(Path(relative_file_path), workspace_path)
183
+ return str(resolved_path)
187
184
 
188
185
  @classmethod
189
186
  def delete_workflow_by_name(cls, name: str) -> Workflow:
@@ -113,6 +113,7 @@ class AppInitializationComplete(AppPayload):
113
113
  libraries_to_register: list[str] = field(default_factory=list)
114
114
  workflows_to_register: list[str] = field(default_factory=list)
115
115
  models_to_download: list[str] = field(default_factory=list)
116
+ skip_library_loading: bool = False
116
117
 
117
118
 
118
119
  @dataclass
@@ -16,6 +16,31 @@ if TYPE_CHECKING:
16
16
  import builtins
17
17
 
18
18
 
19
+ def default_json_encoder(obj: Any) -> Any:
20
+ """Custom JSON encoder for various object types.
21
+
22
+ Attempts the following encodings in order:
23
+ 1. If the object is a SerializableMixin, call to_dict()
24
+ 2. If the object is a Pydantic model, call model_dump()
25
+ 3. Attempt to use the default JSON encoder
26
+ 4. If all else fails, return the string representation of the object
27
+
28
+ Args:
29
+ obj: The object to encode
30
+
31
+ Returns:
32
+ JSON-serializable representation of the object
33
+ """
34
+ if isinstance(obj, SerializableMixin):
35
+ return obj.to_dict()
36
+ if isinstance(obj, BaseModel):
37
+ return obj.model_dump()
38
+ try:
39
+ return json.JSONEncoder().default(obj)
40
+ except TypeError:
41
+ return str(obj)
42
+
43
+
19
44
  @dataclass
20
45
  class ResultDetail:
21
46
  """A single detail about an operation result, including logging level and human readable message."""
@@ -71,6 +96,22 @@ class ResultDetails:
71
96
  class Payload(ABC): # noqa: B024
72
97
  """Base class for all payload types. Customers will derive from this."""
73
98
 
99
+ def to_json(self, **kwargs) -> str:
100
+ """Serialize this payload to JSON string.
101
+
102
+ Returns:
103
+ JSON string representation of the payload
104
+ """
105
+ # Convert payload to dict
106
+ if is_dataclass(self):
107
+ payload_dict = asdict(self)
108
+ elif hasattr(self, "__dict__"):
109
+ payload_dict = self.__dict__
110
+ else:
111
+ payload_dict = str(self)
112
+
113
+ return json.dumps(payload_dict, default=default_json_encoder, **kwargs)
114
+
74
115
 
75
116
  # Request payload base class with optional request ID
76
117
  @dataclass(kw_only=True)
@@ -228,32 +269,7 @@ class BaseEvent(BaseModel, ABC):
228
269
 
229
270
  def json(self, **kwargs) -> str:
230
271
  """Serialize to JSON string."""
231
-
232
- # TODO: https://github.com/griptape-ai/griptape-nodes/issues/906
233
- def default_encoder(obj: Any) -> Any:
234
- """Custom JSON encoder for various object types.
235
-
236
- Attempts the following encodings in order:
237
- 1. If the object is a SerializableMixin, call to_dict()
238
- 2. If the object is a Pydantic model, call model_dump()
239
- 3. Attempt to use the default JSON encoder
240
- 4. If all else fails, return the string representation of the object
241
-
242
- Args:
243
- obj: The object to encode
244
-
245
-
246
- """
247
- if isinstance(obj, SerializableMixin):
248
- return obj.to_dict()
249
- if isinstance(obj, BaseModel):
250
- return obj.model_dump()
251
- try:
252
- return json.JSONEncoder().default(obj)
253
- except TypeError:
254
- return str(obj)
255
-
256
- return json.dumps(self.dict(), default=default_encoder, **kwargs)
272
+ return json.dumps(self.dict(), default=default_json_encoder, **kwargs)
257
273
 
258
274
  @abstractmethod
259
275
  def get_request(self) -> Payload:
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
+ from enum import StrEnum
4
5
  from typing import TYPE_CHECKING, Any, NamedTuple
5
6
 
6
7
  if TYPE_CHECKING:
@@ -19,6 +20,18 @@ from griptape_nodes.retained_mode.events.base_events import (
19
20
  from griptape_nodes.retained_mode.events.payload_registry import PayloadRegistry
20
21
 
21
22
 
23
+ class FlowMetadataExtractionFailureReason(StrEnum):
24
+ """Reasons why flow metadata extraction from image failed."""
25
+
26
+ FILE_NOT_FOUND = "file_not_found"
27
+ FILE_READ_ERROR = "file_read_error"
28
+ INVALID_IMAGE_FORMAT = "invalid_image_format"
29
+ NO_FLOW_METADATA = "no_flow_metadata"
30
+ INVALID_BASE64 = "invalid_base64"
31
+ INVALID_PICKLE = "invalid_pickle"
32
+ DESERIALIZATION_FAILED = "deserialization_failed"
33
+
34
+
22
35
  @dataclass(kw_only=True)
23
36
  @PayloadRegistry.register
24
37
  class CreateFlowRequest(RequestPayload):
@@ -288,6 +301,60 @@ class DeserializeFlowFromCommandsResultFailure(ResultPayloadFailure):
288
301
  pass
289
302
 
290
303
 
304
+ @dataclass
305
+ @PayloadRegistry.register
306
+ class ExtractFlowCommandsFromImageMetadataRequest(RequestPayload):
307
+ """Extract flow commands from PNG image metadata.
308
+
309
+ This request reads a PNG image file, extracts the embedded workflow metadata
310
+ (specifically the gtn_flow_commands field), decodes it from base64, unpickles it,
311
+ and returns the SerializedFlowCommands object. Optionally, it can automatically
312
+ deserialize the flow by calling DeserializeFlowFromCommandsRequest.
313
+
314
+ Use when: Loading flow commands from an exported image, inspecting workflow
315
+ structure before deserialization, extracting flows shared as PNG files.
316
+
317
+ Args:
318
+ file_url_or_path: Path to the PNG image file (absolute or relative) or URL
319
+ deserialize: If True, automatically deserialize the flow after extraction (default: False)
320
+
321
+ Results:
322
+ ExtractFlowCommandsFromImageMetadataResultSuccess - Commands extracted (and optionally deserialized)
323
+ ExtractFlowCommandsFromImageMetadataResultFailure - Extraction or deserialization failed
324
+ """
325
+
326
+ file_url_or_path: str
327
+ deserialize: bool = False
328
+
329
+
330
+ @dataclass(kw_only=True)
331
+ @PayloadRegistry.register
332
+ class ExtractFlowCommandsFromImageMetadataResultSuccess(ResultPayloadSuccess):
333
+ """Flow commands extracted successfully from image metadata.
334
+
335
+ Args:
336
+ serialized_flow_commands: The extracted and decoded SerializedFlowCommands object
337
+ flow_name: Name of the deserialized flow (only present if deserialize=True)
338
+ node_name_mappings: Mapping from original to deserialized node names (only present if deserialize=True)
339
+ """
340
+
341
+ serialized_flow_commands: SerializedFlowCommands
342
+ flow_name: str | None = None
343
+ node_name_mappings: dict[str, str] = field(default_factory=dict)
344
+
345
+
346
+ @dataclass
347
+ @PayloadRegistry.register
348
+ class ExtractFlowCommandsFromImageMetadataResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
349
+ """Flow commands extraction failed.
350
+
351
+ Args:
352
+ file_path: The file path that was attempted (for error context)
353
+ """
354
+
355
+ file_path: str
356
+
357
+
291
358
  @dataclass
292
359
  @PayloadRegistry.register
293
360
  class GetFlowDetailsRequest(RequestPayload):
@@ -607,7 +607,7 @@ class DiscoverLibrariesRequest(RequestPayload):
607
607
  class DiscoverLibrariesResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
608
608
  """Libraries discovered successfully."""
609
609
 
610
- libraries_discovered: set[DiscoveredLibrary] # Discovered libraries with type info
610
+ libraries_discovered: list[DiscoveredLibrary] # Discovered libraries in config order
611
611
 
612
612
 
613
613
  @dataclass
@@ -565,6 +565,7 @@ class SerializeNodeToCommandsRequest(RequestPayload):
565
565
  default_factory=SerializedParameterValueTracker
566
566
  )
567
567
  use_pickling: bool = False
568
+ serialize_all_parameter_values: bool = False
568
569
 
569
570
 
570
571
  @dataclass
@@ -54,6 +54,24 @@ class FileIOFailureReason(StrEnum):
54
54
  IO_ERROR = "io_error" # Generic I/O error
55
55
  UNKNOWN = "unknown" # Unexpected error
56
56
 
57
+ # Recycle bin errors
58
+ RECYCLE_BIN_UNAVAILABLE = "recycle_bin_unavailable" # Recycle bin unavailable and behavior was RECYCLE_BIN_ONLY
59
+
60
+
61
+ class DeletionBehavior(StrEnum):
62
+ """How to handle file/directory deletion."""
63
+
64
+ PERMANENTLY_DELETE = "permanently_delete" # Permanently delete (default, current behavior)
65
+ RECYCLE_BIN_ONLY = "recycle_bin_only" # Send to recycle bin; fail if unavailable
66
+ PREFER_RECYCLE_BIN = "prefer_recycle_bin" # Try recycle bin; fall back to permanent deletion
67
+
68
+
69
+ class DeletionOutcome(StrEnum):
70
+ """The actual outcome of a deletion operation."""
71
+
72
+ PERMANENTLY_DELETED = "permanently_deleted"
73
+ SENT_TO_RECYCLE_BIN = "sent_to_recycle_bin"
74
+
57
75
 
58
76
  @dataclass
59
77
  class FileSystemEntry:
@@ -585,6 +603,7 @@ class DeleteFileRequest(RequestPayload):
585
603
  path: Path to file/directory to delete (mutually exclusive with file_entry)
586
604
  file_entry: FileSystemEntry from directory listing (mutually exclusive with path)
587
605
  workspace_only: If True, constrain to workspace directory
606
+ deletion_behavior: How to handle deletion (permanent, recycle bin only, or prefer recycle bin)
588
607
 
589
608
  Results: DeleteFileResultSuccess | DeleteFileResultFailure
590
609
  """
@@ -592,6 +611,7 @@ class DeleteFileRequest(RequestPayload):
592
611
  path: str | None = None
593
612
  file_entry: FileSystemEntry | None = None
594
613
  workspace_only: bool | None = True
614
+ deletion_behavior: DeletionBehavior = DeletionBehavior.PREFER_RECYCLE_BIN
595
615
 
596
616
 
597
617
  @dataclass
@@ -603,11 +623,13 @@ class DeleteFileResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
603
623
  deleted_path: The absolute path that was deleted (primary path)
604
624
  was_directory: Whether the deleted item was a directory
605
625
  deleted_paths: List of all paths that were deleted (for recursive deletes, includes all files/dirs)
626
+ outcome: The actual outcome of the deletion (permanently deleted or sent to recycle bin)
606
627
  """
607
628
 
608
629
  deleted_path: str
609
630
  was_directory: bool
610
631
  deleted_paths: list[str]
632
+ outcome: DeletionOutcome
611
633
 
612
634
 
613
635
  @dataclass
@@ -78,11 +78,13 @@ class CreateStaticFileUploadUrlResultSuccess(WorkflowNotAlteredMixin, ResultPayl
78
78
  url: Presigned URL for uploading the file
79
79
  headers: HTTP headers required for the upload request
80
80
  method: HTTP method to use for upload (typically PUT)
81
+ file_url: File URI (file://) for the absolute path where the file will be accessible after upload
81
82
  """
82
83
 
83
84
  url: str
84
85
  headers: dict = field(default_factory=dict)
85
86
  method: str = "PUT"
87
+ file_url: str = ""
86
88
 
87
89
 
88
90
  @dataclass
@@ -100,13 +102,13 @@ class CreateStaticFileUploadUrlResultFailure(WorkflowNotAlteredMixin, ResultPayl
100
102
  @dataclass
101
103
  @PayloadRegistry.register
102
104
  class CreateStaticFileDownloadUrlRequest(RequestPayload):
103
- """Create a presigned URL for downloading a static file via HTTP GET.
105
+ """Create a presigned URL for downloading a static file from the staticfiles directory via HTTP GET.
104
106
 
105
- Use when: Providing secure file access, implementing file sharing,
106
- enabling temporary download links, controlling file access permissions.
107
+ Use when: Providing secure file access to files in the staticfiles directory,
108
+ implementing file sharing, enabling temporary download links, controlling file access permissions.
107
109
 
108
110
  Args:
109
- file_name: Name of the file to be downloaded
111
+ file_name: Name of the file to be downloaded from the staticfiles directory
110
112
 
111
113
  Results: CreateStaticFileDownloadUrlResultSuccess (with URL) | CreateStaticFileDownloadUrlResultFailure (URL creation error)
112
114
  """
@@ -114,6 +116,26 @@ class CreateStaticFileDownloadUrlRequest(RequestPayload):
114
116
  file_name: str
115
117
 
116
118
 
119
+ @dataclass
120
+ @PayloadRegistry.register
121
+ class CreateStaticFileDownloadUrlFromPathRequest(RequestPayload):
122
+ """Create a presigned URL for downloading a file from an arbitrary path.
123
+
124
+ Use when: Need to create download URLs for files outside the staticfiles directory,
125
+ working with absolute paths, file:// URLs, or workspace-relative paths.
126
+
127
+ Args:
128
+ file_path: File path or URL. Accepts:
129
+ - file:// URLs (e.g., "file:///absolute/path/to/file.jpg")
130
+ - Absolute paths (e.g., "/absolute/path/to/file.jpg")
131
+ - Workspace-relative paths (e.g., "relative/path/to/file.jpg")
132
+
133
+ Results: CreateStaticFileDownloadUrlResultSuccess (with URL) | CreateStaticFileDownloadUrlResultFailure (URL creation error)
134
+ """
135
+
136
+ file_path: str
137
+
138
+
117
139
  @dataclass
118
140
  @PayloadRegistry.register
119
141
  class CreateStaticFileDownloadUrlResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
@@ -121,9 +143,11 @@ class CreateStaticFileDownloadUrlResultSuccess(WorkflowNotAlteredMixin, ResultPa
121
143
 
122
144
  Args:
123
145
  url: Presigned URL for downloading the file
146
+ file_url: File URI (file://) for the absolute path to the file that was used to create the download URL
124
147
  """
125
148
 
126
149
  url: str
150
+ file_url: str = ""
127
151
 
128
152
 
129
153
  @dataclass