griptape-nodes 0.53.0__py3-none-any.whl → 0.54.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 (56) hide show
  1. griptape_nodes/__init__.py +5 -2
  2. griptape_nodes/app/app.py +4 -26
  3. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +35 -5
  4. griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +15 -1
  5. griptape_nodes/cli/commands/config.py +4 -1
  6. griptape_nodes/cli/commands/init.py +5 -3
  7. griptape_nodes/cli/commands/libraries.py +14 -8
  8. griptape_nodes/cli/commands/models.py +504 -0
  9. griptape_nodes/cli/commands/self.py +5 -2
  10. griptape_nodes/cli/main.py +11 -1
  11. griptape_nodes/cli/shared.py +0 -9
  12. griptape_nodes/common/directed_graph.py +17 -1
  13. griptape_nodes/drivers/storage/base_storage_driver.py +40 -20
  14. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +24 -29
  15. griptape_nodes/drivers/storage/local_storage_driver.py +17 -13
  16. griptape_nodes/exe_types/node_types.py +219 -14
  17. griptape_nodes/exe_types/param_components/__init__.py +1 -0
  18. griptape_nodes/exe_types/param_components/execution_status_component.py +138 -0
  19. griptape_nodes/machines/control_flow.py +129 -92
  20. griptape_nodes/machines/dag_builder.py +207 -0
  21. griptape_nodes/machines/parallel_resolution.py +264 -276
  22. griptape_nodes/machines/sequential_resolution.py +9 -7
  23. griptape_nodes/node_library/library_registry.py +34 -1
  24. griptape_nodes/retained_mode/events/app_events.py +5 -1
  25. griptape_nodes/retained_mode/events/base_events.py +7 -7
  26. griptape_nodes/retained_mode/events/config_events.py +30 -0
  27. griptape_nodes/retained_mode/events/execution_events.py +2 -2
  28. griptape_nodes/retained_mode/events/model_events.py +296 -0
  29. griptape_nodes/retained_mode/griptape_nodes.py +10 -1
  30. griptape_nodes/retained_mode/managers/agent_manager.py +14 -0
  31. griptape_nodes/retained_mode/managers/config_manager.py +44 -3
  32. griptape_nodes/retained_mode/managers/event_manager.py +8 -2
  33. griptape_nodes/retained_mode/managers/flow_manager.py +45 -14
  34. griptape_nodes/retained_mode/managers/library_manager.py +3 -3
  35. griptape_nodes/retained_mode/managers/model_manager.py +1107 -0
  36. griptape_nodes/retained_mode/managers/node_manager.py +26 -26
  37. griptape_nodes/retained_mode/managers/object_manager.py +1 -1
  38. griptape_nodes/retained_mode/managers/os_manager.py +6 -6
  39. griptape_nodes/retained_mode/managers/settings.py +87 -9
  40. griptape_nodes/retained_mode/managers/static_files_manager.py +77 -9
  41. griptape_nodes/retained_mode/managers/sync_manager.py +10 -5
  42. griptape_nodes/retained_mode/managers/workflow_manager.py +98 -92
  43. griptape_nodes/retained_mode/retained_mode.py +19 -0
  44. griptape_nodes/servers/__init__.py +1 -0
  45. griptape_nodes/{mcp_server/server.py → servers/mcp.py} +1 -1
  46. griptape_nodes/{app/api.py → servers/static.py} +43 -40
  47. griptape_nodes/traits/button.py +124 -6
  48. griptape_nodes/traits/multi_options.py +188 -0
  49. griptape_nodes/traits/numbers_selector.py +77 -0
  50. griptape_nodes/traits/options.py +93 -2
  51. griptape_nodes/utils/async_utils.py +31 -0
  52. {griptape_nodes-0.53.0.dist-info → griptape_nodes-0.54.0.dist-info}/METADATA +3 -1
  53. {griptape_nodes-0.53.0.dist-info → griptape_nodes-0.54.0.dist-info}/RECORD +56 -47
  54. {griptape_nodes-0.53.0.dist-info → griptape_nodes-0.54.0.dist-info}/WHEEL +1 -1
  55. /griptape_nodes/{mcp_server → servers}/ws_request_manager.py +0 -0
  56. {griptape_nodes-0.53.0.dist-info → griptape_nodes-0.54.0.dist-info}/entry_points.txt +0 -0
