griptape-nodes 0.43.1__py3-none-any.whl → 0.44.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. griptape_nodes/__init__.py +41 -51
  2. griptape_nodes/app/.python-version +0 -0
  3. griptape_nodes/app/__init__.py +0 -0
  4. griptape_nodes/app/api.py +35 -6
  5. griptape_nodes/app/app.py +0 -0
  6. griptape_nodes/app/watch.py +0 -0
  7. griptape_nodes/bootstrap/__init__.py +0 -0
  8. griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
  9. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +7 -1
  10. griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +90 -0
  11. griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +7 -1
  12. griptape_nodes/drivers/__init__.py +0 -0
  13. griptape_nodes/drivers/storage/__init__.py +0 -0
  14. griptape_nodes/drivers/storage/base_storage_driver.py +53 -0
  15. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +49 -2
  16. griptape_nodes/drivers/storage/local_storage_driver.py +37 -0
  17. griptape_nodes/drivers/storage/storage_backend.py +0 -0
  18. griptape_nodes/exe_types/__init__.py +0 -0
  19. griptape_nodes/exe_types/connections.py +0 -0
  20. griptape_nodes/exe_types/core_types.py +113 -8
  21. griptape_nodes/exe_types/flow.py +0 -0
  22. griptape_nodes/exe_types/node_types.py +1 -0
  23. griptape_nodes/exe_types/type_validator.py +0 -0
  24. griptape_nodes/machines/__init__.py +0 -0
  25. griptape_nodes/machines/control_flow.py +5 -4
  26. griptape_nodes/machines/fsm.py +0 -0
  27. griptape_nodes/machines/node_resolution.py +110 -74
  28. griptape_nodes/mcp_server/__init__.py +0 -0
  29. griptape_nodes/mcp_server/server.py +16 -8
  30. griptape_nodes/mcp_server/ws_request_manager.py +0 -0
  31. griptape_nodes/node_library/__init__.py +0 -0
  32. griptape_nodes/node_library/advanced_node_library.py +0 -0
  33. griptape_nodes/node_library/library_registry.py +0 -0
  34. griptape_nodes/node_library/workflow_registry.py +0 -0
  35. griptape_nodes/py.typed +0 -0
  36. griptape_nodes/retained_mode/__init__.py +0 -0
  37. griptape_nodes/retained_mode/events/__init__.py +0 -0
  38. griptape_nodes/retained_mode/events/agent_events.py +0 -0
  39. griptape_nodes/retained_mode/events/app_events.py +0 -6
  40. griptape_nodes/retained_mode/events/arbitrary_python_events.py +0 -0
  41. griptape_nodes/retained_mode/events/base_events.py +6 -7
  42. griptape_nodes/retained_mode/events/config_events.py +0 -0
  43. griptape_nodes/retained_mode/events/connection_events.py +0 -0
  44. griptape_nodes/retained_mode/events/context_events.py +0 -0
  45. griptape_nodes/retained_mode/events/execution_events.py +0 -0
  46. griptape_nodes/retained_mode/events/flow_events.py +2 -1
  47. griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
  48. griptape_nodes/retained_mode/events/library_events.py +0 -0
  49. griptape_nodes/retained_mode/events/logger_events.py +0 -0
  50. griptape_nodes/retained_mode/events/node_events.py +36 -0
  51. griptape_nodes/retained_mode/events/object_events.py +0 -0
  52. griptape_nodes/retained_mode/events/os_events.py +98 -6
  53. griptape_nodes/retained_mode/events/parameter_events.py +0 -0
  54. griptape_nodes/retained_mode/events/payload_registry.py +0 -0
  55. griptape_nodes/retained_mode/events/secrets_events.py +0 -0
  56. griptape_nodes/retained_mode/events/static_file_events.py +0 -0
  57. griptape_nodes/retained_mode/events/validation_events.py +0 -0
  58. griptape_nodes/retained_mode/events/workflow_events.py +0 -0
  59. griptape_nodes/retained_mode/griptape_nodes.py +1 -4
  60. griptape_nodes/retained_mode/managers/__init__.py +0 -0
  61. griptape_nodes/retained_mode/managers/agent_manager.py +0 -0
  62. griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
  63. griptape_nodes/retained_mode/managers/config_manager.py +1 -1
  64. griptape_nodes/retained_mode/managers/context_manager.py +0 -0
  65. griptape_nodes/retained_mode/managers/engine_identity_manager.py +0 -0
  66. griptape_nodes/retained_mode/managers/event_manager.py +0 -0
  67. griptape_nodes/retained_mode/managers/flow_manager.py +6 -0
  68. griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +0 -0
  69. griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +0 -0
  70. griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +0 -0
  71. griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +0 -0
  72. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +0 -0
  73. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +0 -0
  74. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +0 -0
  75. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +0 -0
  76. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +0 -0
  77. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +0 -0
  78. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +0 -0
  79. griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +0 -0
  80. griptape_nodes/retained_mode/managers/library_manager.py +2 -8
  81. griptape_nodes/retained_mode/managers/node_manager.py +76 -5
  82. griptape_nodes/retained_mode/managers/object_manager.py +0 -0
  83. griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
  84. griptape_nodes/retained_mode/managers/os_manager.py +133 -8
  85. griptape_nodes/retained_mode/managers/secrets_manager.py +0 -0
  86. griptape_nodes/retained_mode/managers/session_manager.py +0 -0
  87. griptape_nodes/retained_mode/managers/settings.py +0 -0
  88. griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
  89. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +0 -0
  90. griptape_nodes/retained_mode/managers/workflow_manager.py +54 -5
  91. griptape_nodes/retained_mode/retained_mode.py +0 -0
  92. griptape_nodes/retained_mode/utils/__init__.py +0 -0
  93. griptape_nodes/retained_mode/utils/engine_identity.py +0 -0
  94. griptape_nodes/retained_mode/utils/name_generator.py +0 -0
  95. griptape_nodes/traits/__init__.py +0 -0
  96. griptape_nodes/traits/add_param_button.py +0 -0
  97. griptape_nodes/traits/button.py +0 -0
  98. griptape_nodes/traits/clamp.py +0 -0
  99. griptape_nodes/traits/compare.py +0 -0
  100. griptape_nodes/traits/compare_images.py +0 -0
  101. griptape_nodes/traits/file_system_picker.py +18 -0
  102. griptape_nodes/traits/minmax.py +0 -0
  103. griptape_nodes/traits/options.py +0 -0
  104. griptape_nodes/traits/slider.py +0 -0
  105. griptape_nodes/traits/trait_registry.py +0 -0
  106. griptape_nodes/traits/traits.json +0 -0
  107. griptape_nodes/updater/__init__.py +0 -0
  108. griptape_nodes/updater/__main__.py +0 -0
  109. griptape_nodes/utils/__init__.py +0 -0
  110. griptape_nodes/utils/dict_utils.py +0 -0
  111. griptape_nodes/utils/image_preview.py +0 -0
  112. griptape_nodes/utils/metaclasses.py +0 -0
  113. griptape_nodes/utils/version_utils.py +51 -0
  114. griptape_nodes/version_compatibility/__init__.py +0 -0
  115. griptape_nodes/version_compatibility/versions/__init__.py +0 -0
  116. griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
  117. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +0 -0
  118. {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.44.0.dist-info}/METADATA +1 -1
  119. {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.44.0.dist-info}/RECORD +31 -39
  120. {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.44.0.dist-info}/WHEEL +1 -1
  121. griptape_nodes/bootstrap/bootstrap_script.py +0 -54
  122. griptape_nodes/bootstrap/post_build_install_script.sh +0 -3
  123. griptape_nodes/bootstrap/pre_build_install_script.sh +0 -4
  124. griptape_nodes/bootstrap/register_libraries_script.py +0 -32
  125. griptape_nodes/bootstrap/structure_config.yaml +0 -15
  126. griptape_nodes/bootstrap/workflow_runners/__init__.py +0 -1
  127. griptape_nodes/bootstrap/workflow_runners/bootstrap_workflow_runner.py +0 -28
  128. griptape_nodes/bootstrap/workflow_runners/local_workflow_runner.py +0 -237
  129. griptape_nodes/bootstrap/workflow_runners/subprocess_workflow_runner.py +0 -62
  130. griptape_nodes/bootstrap/workflow_runners/workflow_runner.py +0 -11
  131. {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.44.0.dist-info}/entry_points.txt +0 -0
