griptape-nodes 0.43.1__tar.gz → 0.45.0__tar.gz

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 (134) hide show
  1. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/PKG-INFO +2 -1
  2. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/pyproject.toml +2 -1
  3. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/__init__.py +46 -52
  4. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/app/api.py +37 -41
  5. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/app/app.py +70 -3
  6. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/app/watch.py +5 -2
  7. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +7 -1
  8. griptape_nodes-0.45.0/src/griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +90 -0
  9. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +7 -1
  10. griptape_nodes-0.45.0/src/griptape_nodes/drivers/storage/base_storage_driver.py +128 -0
  11. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +48 -0
  12. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/drivers/storage/local_storage_driver.py +37 -0
  13. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/exe_types/core_types.py +222 -17
  14. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/exe_types/node_types.py +20 -5
  15. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/machines/control_flow.py +5 -4
  16. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/machines/node_resolution.py +110 -74
  17. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/mcp_server/server.py +16 -8
  18. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/node_library/workflow_registry.py +29 -0
  19. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/app_events.py +3 -8
  20. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/base_events.py +15 -7
  21. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/flow_events.py +2 -1
  22. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/node_events.py +36 -0
  23. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/os_events.py +98 -6
  24. griptape_nodes-0.45.0/src/griptape_nodes/retained_mode/events/sync_events.py +60 -0
  25. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/workflow_events.py +231 -0
  26. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/griptape_nodes.py +9 -4
  27. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/config_manager.py +1 -1
  28. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/flow_manager.py +6 -0
  29. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/library_manager.py +8 -26
  30. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/node_manager.py +78 -7
  31. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/operation_manager.py +7 -0
  32. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/os_manager.py +133 -8
  33. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/settings.py +5 -0
  34. griptape_nodes-0.45.0/src/griptape_nodes/retained_mode/managers/sync_manager.py +498 -0
  35. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/workflow_manager.py +736 -33
  36. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/retained_mode.py +23 -0
  37. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/traits/file_system_picker.py +18 -0
  38. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/updater/__init__.py +4 -2
  39. griptape_nodes-0.45.0/src/griptape_nodes/utils/uv_utils.py +18 -0
  40. griptape_nodes-0.45.0/src/griptape_nodes/utils/version_utils.py +51 -0
  41. griptape_nodes-0.43.1/src/griptape_nodes/bootstrap/bootstrap_script.py +0 -54
  42. griptape_nodes-0.43.1/src/griptape_nodes/bootstrap/post_build_install_script.sh +0 -3
  43. griptape_nodes-0.43.1/src/griptape_nodes/bootstrap/pre_build_install_script.sh +0 -4
  44. griptape_nodes-0.43.1/src/griptape_nodes/bootstrap/register_libraries_script.py +0 -32
  45. griptape_nodes-0.43.1/src/griptape_nodes/bootstrap/structure_config.yaml +0 -15
  46. griptape_nodes-0.43.1/src/griptape_nodes/bootstrap/workflow_runners/__init__.py +0 -1
  47. griptape_nodes-0.43.1/src/griptape_nodes/bootstrap/workflow_runners/bootstrap_workflow_runner.py +0 -28
  48. griptape_nodes-0.43.1/src/griptape_nodes/bootstrap/workflow_runners/local_workflow_runner.py +0 -237
  49. griptape_nodes-0.43.1/src/griptape_nodes/bootstrap/workflow_runners/subprocess_workflow_runner.py +0 -62
  50. griptape_nodes-0.43.1/src/griptape_nodes/bootstrap/workflow_runners/workflow_runner.py +0 -11
  51. griptape_nodes-0.43.1/src/griptape_nodes/drivers/storage/base_storage_driver.py +0 -38
  52. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/README.md +0 -0
  53. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/app/.python-version +0 -0
  54. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/app/__init__.py +0 -0
  55. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/bootstrap/__init__.py +0 -0
  56. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
  57. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/drivers/__init__.py +0 -0
  58. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/drivers/storage/__init__.py +0 -0
  59. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/drivers/storage/storage_backend.py +0 -0
  60. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/exe_types/__init__.py +0 -0
  61. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/exe_types/connections.py +0 -0
  62. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/exe_types/flow.py +0 -0
  63. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/exe_types/type_validator.py +0 -0
  64. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/machines/__init__.py +0 -0
  65. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/machines/fsm.py +0 -0
  66. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/mcp_server/__init__.py +0 -0
  67. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/mcp_server/ws_request_manager.py +0 -0
  68. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/node_library/__init__.py +0 -0
  69. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/node_library/advanced_node_library.py +0 -0
  70. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/node_library/library_registry.py +0 -0
  71. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/py.typed +0 -0
  72. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/__init__.py +0 -0
  73. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/__init__.py +0 -0
  74. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/agent_events.py +0 -0
  75. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/arbitrary_python_events.py +0 -0
  76. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/config_events.py +0 -0
  77. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/connection_events.py +0 -0
  78. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/context_events.py +0 -0
  79. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/execution_events.py +0 -0
  80. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
  81. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/library_events.py +0 -0
  82. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/logger_events.py +0 -0
  83. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/object_events.py +0 -0
  84. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/parameter_events.py +0 -0
  85. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/payload_registry.py +0 -0
  86. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/secrets_events.py +0 -0
  87. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/static_file_events.py +0 -0
  88. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/events/validation_events.py +0 -0
  89. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/__init__.py +0 -0
  90. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/agent_manager.py +0 -0
  91. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
  92. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/context_manager.py +0 -0
  93. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/engine_identity_manager.py +0 -0
  94. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/event_manager.py +0 -0
  95. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +0 -0
  96. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +0 -0
  97. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +0 -0
  98. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +0 -0
  99. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +0 -0
  100. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +0 -0
  101. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +0 -0
  102. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +0 -0
  103. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +0 -0
  104. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +0 -0
  105. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +0 -0
  106. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +0 -0
  107. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/object_manager.py +0 -0
  108. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/secrets_manager.py +0 -0
  109. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/session_manager.py +0 -0
  110. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
  111. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/managers/version_compatibility_manager.py +0 -0
  112. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/utils/__init__.py +0 -0
  113. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/utils/engine_identity.py +0 -0
  114. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/retained_mode/utils/name_generator.py +0 -0
  115. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/traits/__init__.py +0 -0
  116. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/traits/add_param_button.py +0 -0
  117. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/traits/button.py +0 -0
  118. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/traits/clamp.py +0 -0
  119. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/traits/compare.py +0 -0
  120. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/traits/compare_images.py +0 -0
  121. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/traits/minmax.py +0 -0
  122. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/traits/options.py +0 -0
  123. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/traits/slider.py +0 -0
  124. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/traits/trait_registry.py +0 -0
  125. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/traits/traits.json +0 -0
  126. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/updater/__main__.py +0 -0
  127. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/utils/__init__.py +0 -0
  128. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/utils/dict_utils.py +0 -0
  129. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/utils/image_preview.py +0 -0
  130. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/utils/metaclasses.py +0 -0
  131. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/version_compatibility/__init__.py +0 -0
  132. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/version_compatibility/versions/__init__.py +0 -0
  133. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
  134. {griptape_nodes-0.43.1 → griptape_nodes-0.45.0}/src/griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: griptape-nodes