@@ -3,6 +3,8 @@
3
3
  import sys
4
4
  from pathlib import Path
5
5
 
6
+ from rich.console import Console
7
+
6
8
 
7
9
  def main() -> None:
8
10
  """Main entry point for the Griptape Nodes CLI."""
@@ -12,8 +14,9 @@ def main() -> None:
12
14
  # but current engine relies on importing files rather than packages.
13
15
  sys.path.append(str(Path.cwd()))
14
16
 
15
- # Import and run the new CLI
16
- from griptape_nodes.cli.main import app
17
+ console = Console()
18
+ with console.status("[bold green]Loading Griptape Nodes...", spinner="dots"):
19
+ from griptape_nodes.cli.main import app
17
20
 
18
21
  app()
19
22
 
griptape_nodes/app/app.py CHANGED
@@ -7,7 +7,6 @@ import os
7
7
  import sys
8
8
  import threading
9
9
  from dataclasses import dataclass
10
- from pathlib import Path
11
10
  from typing import Any
12
11
  from urllib.parse import urljoin
13
12
 
@@ -18,8 +17,6 @@ from rich.panel import Panel
18
17
  from websockets.asyncio.client import connect
19
18
  from websockets.exceptions import ConnectionClosed, ConnectionClosedError, WebSocketException
20
19
 
21
- from griptape_nodes.app.api import start_static_server
22
- from griptape_nodes.mcp_server.server import start_mcp_server
23
20
  from griptape_nodes.retained_mode.events import app_events, execution_events
24
21
 
25
22
  # This import is necessary to register all events, even if not technically used
@@ -80,9 +77,6 @@ websocket_event_loop: asyncio.AbstractEventLoop | None = None
80
77
  websocket_event_loop_ready = threading.Event()
81
78
 
82
79
 
83
- # Whether to enable the static server
84
- STATIC_SERVER_ENABLED = os.getenv("STATIC_SERVER_ENABLED", "true").lower() == "true"
85
-
86
80
  # Semaphore to limit concurrent requests
87
81
  REQUEST_SEMAPHORE = asyncio.Semaphore(100)
88
82
 
@@ -135,14 +129,6 @@ async def astart_app() -> None:
135
129
  main_loop = asyncio.get_running_loop()
136
130
 
137
131
  try:
138
- # Start MCP server in daemon thread
139
- threading.Thread(target=start_mcp_server, args=(api_key,), daemon=True, name="mcp-server").start()
140
-
141
- # Start static server in daemon thread if enabled
142
- if STATIC_SERVER_ENABLED:
143
- static_dir = _build_static_dir()
144
- threading.Thread(target=start_static_server, args=(static_dir,), daemon=True, name="static-server").start()
145
-
146
132
  # Start WebSocket tasks in daemon thread