@@ -6,7 +6,6 @@ console = Console()
6
6
 
7
7
  with console.status("Loading Griptape Nodes...") as status:
8
8
  import argparse
9
- import importlib.metadata
10
9
  import json
11
10
  import os
12
11
  import shutil
@@ -15,7 +14,7 @@ with console.status("Loading Griptape Nodes...") as status:
15
14
  import tempfile
16
15
  from dataclasses import dataclass
17
16
  from pathlib import Path
18
- from typing import Any, Literal
17
+ from typing import Any
19
18
 
20
19
  import httpx
21
20
  from rich.box import HEAVY_EDGE
@@ -28,10 +27,11 @@ with console.status("Loading Griptape Nodes...") as status:
28
27
  from griptape_nodes.app import start_app
29
28
  from griptape_nodes.drivers.storage import StorageBackend
30
29
  from griptape_nodes.drivers.storage.griptape_cloud_storage_driver import GriptapeCloudStorageDriver
31
- from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes, engine_version
30
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
32
31
  from griptape_nodes.retained_mode.managers.config_manager import ConfigManager
33
32
  from griptape_nodes.retained_mode.managers.os_manager import OSManager
34
33
  from griptape_nodes.retained_mode.managers.secrets_manager import SecretsManager
34
+ from griptape_nodes.utils.version_utils import get_complete_version_string, get_current_version, get_install_source
35
35
 