3
- Version: 0.43.1
3
+ Version: 0.45.0
4
4
  Summary: Add your description here
5
5
  Requires-Dist: griptape>=1.8.0
6
6
  Requires-Dist: pydantic>=2.10.6
@@ -19,6 +19,7 @@ Requires-Dist: imageio-ffmpeg>=0.6.0
19
19
  Requires-Dist: mcp[ws]>=1.10.1
20
20
  Requires-Dist: binaryornot>=0.4.4
21
21
  Requires-Dist: pillow>=11.3.0
22
+ Requires-Dist: watchfiles>=1.1.0
22
23
  Requires-Dist: austin-dist>=3.7.0 ; extra == 'profiling'
23
24
  Requires-Python: >=3.12.0, <3.13
24
25
  Provides-Extra: profiling
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "griptape-nodes"
3
- version = "0.43.1"
3
+ version = "0.45.0"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12.0, <3.13"
@@ -23,6 +23,7 @@ dependencies = [
23
23
  "mcp[ws]>=1.10.1",
24
24
  "binaryornot>=0.4.4",
25
25
  "pillow>=11.3.0",
26
+ "watchfiles>=1.1.0",
26
27
  ]
27
28
 
28
29
  [project.optional-dependencies]
@@ -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,12 @@ 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.uv_utils import find_uv_bin
35
+ from griptape_nodes.utils.version_utils import get_complete_version_string, get_current_version, get_install_source
35
36
 