147
133
  threading.Thread(
148
134
  target=_start_websocket_connection, args=(api_key, main_loop), daemon=True, name="websocket-tasks"
@@ -187,13 +173,11 @@ async def _run_websocket_tasks(api_key: str, main_loop: asyncio.AbstractEventLoo
187
173
  initialized = False
188
174
 
189
175
  async for ws_connection in connection_stream:
190
- logger.info("WebSocket connection established")
176
+ logger.debug("WebSocket connection established")
191
177
  try:
192
178
  # Emit initialization event only for the first connection
193
179
  if not initialized:
194
- griptape_nodes.EventManager().put_event_threadsafe(
195
- main_loop, AppEvent(payload=app_events.AppInitializationComplete())
196
- )
180
+ griptape_nodes.EventManager().put_event(AppEvent(payload=app_events.AppInitializationComplete()))
197
181
  initialized = True
198
182
 
199
183
  # Emit connection established event for every connection
@@ -233,12 +217,6 @@ def _ensure_api_key() -> str:
233
217
  return api_key
234
218
 
235
219
 
236
- def _build_static_dir() -> Path:
237
- """Build the static directory path based on the workspace configuration."""
238
- config_manager = griptape_nodes.ConfigManager()
239
- return Path(config_manager.workspace_path) / config_manager.merged_config["static_files_directory"]
240
-
241
-
242
220
  async def _process_incoming_messages(ws_connection: Any, main_loop: asyncio.AbstractEventLoop) -> None:
243
221
  """Process incoming WebSocket requests from Nodes API."""
244
222
  logger.debug("Processing incoming WebSocket requests from WebSocket connection")
@@ -366,7 +344,7 @@ async def _send_unsubscribe_command(ws_connection: Any, topic: str) -> None:
366
344
 
367
345
  async def _process_event_queue() -> None:
368
346
  """Process events concurrently - runs on main thread."""
369
- logger.info("Starting event queue processor on main thread")
347
+ logger.debug("Starting event queue processor on main thread")
370
348
  background_tasks = set()
371
349
 
372
350
  def _handle_task_result(task: asyncio.Task) -> None:
@@ -399,7 +377,7 @@ async def _process_event_queue() -> None:
399
377
  task.add_done_callback(_handle_task_result)
400
378
  event_queue.task_done()
401
379
  except asyncio.CancelledError:
402
- logger.info("Event queue processor shutdown complete")
380
+ logger.debug("Event queue processor shutdown complete")
403
381
  raise
404
382
 
405
383
 
@@ -1,9 +1,12 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
2
- from typing import Any
4
+ from typing import TYPE_CHECKING, Any, Self
3
5
 
4
6
  from griptape_nodes.bootstrap.workflow_executors.workflow_executor import WorkflowExecutor
5
7
  from griptape_nodes.drivers.storage import StorageBackend
6
8
  from griptape_nodes.exe_types.node_types import EndNode, StartNode
9
+ from griptape_nodes.retained_mode.events.app_events import AppInitializationComplete
7
10
  from griptape_nodes.retained_mode.events.base_events import (
8
11
  EventRequest,
9
12
  ExecutionGriptapeNodeEvent,
@@ -15,6 +18,9 @@ from griptape_nodes.retained_mode.events.workflow_events import (
15
18
  )
16
19
  from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
17
20
 
21
+ if TYPE_CHECKING:
22
+ from types import TracebackType
23
+
18
24
  logger = logging.getLogger(__name__)
19
25
 
20
26
 
@@ -23,6 +29,29 @@ class LocalExecutorError(Exception):
23
29
 
24
30
 
25
31
  class LocalWorkflowExecutor(WorkflowExecutor):
32
+ def __init__(
33
+ self,
34
+ storage_backend: StorageBackend = StorageBackend.LOCAL,
35
+ ):
36
+ super().__init__()
37
+ self._set_storage_backend(storage_backend=storage_backend)
38
+
39
+ async def __aenter__(self) -> Self:
40
+ """Async context manager entry: initialize queue and broadcast app initialization."""
41
+ GriptapeNodes.EventManager().initialize_queue()
42
+ await GriptapeNodes.EventManager().broadcast_app_event(AppInitializationComplete())
43
+ return self
44
+
45
+ async def __aexit__(
46
+ self,
47
+ exc_type: type[BaseException] | None,
48
+ exc_val: BaseException | None,
49
+ exc_tb: TracebackType | None,
50
+ ) -> None:
51
+ """Async context manager exit."""
52
+ # TODO: Broadcast shutdown https://github.com/griptape-ai/griptape-nodes/issues/2149
53
+ return
54
+
26
55
  def _load_flow_for_workflow(self) -> str:
27
56
  try:
28
57
  context_manager = GriptapeNodes.ContextManager()
@@ -124,7 +153,7 @@ class LocalWorkflowExecutor(WorkflowExecutor):
124
153
  self,
125
154
  workflow_name: str,
126
155
  flow_input: Any,
127
- storage_backend: StorageBackend = StorageBackend.LOCAL,
156
+ storage_backend: StorageBackend | None = None,
128
157
  **kwargs: Any,
129
158
  ) -> None:
130
159
  """Executes a local workflow.
@@ -140,12 +169,13 @@ class LocalWorkflowExecutor(WorkflowExecutor):
140
169
  Returns:
141
170
  None
142
171
  """
172
+ if storage_backend is not None:
173
+ msg = "The storage_backend parameter is deprecated. Pass `storage_backend` to the constructor instead."
174
+ raise ValueError(msg)
175
+
143
176
  logger.info("Executing workflow: %s", workflow_name)
144
177
  GriptapeNodes.EventManager().initialize_queue()
145
178
 
146
- # Set the storage backend
147
- self._set_storage_backend(storage_backend=storage_backend)
148
-
149
179
  # Load workflow from file if workflow_path is provided
150
180
  workflow_path = kwargs.get("workflow_path")
151
181
  if workflow_path:
@@ -1,7 +1,8 @@
1
1
  import asyncio
2
2
  import logging
3
3
  from abc import abstractmethod
4
- from typing import Any
4
+ from types import TracebackType
5
+ from typing import Any, Self
5
6
 
6
7
  from griptape_nodes.drivers.storage import StorageBackend
7
8
 
@@ -12,6 +13,19 @@ class WorkflowExecutor:
12
13
  def __init__(self) -> None:
13
14
  self.output: dict | None = None
14
15
 
16
+ async def __aenter__(self) -> Self:
17
+ """Async context manager entry."""
18
+ return self
19
+
20
+ async def __aexit__(
21
+ self,
22
+ exc_type: type[BaseException] | None,
23
+ exc_val: BaseException | None,
24
+ exc_tb: TracebackType | None,
25
+ ) -> None:
26
+ """Async context manager exit."""
27
+ return
28
+
15
29
  def run(
16
30
  self,
17
31
  workflow_name: str,
@@ -5,7 +5,10 @@ import sys
5
5
 
6
6
  import typer
7
7
 
8
- from griptape_nodes.cli.shared import config_manager, console
8
+ from griptape_nodes.cli.shared import console
9
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
10
+
11
+ config_manager = GriptapeNodes.ConfigManager()
9
12
 
10
13
  app = typer.Typer(help="Manage configuration.")
11
14
 
@@ -20,12 +20,14 @@ from griptape_nodes.cli.shared import (
20
20
  GT_CLOUD_BASE_URL,
21
21
  NODES_APP_URL,
22
22
  InitConfig,
23
- config_manager,
24
23
  console,
25
- secrets_manager,
26
24
  )
27
25
  from griptape_nodes.drivers.storage import StorageBackend
28
26
  from griptape_nodes.drivers.storage.griptape_cloud_storage_driver import GriptapeCloudStorageDriver
27
+ from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
28
+
29
+ config_manager = GriptapeNodes.ConfigManager()
30
+ secrets_manager = GriptapeNodes.SecretsManager()
29
31
 
30
32
 
31
33
  def init_command( # noqa: PLR0913
@@ -98,7 +100,7 @@ def _run_init(config: InitConfig) -> None:
98
100
 
99
101
  # Sync libraries
100
102
  if config.libraries_sync is not False:
101
- asyncio.run(_sync_libraries())
103
+ asyncio.run(_sync_libraries(load_libraries_from_config=False))
102
104
 
103
105
  console.print("[bold green]Initialization complete![/bold green]")
104
106
 
@@ -28,8 +28,13 @@ def sync() -> None:
28
28
  asyncio.run(_sync_libraries())
29
29
 
30
30
 
31
- async def _sync_libraries() -> None:
32
- """Download and sync Griptape Nodes libraries, copying only directories from synced libraries."""
31
+ async def _sync_libraries(*, load_libraries_from_config: bool = True) -> None:
32
+ """Download and sync Griptape Nodes libraries, copying only directories from synced libraries.
33
+
34
+ Args:
35
+ load_libraries_from_config (bool): If True, re-initialize all libraries from config
36
+
37
+ """
33
38
  install_source, _ = get_install_source()
34
39
  # Unless we're installed from PyPi, grab libraries from the 'latest' tag
35
40
  if install_source == "pypi":
@@ -80,11 +85,12 @@ async def _sync_libraries() -> None:
80
85
  console.print(f"[green]Synced library: {library_dir.name}[/green]")
81
86
 
82
87
  # Re-initialize all libraries from config
83
- console.print("[bold cyan]Initializing libraries...[/bold cyan]")
84
- try:
85
- await GriptapeNodes.LibraryManager().load_all_libraries_from_config()
86
- console.print("[bold green]Libraries Initialized successfully.[/bold green]")
87
- except Exception as e:
88
- console.print(f"[red]Error initializing libraries: {e}[/red]")
88
+ if load_libraries_from_config:
89
+ console.print("[bold cyan]Initializing libraries...[/bold cyan]")
90
+ try:
91
+ await GriptapeNodes.LibraryManager().load_all_libraries_from_config()
92
+ console.print("[bold green]Libraries Initialized successfully.[/bold green]")
93
+ except Exception as e:
94
+ console.print(f"[red]Error initializing libraries: {e}[/red]")
89
95
 
90
96
  console.print("[bold green]Libraries synced.[/bold green]")