36
36
  CONFIG_DIR = xdg_config_home() / "griptape_nodes"
37
37
  DATA_DIR = xdg_data_home() / "griptape_nodes"
@@ -330,7 +330,12 @@ def _get_args() -> argparse.Namespace:
330
330
  metavar="SUBCOMMAND",
331
331
  required=True,
332
332
  )
333
- config_subparsers.add_parser("show", help="Show configuration values.")
333
+ config_show_parser = config_subparsers.add_parser("show", help="Show configuration values.")
334
+ config_show_parser.add_argument(
335
+ "config_path",
336
+ nargs="?",
337
+ help="Optional config path to show specific value (e.g., 'workspace_directory').",
338
+ )
334
339
  config_subparsers.add_parser("list", help="List configuration values.")
335
340
  config_subparsers.add_parser("reset", help="Reset configuration to defaults.")
336
341
 
@@ -609,7 +614,7 @@ def _get_latest_version(package: str, install_source: str) -> str:
609
614
  return f"v{data['info']['version']}"
610
615
  except httpx.HTTPStatusError as e:
611
616
  console.print(f"[red]Error fetching latest version: {e}[/red]")
612
- return __get_current_version()
617
+ return get_current_version()
613
618
  elif install_source == "git":
614
619
  # We only install auto updating from the 'latest' tag
615
620
  revision = LATEST_TAG
@@ -624,20 +629,20 @@ def _get_latest_version(package: str, install_source: str) -> str:
624
629
  if "object" in data and "sha" in data["object"]:
625
630
  return data["object"]["sha"][:7]
626
631
  # Should not happen, but if it does, return the current version
627
- return __get_current_version()
632
+ return get_current_version()
628
633
  except httpx.HTTPStatusError as e:
629
634
  console.print(f"[red]Error fetching latest version: {e}[/red]")
630
- return __get_current_version()
635
+ return get_current_version()
631
636
  else:
632
637
  # If the package is installed from a file, just return the current version since the user is likely managing it manually
633
- return __get_current_version()
638
+ return get_current_version()
634
639
 
635
640
 
636
641
  def _auto_update_self() -> None:
637
642
  """Automatically updates the script to the latest version if the user confirms."""
638
643
  console.print("[bold green]Checking for updates...[/bold green]")
639
- source, commit_id = __get_install_source()
640
- current_version = __get_current_version()
644
+ source, commit_id = get_install_source()
645
+ current_version = get_current_version()
641
646
  latest_version = _get_latest_version(PACKAGE_NAME, source)
642
647
 
643
648
  if source == "git" and commit_id is not None:
@@ -663,10 +668,10 @@ def _update_self() -> None:
663
668
 
664
669
  def _sync_libraries() -> None:
665
670
  """Download and sync Griptape Nodes libraries, copying only directories from synced libraries."""
666
- install_source, _ = __get_install_source()
671
+ install_source, _ = get_install_source()
667
672
  # Unless we're installed from PyPi, grab libraries from the 'latest' tag
668
673
  if install_source == "pypi":
669
- version = __get_current_version()
674
+ version = get_current_version()
670
675
  else:
671
676
  version = LATEST_TAG
672
677
 
@@ -681,12 +686,13 @@ def _sync_libraries() -> None:
681
686
 
682
687
  # Streaming download with a tiny progress bar
683
688
  with httpx.stream("GET", tar_url, follow_redirects=True) as r, Progress() as progress:
689
+ task = progress.add_task("[green]Downloading...", total=int(r.headers.get("Content-Length", 0)))
690
+ progress.start()
684
691
  try:
685
692
  r.raise_for_status()
686
693
  except httpx.HTTPStatusError as e:
687
694
  console.print(f"[red]Error fetching libraries: {e}[/red]")
688
695
  return
689
- task = progress.add_task("[green]Downloading...", total=int(r.headers.get("Content-Length", 0)))
690
696
  with tar_path.open("wb") as f:
691
697
  for chunk in r.iter_bytes():
692
698
  f.write(chunk)
@@ -724,18 +730,29 @@ def _sync_libraries() -> None:
724
730
 
725
731
  def _print_current_version() -> None:
726
732
  """Prints the current version of the script."""
727
- version = __get_current_version()
728
- source, commit_id = __get_install_source()
729
- if commit_id is None:
730
- console.print(f"[bold green]{version} ({source})[/bold green]")
731
- else:
732
- console.print(f"[bold green]{version} ({source} - {commit_id})[/bold green]")
733
+ version_string = get_complete_version_string()
734
+ console.print(f"[bold green]{version_string}[/bold green]")
733
735
 
734
736
 
735
- def _print_user_config() -> None:
736
- """Prints the user configuration from the config file."""
737
- config = config_manager.merged_config
738
- sys.stdout.write(json.dumps(config, indent=2))
737
+ def _print_user_config(config_path: str | None = None) -> None:
738
+ """Prints the user configuration from the config file.
739
+
740
+ Args:
741
+ config_path: Optional path to specific config value. If None, prints entire config.
742
+ """
743
+ if config_path is None:
744
+ config = config_manager.merged_config
745
+ sys.stdout.write(json.dumps(config, indent=2))
746
+ else:
747
+ try:
748
+ value = config_manager.get_config_value(config_path)
749
+ if isinstance(value, (dict, list)):
750
+ sys.stdout.write(json.dumps(value, indent=2))
751
+ else:
752
+ sys.stdout.write(str(value))
753
+ except (KeyError, AttributeError, ValueError):
754
+ console.print(f"[bold red]Config path '{config_path}' not found[/bold red]")
755
+ sys.exit(1)
739
756
 
740
757
 
741
758
  def _list_user_configs() -> None:
@@ -857,7 +874,7 @@ def _process_args(args: argparse.Namespace) -> None: # noqa: C901, PLR0912
857
874
  elif args.subcommand == "reset":
858
875
  _reset_user_config()
859
876
  elif args.subcommand == "show":
860
- _print_user_config()
877
+ _print_user_config(args.config_path)
861
878
  elif args.command == "self":
862
879
  if args.subcommand == "update":
863
880
  _update_self()
@@ -873,33 +890,6 @@ def _process_args(args: argparse.Namespace) -> None: # noqa: C901, PLR0912
873
890
  raise ValueError(msg)
874
891
 
875
892
 