36
37
  CONFIG_DIR = xdg_config_home() / "griptape_nodes"
37
38
  DATA_DIR = xdg_data_home() / "griptape_nodes"
@@ -330,7 +331,12 @@ def _get_args() -> argparse.Namespace:
330
331
  metavar="SUBCOMMAND",
331
332
  required=True,
332
333
  )
333
- config_subparsers.add_parser("show", help="Show configuration values.")
334
+ config_show_parser = config_subparsers.add_parser("show", help="Show configuration values.")
335
+ config_show_parser.add_argument(
336
+ "config_path",
337
+ nargs="?",
338
+ help="Optional config path to show specific value (e.g., 'workspace_directory').",
339
+ )
334
340
  config_subparsers.add_parser("list", help="List configuration values.")
335
341
  config_subparsers.add_parser("reset", help="Reset configuration to defaults.")
336
342
 
@@ -609,7 +615,7 @@ def _get_latest_version(package: str, install_source: str) -> str:
609
615
  return f"v{data['info']['version']}"
610
616
  except httpx.HTTPStatusError as e:
611
617
  console.print(f"[red]Error fetching latest version: {e}[/red]")
612
- return __get_current_version()
618
+ return get_current_version()
613
619
  elif install_source == "git":
614
620
  # We only install auto updating from the 'latest' tag
615
621
  revision = LATEST_TAG
@@ -624,20 +630,20 @@ def _get_latest_version(package: str, install_source: str) -> str:
624
630
  if "object" in data and "sha" in data["object"]:
625
631
  return data["object"]["sha"][:7]
626
632
  # Should not happen, but if it does, return the current version
627
- return __get_current_version()
633
+ return get_current_version()
628
634
  except httpx.HTTPStatusError as e:
629
635
  console.print(f"[red]Error fetching latest version: {e}[/red]")
630
- return __get_current_version()
636
+ return get_current_version()
631
637
  else:
632
638
  # 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()
639
+ return get_current_version()
634
640
 
635
641
 
636
642
  def _auto_update_self() -> None:
637
643
  """Automatically updates the script to the latest version if the user confirms."""
638
644
  console.print("[bold green]Checking for updates...[/bold green]")
639
- source, commit_id = __get_install_source()
640
- current_version = __get_current_version()
645
+ source, commit_id = get_install_source()
646
+ current_version = get_current_version()
641
647
  latest_version = _get_latest_version(PACKAGE_NAME, source)
642
648
 
643
649
  if source == "git" and commit_id is not None:
@@ -663,10 +669,10 @@ def _update_self() -> None:
663
669
 
664
670
  def _sync_libraries() -> None:
665
671
  """Download and sync Griptape Nodes libraries, copying only directories from synced libraries."""
666
- install_source, _ = __get_install_source()
672
+ install_source, _ = get_install_source()
667
673
  # Unless we're installed from PyPi, grab libraries from the 'latest' tag
668
674
  if install_source == "pypi":
669
- version = __get_current_version()
675
+ version = get_current_version()
670
676
  else:
671
677
  version = LATEST_TAG
672
678
 
@@ -681,12 +687,13 @@ def _sync_libraries() -> None:
681
687
 
682
688
  # Streaming download with a tiny progress bar
683
689
  with httpx.stream("GET", tar_url, follow_redirects=True) as r, Progress() as progress:
690
+ task = progress.add_task("[green]Downloading...", total=int(r.headers.get("Content-Length", 0)))
691
+ progress.start()
684
692
  try:
685
693
  r.raise_for_status()
686
694
  except httpx.HTTPStatusError as e:
687
695
  console.print(f"[red]Error fetching libraries: {e}[/red]")
688
696
  return
689
- task = progress.add_task("[green]Downloading...", total=int(r.headers.get("Content-Length", 0)))
690
697
  with tar_path.open("wb") as f:
691
698
  for chunk in r.iter_bytes():
692
699
  f.write(chunk)
@@ -724,18 +731,29 @@ def _sync_libraries() -> None:
724
731
 
725
732
  def _print_current_version() -> None:
726
733
  """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]")
734
+ version_string = get_complete_version_string()
735
+ console.print(f"[bold green]{version_string}[/bold green]")
736
+
733
737
 
738
+ def _print_user_config(config_path: str | None = None) -> None:
739
+ """Prints the user configuration from the config file.
734
740
 
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))
741
+ Args:
742
+ config_path: Optional path to specific config value. If None, prints entire config.
743
+ """
744
+ if config_path is None:
745
+ config = config_manager.merged_config
746
+ sys.stdout.write(json.dumps(config, indent=2))
747
+ else:
748
+ try:
749
+ value = config_manager.get_config_value(config_path)
750
+ if isinstance(value, (dict, list)):
751
+ sys.stdout.write(json.dumps(value, indent=2))
752
+ else:
753
+ sys.stdout.write(str(value))
754
+ except (KeyError, AttributeError, ValueError):
755
+ console.print(f"[bold red]Config path '{config_path}' not found[/bold red]")
756
+ sys.exit(1)
739
757
 
740
758
 
741
759
  def _list_user_configs() -> None:
@@ -791,7 +809,10 @@ def _uninstall_self() -> None:
791
809
  # Remove the executable
792
810
  console.print("[bold]Removing the executable...[/bold]")
793
811
  console.print("[bold yellow]When done, press Enter to exit.[/bold yellow]")
794
- os_manager.replace_process(["uv", "tool", "uninstall", "griptape-nodes"])
812
+
813
+ # Remove the tool using UV
814
+ uv_path = find_uv_bin()
815
+ os_manager.replace_process([uv_path, "tool", "uninstall", "griptape-nodes"])
795
816
 
796
817
 
797
818
  def _parse_key_value_pairs(pairs: list[str] | None) -> dict[str, Any] | None:
@@ -857,7 +878,7 @@ def _process_args(args: argparse.Namespace) -> None: # noqa: C901, PLR0912
857
878
  elif args.subcommand == "reset":
858
879
  _reset_user_config()
859
880
  elif args.subcommand == "show":
860
- _print_user_config()
881
+ _print_user_config(args.config_path)
861
882
  elif args.command == "self":
862
883
  if args.subcommand == "update":
863
884
  _update_self()
@@ -873,33 +894,6 @@ def _process_args(args: argparse.Namespace) -> None: # noqa: C901, PLR0912
873
894
  raise ValueError(msg)
874
895
 
875
896
 
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
897
  def __init_system_config() -> None:
904
898
  """Initializes the system config directory if it doesn't exist."""
905
899
  if not CONFIG_DIR.exists():
@@ -13,8 +13,6 @@ from fastapi.middleware.cors import CORSMiddleware
13
13
  from fastapi.staticfiles import StaticFiles
14
14
  from rich.logging import RichHandler
15
15
 
16
- from griptape_nodes.retained_mode.events.base_events import EventRequest, deserialize_event
17
-
18
16
  if TYPE_CHECKING:
19
17
  from queue import Queue
20
18
 
@@ -27,9 +25,11 @@ STATIC_SERVER_PORT = int(os.getenv("STATIC_SERVER_PORT", "8124"))
27
25
  # URL path for the static server
28
26
  STATIC_SERVER_URL = os.getenv("STATIC_SERVER_URL", "/static")
29
27
  # Log level for the static server
30
- STATIC_SERVER_LOG_LEVEL = os.getenv("STATIC_SERVER_LOG_LEVEL", "info").lower()
28
+ STATIC_SERVER_LOG_LEVEL = os.getenv("STATIC_SERVER_LOG_LEVEL", "ERROR").lower()
31
29
 
32
30
  logger = logging.getLogger("griptape_nodes_api")
31
+ logging.getLogger("uvicorn").addHandler(RichHandler(show_time=True, show_path=False, markup=True, rich_tracebacks=True))
32
+
33
33
 
34
34
  # Global event queue - initialized as None and set when starting the API
35
35
  event_queue: Queue | None = None
@@ -124,8 +124,41 @@ async def _list_static_files(static_directory: Annotated[Path, Depends(get_stati
124
124
  return {"files": file_names}
125
125
 
126
126
 
127
+ @app.delete("/static-files/{file_path:path}")
128
+ async def _delete_static_file(file_path: str, static_directory: Annotated[Path, Depends(get_static_dir)]) -> dict:
129
+ """Delete a static file from the static server."""
130
+ if not STATIC_SERVER_ENABLED:
131
+ msg = "Static server is not enabled. Please set STATIC_SERVER_ENABLED to True."
132
+ raise HTTPException(status_code=500, detail=msg)
133
+
134
+ file_full_path = Path(static_directory / file_path)
135
+
136
+ # Check if file exists
137
+ if not file_full_path.exists():
138
+ logger.warning("File not found for deletion: %s", file_path)
139
+ raise HTTPException(status_code=404, detail=f"File {file_path} not found")
140
+
141
+ # Check if it's actually a file (not a directory)
142
+ if not file_full_path.is_file():
143
+ msg = f"Path {file_path} is not a file"
144
+ logger.error(msg)
145
+ raise HTTPException(status_code=400, detail=msg)
146
+
147
+ try:
148
+ file_full_path.unlink()
149
+ except (OSError, PermissionError) as e:
150
+ msg = f"Failed to delete file {file_path}: {e}"
151
+ logger.error(msg)
152
+ raise HTTPException(status_code=500, detail=msg) from e
153
+ else:
154
+ logger.info("Successfully deleted static file: %s", file_path)
155
+ return {"message": f"File {file_path} deleted successfully"}
156
+
157
+
127
158
  @app.post("/engines/request")
128
159
  async def _create_event(request: Request, queue: Annotated[Queue, Depends(get_event_queue)]) -> None:
160
+ from .app import _process_api_event
161
+
129
162
  body = await request.json()
130
163
  _process_api_event(body, queue)
131
164
 
@@ -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
 
@@ -164,36 +193,3 @@ def start_api(static_directory: Path, queue: Queue) -> None:
164
193
  uvicorn.run(
165
194
  app, host=STATIC_SERVER_HOST, port=STATIC_SERVER_PORT, log_level=STATIC_SERVER_LOG_LEVEL, log_config=None
166
195
  )
167
-
168
-
169
- def _process_api_event(event: dict, event_queue: Queue) -> None:
170
- """Process API events and send them to the event queue."""
171
- payload = event.get("payload", {})
172
-
173
- try:
174
- payload["request"]
175
- except KeyError:
176
- msg = "Error: 'request' was expected but not found."
177
- raise RuntimeError(msg) from None
178
-
179
- try:
180
- event_type = payload["event_type"]
181
- if event_type != "EventRequest":
182
- msg = "Error: 'event_type' was found on request, but did not match 'EventRequest' as expected."
183
- raise RuntimeError(msg) from None
184
- except KeyError:
185
- msg = "Error: 'event_type' not found in request."
186
- raise RuntimeError(msg) from None
187
-
188
- # Now attempt to convert it into an EventRequest.
189
- try:
190
- request_event = deserialize_event(json_data=payload)
191
- if not isinstance(request_event, EventRequest):
192
- msg = f"Deserialized event is not an EventRequest: {type(request_event)}"
193
- raise TypeError(msg) # noqa: TRY301
194
- except Exception as e:
195
- msg = f"Unable to convert request JSON into a valid EventRequest object. Error Message: '{e}'"
196
- raise RuntimeError(msg) from None
197
-
198
- # Add the event to the queue
199
- event_queue.put(request_event)
@@ -9,7 +9,7 @@ import sys
9
9
  import threading
10
10
  from pathlib import Path
11
11
  from queue import Queue
12
- from typing import Any
12
+ from typing import Any, cast
13
13
  from urllib.parse import urljoin
14
14
 
15
15
  from griptape.events import (
@@ -24,9 +24,9 @@ from websockets.asyncio.client import connect
24
24
  from websockets.exceptions import ConnectionClosed, WebSocketException
25
25
 
26
26
  from griptape_nodes.mcp_server.server import main as mcp_server
27
+ from griptape_nodes.retained_mode.events import app_events, execution_events
27
28
 
28
29
  # This import is necessary to register all events, even if not technically used
29
- from griptape_nodes.retained_mode.events import app_events, execution_events
30
30
  from griptape_nodes.retained_mode.events.base_events import (
31
31
  AppEvent,
32
32
  EventRequest,
@@ -36,11 +36,14 @@ from griptape_nodes.retained_mode.events.base_events import (
36
36
  ExecutionGriptapeNodeEvent,
37
37
  GriptapeNodeEvent,
38
38
  ProgressEvent,
39
+ RequestPayload,
40
+ SkipTheLineMixin,
41
+ deserialize_event,
39
42
  )
40
43
  from griptape_nodes.retained_mode.events.logger_events import LogHandlerEvent
41
44
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
42
45
 
43
- from .api import _process_api_event, start_api
46
+ from .api import start_api
44
47
 
45
48
  # This is a global event queue that will be used to pass events between threads
46
49
  event_queue = Queue()
@@ -395,3 +398,67 @@ def __schedule_async_task(coro: Any) -> None:
395
398
  asyncio.run_coroutine_threadsafe(coro, event_loop)
396
399
  else:
397
400
  logger.warning("Event loop not available for scheduling async task")
401
+
402
+
403
+ def _process_api_event(event: dict, event_queue: Queue) -> None:
404
+ """Process API events and send them to the event queue."""
405
+ payload = event.get("payload", {})
406
+
407
+ try:
408
+ payload["request"]
409
+ except KeyError:
410
+ msg = "Error: 'request' was expected but not found."
411
+ raise RuntimeError(msg) from None
412
+
413
+ try:
414
+ event_type = payload["event_type"]
415
+ if event_type != "EventRequest":
416
+ msg = "Error: 'event_type' was found on request, but did not match 'EventRequest' as expected."
417
+ raise RuntimeError(msg) from None
418
+ except KeyError:
419
+ msg = "Error: 'event_type' not found in request."
420
+ raise RuntimeError(msg) from None
421
+
422
+ # Now attempt to convert it into an EventRequest.
423
+ try:
424
+ request_event = deserialize_event(json_data=payload)
425
+ if not isinstance(request_event, EventRequest):
426
+ msg = f"Deserialized event is not an EventRequest: {type(request_event)}"
427
+ raise TypeError(msg) # noqa: TRY301
428
+ except Exception as e:
429
+ msg = f"Unable to convert request JSON into a valid EventRequest object. Error Message: '{e}'"
430
+ raise RuntimeError(msg) from None
431
+
432
+ # Check if the event implements SkipTheLineMixin for priority processing
433
+ if isinstance(request_event.request, SkipTheLineMixin):
434
+ # Handle the event immediately without queuing
435
+ # The request is guaranteed to be a RequestPayload since it passed earlier validation
436
+ result_payload = GriptapeNodes.handle_request(
437
+ cast("RequestPayload", request_event.request),
438
+ response_topic=request_event.response_topic,
439
+ request_id=request_event.request_id,
440
+ )
441
+
442
+ # Create the result event and emit response immediately
443
+ if result_payload.succeeded():
444
+ result_event = EventResultSuccess(
445
+ request=cast("RequestPayload", request_event.request),
446
+ request_id=request_event.request_id,
447
+ result=result_payload,
448
+ response_topic=request_event.response_topic,
449
+ )
450
+ dest_socket = "success_result"
451
+ else:
452
+ result_event = EventResultFailure(
453
+ request=cast("RequestPayload", request_event.request),
454
+ request_id=request_event.request_id,
455
+ result=result_payload,
456
+ response_topic=request_event.response_topic,
457
+ )
458
+ dest_socket = "failure_result"
459
+
460
+ # Emit the response immediately
461
+ __schedule_async_task(__emit_message(dest_socket, result_event.json(), topic=result_event.response_topic))
462
+ else:
463
+ # Add the event to the queue for normal processing
464
+ event_queue.put(request_event)
@@ -8,6 +8,8 @@ from typing import Any
8
8
  from watchdog.events import PatternMatchingEventHandler
9
9
  from watchdog.observers import Observer
10
10
 
11
+ from griptape_nodes.utils.uv_utils import find_uv_bin
12
+
11
13
 
12
14
  class ReloadHandler(PatternMatchingEventHandler):
13
15
  def __init__(
@@ -30,8 +32,9 @@ class ReloadHandler(PatternMatchingEventHandler):
30
32
  def start_process(self) -> None:
31
33
  if self.process:
32
34
  self.process.terminate()
33
- self.process = subprocess.Popen(
34
- ["uv", "run", "gtn"], # noqa: S607
35
+ uv_path = find_uv_bin()
36
+ self.process = subprocess.Popen( # noqa: S603
37
+ [uv_path, "run", "gtn"],
35
38
  stdout=sys.stdout,
36
39
  stderr=sys.stderr,
37
40
  )
@@ -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