876
- def __get_current_version() -> str:
877
- """Returns the current version of the Griptape Nodes package."""
878
- return f"v{engine_version}"
879
-
880
-
881
- def __get_install_source() -> tuple[Literal["git", "file", "pypi"], str | None]:
882
- """Determines the install source of the Griptape Nodes package.
883
-
884
- Returns:
885
- tuple: A tuple containing the install source and commit ID (if applicable).
886
- """
887
- dist = importlib.metadata.distribution("griptape_nodes")
888
- direct_url_text = dist.read_text("direct_url.json")
889
- # installing from pypi doesn't have a direct_url.json file
890
- if direct_url_text is None:
891
- return "pypi", None
892
-
893
- direct_url_info = json.loads(direct_url_text)
894
- url = direct_url_info.get("url")
895
- if url.startswith("file://"):
896
- return "file", None
897
- if "vcs_info" in direct_url_info:
898
- return "git", direct_url_info["vcs_info"].get("commit_id")[:7]
899
- # Fall back to pypi if no other source is found
900
- return "pypi", None
901
-
902
-
903
893
  def __init_system_config() -> None:
904
894
  """Initializes the system config directory if it doesn't exist."""
905
895
  if not CONFIG_DIR.exists():
File without changes
File without changes
griptape_nodes/app/api.py CHANGED
@@ -27,9 +27,11 @@ STATIC_SERVER_PORT = int(os.getenv("STATIC_SERVER_PORT", "8124"))
27
27
  # URL path for the static server
28
28
  STATIC_SERVER_URL = os.getenv("STATIC_SERVER_URL", "/static")
29
29
  # Log level for the static server
30
- STATIC_SERVER_LOG_LEVEL = os.getenv("STATIC_SERVER_LOG_LEVEL", "info").lower()
30
+ STATIC_SERVER_LOG_LEVEL = os.getenv("STATIC_SERVER_LOG_LEVEL", "ERROR").lower()
31
31
 
32
32
  logger = logging.getLogger("griptape_nodes_api")
33
+ logging.getLogger("uvicorn").addHandler(RichHandler(show_time=True, show_path=False, markup=True, rich_tracebacks=True))
34
+
33
35
 
34
36
  # Global event queue - initialized as None and set when starting the API
35
37
  event_queue: Queue | None = None
@@ -124,6 +126,37 @@ async def _list_static_files(static_directory: Annotated[Path, Depends(get_stati
124
126
  return {"files": file_names}
125
127
 
126
128
 
129
+ @app.delete("/static-files/{file_path:path}")
130
+ async def _delete_static_file(file_path: str, static_directory: Annotated[Path, Depends(get_static_dir)]) -> dict:
131
+ """Delete a static file from the static server."""
132
+ if not STATIC_SERVER_ENABLED:
133
+ msg = "Static server is not enabled. Please set STATIC_SERVER_ENABLED to True."
134
+ raise HTTPException(status_code=500, detail=msg)
135
+
136
+ file_full_path = Path(static_directory / file_path)
137
+
138
+ # Check if file exists
139
+ if not file_full_path.exists():
140
+ logger.warning("File not found for deletion: %s", file_path)
141
+ raise HTTPException(status_code=404, detail=f"File {file_path} not found")
142
+
143
+ # Check if it's actually a file (not a directory)
144
+ if not file_full_path.is_file():
145
+ msg = f"Path {file_path} is not a file"
146
+ logger.error(msg)
147
+ raise HTTPException(status_code=400, detail=msg)
148
+
149
+ try:
150
+ file_full_path.unlink()
151
+ except (OSError, PermissionError) as e:
152
+ msg = f"Failed to delete file {file_path}: {e}"
153
+ logger.error(msg)
154
+ raise HTTPException(status_code=500, detail=msg) from e
155
+ else:
156
+ logger.info("Successfully deleted static file: %s", file_path)
157
+ return {"message": f"File {file_path} deleted successfully"}
158
+
159
+
127
160
  @app.post("/engines/request")
128
161
  async def _create_event(request: Request, queue: Annotated[Queue, Depends(get_event_queue)]) -> None:
129
162
  body = await request.json()
@@ -136,10 +169,6 @@ def start_api(static_directory: Path, queue: Queue) -> None:
136
169
  event_queue = queue
137
170
  static_dir = static_directory
138
171
 
139
- logging.getLogger("uvicorn").addHandler(
140
- RichHandler(show_time=True, show_path=False, markup=True, rich_tracebacks=True)
141
- )
142
-
143
172
  if not static_dir.exists():
144
173
  static_dir.mkdir(parents=True, exist_ok=True)
145
174
 
@@ -151,7 +180,7 @@ def start_api(static_directory: Path, queue: Queue) -> None:
151
180
  "http://localhost:5173",
152
181
  ],
153
182
  allow_credentials=True,
154
- allow_methods=["OPTIONS", "GET", "POST", "PUT"],
183
+ allow_methods=["OPTIONS", "GET", "POST", "PUT", "DELETE"],
155
184
  allow_headers=["*"],
156
185
  )
157
186
 
griptape_nodes/app/app.py CHANGED
File without changes
File without changes
File without changes
File without changes
@@ -144,7 +144,13 @@ class LocalWorkflowExecutor(WorkflowExecutor):
144
144
 
145
145
  return output
146
146
 
147
- def run(self, workflow_name: str, flow_input: Any, storage_backend: StorageBackend = StorageBackend.LOCAL) -> None:
147
+ def run(
148
+ self,
149
+ workflow_name: str,
150
+ flow_input: Any,
151
+ storage_backend: StorageBackend = StorageBackend.LOCAL,
152
+ **kwargs: Any, # noqa: ARG002
153
+ ) -> None:
148
154
  """Executes a local workflow.
149
155
 
150
156
  Executes a workflow by setting up event listeners, registering libraries,
@@ -0,0 +1,90 @@
1
+ import importlib.util
2
+ import sys
3
+ import threading
4
+ from multiprocessing import Process, Queue
5
+ from multiprocessing import Queue as ProcessQueue
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from griptape_nodes.app.api import start_api
10
+ from griptape_nodes.app.app import _build_static_dir
11
+ from griptape_nodes.bootstrap.workflow_executors.local_workflow_executor import LocalWorkflowExecutor
12
+ from griptape_nodes.bootstrap.workflow_executors.workflow_executor import WorkflowExecutor
13
+ from griptape_nodes.drivers.storage.storage_backend import StorageBackend
14
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
15
+
16
+
17
+ class SubprocessWorkflowExecutor(WorkflowExecutor):
18
+ @classmethod
19
+ def load_workflow(cls, path_to_workflow: str) -> None:
20
+ """Load a workflow from a file."""
21
+ # Ensure file_path is a Path object
22
+ file_path = Path(path_to_workflow)
23
+
24
+ # Generate a unique module name
25
+ module_name = f"gtn_dynamic_module_{file_path.name.replace('.', '_')}_{hash(str(file_path))}"
26
+
27
+ # Load the module specification
28
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
29
+ if spec is None or spec.loader is None:
30
+ msg = f"Could not load module specification from {file_path}"
31
+ raise ImportError(msg)
32
+
33
+ # Create the module
34
+ module = importlib.util.module_from_spec(spec)
35
+
36
+ # Add to sys.modules to handle recursive imports
37
+ sys.modules[module_name] = module
38
+
39
+ # Execute the module
40
+ spec.loader.exec_module(module)
41
+
42
+ @staticmethod
43
+ def _subprocess_entry(
44
+ exception_queue: Queue,
45
+ workflow_name: str,
46
+ flow_input: Any,
47
+ workflow_path: str | None = None,
48
+ ) -> None:
49
+ try:
50
+ static_dir = _build_static_dir()
51
+ event_queue = ProcessQueue()
52
+ threading.Thread(target=start_api, args=(static_dir, event_queue), daemon=True).start()
53
+
54
+ if workflow_path:
55
+ SubprocessWorkflowExecutor.load_workflow(workflow_path)
56
+ context_manager = GriptapeNodes.ContextManager()
57
+ workflow_name = context_manager.get_current_workflow_name()
58
+
59
+ workflow_runner = LocalWorkflowExecutor()
60
+ workflow_runner.run(workflow_name, flow_input, StorageBackend.LOCAL)
61
+ except Exception as e:
62
+ exception_queue.put(e)
63
+ raise
64
+
65
+ def run(
66
+ self,
67
+ workflow_name: str,
68
+ flow_input: Any,
69
+ storage_backend: StorageBackend = StorageBackend.LOCAL, # noqa: ARG002
70
+ **kwargs: Any,
71
+ ) -> None:
72
+ workflow_path = kwargs.get("workflow_path")
73
+ exception_queue = Queue()
74
+ process = Process(
75
+ target=self._subprocess_entry,
76
+ args=(exception_queue, workflow_name, flow_input, workflow_path),
77
+ )
78
+ process.start()
79
+ process.join()
80
+
81
+ if not exception_queue.empty():
82
+ exception = exception_queue.get_nowait()
83
+ if isinstance(exception, Exception):
84
+ raise exception
85
+ msg = f"Expected an Exception but got: {type(exception)}"
86
+ raise RuntimeError(msg)
87
+
88
+ if process.exitcode != 0:
89
+ msg = f"Process exited with code {process.exitcode} but no exception was raised."
90
+ raise RuntimeError(msg)
@@ -9,5 +9,11 @@ logger = logging.getLogger(__name__)
9
9
 
10
10
  class WorkflowExecutor(ABC):
11
11
  @abstractmethod
12
- def run(self, workflow_name: str, flow_input: Any, storage_backend: StorageBackend = StorageBackend.LOCAL) -> None:
12
+ def run(
13
+ self,
14
+ workflow_name: str,
15
+ flow_input: Any,
16
+ storage_backend: StorageBackend = StorageBackend.LOCAL,
17
+ **kwargs: Any,
18
+ ) -> None:
13
19
  pass
File without changes
File without changes
@@ -1,6 +1,11 @@
1
+ import logging
1
2
  from abc import ABC, abstractmethod
2
3
  from typing import TypedDict
3
4
 
5
+ import httpx
6
+
7
+ logger = logging.getLogger("griptape_nodes")
8
+
4
9
 
5
10
  class CreateSignedUploadUrlResponse(TypedDict):
6
11
  """Response type for create_signed_upload_url method."""
@@ -36,3 +41,51 @@ class BaseStorageDriver(ABC):
36
41
  str: The signed URL for downloading the file.
37
42
  """
38
43
  ...
44
+
45
+ @abstractmethod
46
+ def delete_file(self, file_name: str) -> None:
47
+ """Delete a file from storage.
48
+
49
+ Args:
50
+ file_name: The name of the file to delete.
51
+ """
52
+ ...
53
+
54
+ @abstractmethod
55
+ def list_files(self) -> list[str]:
56
+ """List all files in storage.
57
+
58
+ Returns:
59
+ A list of file names in storage.
60
+ """
61
+ ...
62
+
63
+ def download_file(self, file_name: str) -> bytes:
64
+ """Download a file from the bucket.
65
+
66
+ Args:
67
+ file_name: The name of the file to download.
68
+
69
+ Returns:
70
+ The file content as bytes.
71
+
72
+ Raises:
73
+ RuntimeError: If file download fails.
74
+ """
75
+ try:
76
+ # Get signed download URL
77
+ download_url = self.create_signed_download_url(file_name)
78
+
79
+ # Download the file
80
+ response = httpx.get(download_url)
81
+ response.raise_for_status()
82
+ except httpx.HTTPStatusError as e:
83
+ msg = f"Failed to download file {file_name}: {e}"
84
+ logger.error(msg)
85
+ raise RuntimeError(msg) from e
86
+ except Exception as e:
87
+ msg = f"Unexpected error downloading file {file_name}: {e}"
88
+ logger.error(msg)
89
+ raise RuntimeError(msg) from e
90
+ else:
91
+ return response.content
@@ -76,8 +76,7 @@ class GriptapeCloudStorageDriver(BaseStorageDriver):
76
76
  return {"url": response_data["url"], "headers": response_data.get("headers", {}), "method": "PUT"}
77
77
 
78
78
  def create_signed_download_url(self, file_name: str) -> str:
79
- full_file_path = self._get_full_file_path(file_name)
80
- url = urljoin(self.base_url, f"/api/buckets/{self.bucket_id}/asset-urls/{full_file_path}")
79
+ url = urljoin(self.base_url, f"/api/buckets/{self.bucket_id}/asset-urls/{file_name}")
81
80
  try:
82
81
  response = httpx.post(url, json={"method": "GET"}, headers=self.headers)
83
82
  response.raise_for_status()
@@ -135,6 +134,37 @@ class GriptapeCloudStorageDriver(BaseStorageDriver):
135
134
  logger.info("Created new Griptape Cloud bucket '%s' with ID: %s", bucket_name, bucket_id)
136
135
  return bucket_id
137
136
 
137
+ def list_files(self) -> list[str]:
138
+ """List all files in storage.
139
+
140
+ Returns:
141
+ A list of file names in storage.
142
+
143
+ Raises:
144
+ RuntimeError: If file listing fails.
145
+ """
146
+ url = urljoin(self.base_url, f"/api/buckets/{self.bucket_id}/assets")
147
+ try:
148
+ response = httpx.get(url, headers=self.headers, params={"prefix": self.static_files_directory or ""})
149
+ response.raise_for_status()
150
+ except httpx.HTTPStatusError as e:
151
+ msg = f"Failed to list files in bucket {self.bucket_id}: {e}"
152
+ logger.error(msg)
153
+ raise RuntimeError(msg) from e
154
+
155
+ response_data = response.json()
156
+ assets = response_data.get("assets", [])
157
+
158
+ file_names = []
159
+ for asset in assets:
160
+ name = asset.get("name", "")
161
+ # Remove the static files directory prefix if it exists
162
+ if self.static_files_directory and name.startswith(f"{self.static_files_directory}/"):
163
+ name = name[len(f"{self.static_files_directory}/") :]
164
+ file_names.append(name)
165
+
166
+ return file_names
167
+
138
168
  @staticmethod
139
169
  def list_buckets(*, base_url: str, api_key: str) -> list[dict]:
140
170
  """List all buckets in Griptape Cloud.
@@ -158,3 +188,20 @@ class GriptapeCloudStorageDriver(BaseStorageDriver):
158
188
  raise RuntimeError(msg) from e
159
189
 
160
190
  return response.json().get("buckets", [])
191
+
192
+ def delete_file(self, file_name: str) -> None:
193
+ """Delete a file from the bucket.
194
+
195
+ Args:
196
+ file_name: The name of the file to delete.
197
+ """
198
+ full_file_path = self._get_full_file_path(file_name)
199
+ url = urljoin(self.base_url, f"/api/buckets/{self.bucket_id}/assets/{full_file_path}")
200
+
201
+ try:
202
+ response = httpx.delete(url, headers=self.headers)
203
+ response.raise_for_status()
204
+ except httpx.HTTPStatusError as e:
205
+ msg = f"Failed to delete file {file_name}: {e}"
206
+ logger.error(msg)
207
+ raise RuntimeError(msg) from e
@@ -53,3 +53,40 @@ class LocalStorageDriver(BaseStorageDriver):
53
53
  # Add a cache-busting query parameter to the URL so that the browser always reloads the file
54
54
  cache_busted_url = f"{url}?t={int(time.time())}"
55
55
  return cache_busted_url
56
+
57
+ def delete_file(self, file_name: str) -> None:
58
+ """Delete a file from local storage.
59
+
60
+ Args:
61
+ file_name: The name of the file to delete.
62
+ """
63
+ # Use the static server's delete endpoint
64
+ delete_url = urljoin(self.base_url, f"/static-files/{file_name}")
65
+
66
+ try:
67
+ response = httpx.delete(delete_url)
68
+ response.raise_for_status()
69
+ except httpx.HTTPStatusError as e:
70
+ msg = f"Failed to delete file {file_name}: {e}"
71
+ logger.error(msg)
72
+ raise RuntimeError(msg) from e
73
+
74
+ def list_files(self) -> list[str]:
75
+ """List all files in local storage.
76
+
77
+ Returns:
78
+ A list of file names in storage.
79
+ """
80
+ # Use the static server's list endpoint
81
+ list_url = urljoin(self.base_url, "/static-uploads/")
82
+
83
+ try:
84
+ response = httpx.get(list_url)
85
+ response.raise_for_status()
86
+ except httpx.HTTPStatusError as e:
87
+ msg = f"Failed to list files: {e}"
88
+ logger.error(msg)
89
+ raise RuntimeError(msg) from e
90
+
91
+ response_data = response.json()
92
+ return response_data.get("files", [])
File without changes
File without changes
File without changes