griptape-nodes 0.57.1__py3-none-any.whl → 0.58.1__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 (51) hide show
  1. griptape_nodes/api_client/__init__.py +9 -0
  2. griptape_nodes/api_client/client.py +279 -0
  3. griptape_nodes/api_client/request_client.py +273 -0
  4. griptape_nodes/app/app.py +57 -150
  5. griptape_nodes/bootstrap/utils/python_subprocess_executor.py +1 -1
  6. griptape_nodes/bootstrap/workflow_executors/local_session_workflow_executor.py +22 -50
  7. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +6 -1
  8. griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +27 -46
  9. griptape_nodes/bootstrap/workflow_executors/utils/subprocess_script.py +7 -0
  10. griptape_nodes/bootstrap/workflow_publishers/local_workflow_publisher.py +3 -1
  11. griptape_nodes/bootstrap/workflow_publishers/subprocess_workflow_publisher.py +3 -1
  12. griptape_nodes/bootstrap/workflow_publishers/utils/subprocess_script.py +16 -1
  13. griptape_nodes/common/node_executor.py +466 -0
  14. griptape_nodes/drivers/storage/base_storage_driver.py +0 -11
  15. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +7 -25
  16. griptape_nodes/drivers/storage/local_storage_driver.py +2 -2
  17. griptape_nodes/exe_types/connections.py +37 -9
  18. griptape_nodes/exe_types/core_types.py +1 -1
  19. griptape_nodes/exe_types/node_types.py +115 -22
  20. griptape_nodes/machines/control_flow.py +48 -7
  21. griptape_nodes/machines/parallel_resolution.py +98 -29
  22. griptape_nodes/machines/sequential_resolution.py +61 -22
  23. griptape_nodes/node_library/library_registry.py +24 -1
  24. griptape_nodes/node_library/workflow_registry.py +38 -2
  25. griptape_nodes/retained_mode/events/execution_events.py +8 -1
  26. griptape_nodes/retained_mode/events/flow_events.py +90 -3
  27. griptape_nodes/retained_mode/events/node_events.py +17 -10
  28. griptape_nodes/retained_mode/events/workflow_events.py +5 -0
  29. griptape_nodes/retained_mode/griptape_nodes.py +16 -219
  30. griptape_nodes/retained_mode/managers/config_manager.py +0 -46
  31. griptape_nodes/retained_mode/managers/engine_identity_manager.py +225 -74
  32. griptape_nodes/retained_mode/managers/flow_manager.py +1276 -230
  33. griptape_nodes/retained_mode/managers/library_manager.py +7 -8
  34. griptape_nodes/retained_mode/managers/node_manager.py +197 -9
  35. griptape_nodes/retained_mode/managers/secrets_manager.py +26 -0
  36. griptape_nodes/retained_mode/managers/session_manager.py +264 -227
  37. griptape_nodes/retained_mode/managers/settings.py +4 -38
  38. griptape_nodes/retained_mode/managers/static_files_manager.py +3 -3
  39. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +135 -6
  40. griptape_nodes/retained_mode/managers/workflow_manager.py +206 -78
  41. griptape_nodes/servers/mcp.py +23 -15
  42. griptape_nodes/utils/async_utils.py +36 -0
  43. griptape_nodes/utils/dict_utils.py +8 -2
  44. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +11 -6
  45. griptape_nodes/version_compatibility/workflow_versions/v0_7_0/local_executor_argument_addition.py +12 -5
  46. {griptape_nodes-0.57.1.dist-info → griptape_nodes-0.58.1.dist-info}/METADATA +4 -3
  47. {griptape_nodes-0.57.1.dist-info → griptape_nodes-0.58.1.dist-info}/RECORD +49 -47
  48. {griptape_nodes-0.57.1.dist-info → griptape_nodes-0.58.1.dist-info}/WHEEL +1 -1
  49. griptape_nodes/retained_mode/utils/engine_identity.py +0 -245
  50. griptape_nodes/servers/ws_request_manager.py +0 -268
  51. {griptape_nodes-0.57.1.dist-info → griptape_nodes-0.58.1.dist-info}/entry_points.txt +0 -0
@@ -2,22 +2,65 @@
2
2
 
3
3
  Centralizes engine identity management, providing a consistent interface for
4
4
  engine ID and name operations.
5
+ Handles engine ID, name storage, and generation for unique engine identification.
6
+ Supports multiple engines with selection via GTN_ENGINE_ID environment variable.
5
7
  """
6
8
 
7
- import logging
8
- from pathlib import Path
9
+ from __future__ import annotations
9
10
 
10
- from griptape_nodes.retained_mode.events.base_events import BaseEvent
11
- from griptape_nodes.retained_mode.managers.event_manager import EventManager
12
- from griptape_nodes.retained_mode.utils.engine_identity import EngineIdentity
11
+ import json
12
+ import logging
13
+ import os
14
+ import uuid
15
+ from datetime import UTC, datetime
16
+ from typing import TYPE_CHECKING
17
+
18
+ from pydantic import BaseModel
19
+ from xdg_base_dirs import xdg_data_home
20
+
21
+ from griptape_nodes.retained_mode.events.app_events import (
22
+ GetEngineNameRequest,
23
+ GetEngineNameResultFailure,
24
+ GetEngineNameResultSuccess,
25
+ SetEngineNameRequest,
26
+ SetEngineNameResultFailure,
27
+ SetEngineNameResultSuccess,
28
+ )
29
+ from griptape_nodes.retained_mode.events.base_events import (
30
+ BaseEvent,
31
+ ResultDetails,
32
+ ResultPayload,
33
+ )
34
+ from griptape_nodes.retained_mode.utils.name_generator import generate_engine_name
35
+
36
+ if TYPE_CHECKING:
37
+ from pathlib import Path
38
+
39
+ from griptape_nodes.retained_mode.managers.event_manager import EventManager
13
40
 
14
41
  logger = logging.getLogger("griptape_nodes")
15
42
 
16
43
 
44
+ class EngineData(BaseModel):
45
+ """Represents a single engine's data."""
46
+
47
+ id: str
48
+ name: str
49
+ created_at: str
50
+ updated_at: str | None = None
51
+
52
+
53
+ class EnginesStorage(BaseModel):
54
+ """Represents the engines storage structure."""
55
+
56
+ engines: list[EngineData]
57
+ default_engine_id: str | None = None
58
+
59
+
17
60
  class EngineIdentityManager:
18
61
  """Manages engine identity and active engine state."""
19
62
 
20
- _active_engine_id: str | None = None
63
+ _ENGINE_DATA_FILE = "engines.json"
21
64
 
22
65
  def __init__(self, event_manager: EventManager | None = None) -> None:
23
66
  """Initialize the EngineIdentityManager.
@@ -25,122 +68,230 @@ class EngineIdentityManager:
25
68
  Args:
26
69
  event_manager: The EventManager instance to use for event handling.
27
70
  """
71
+ self._active_engine_id: str | None = None
72
+ self._engines_data = self._load_engines_data()
73
+ self._current_engine_data = self._get_or_initialize_engine_data()
74
+
28
75
  if event_manager is not None:
29
- # Register event handlers here when engine events are defined
30
- pass
76
+ event_manager.assign_manager_to_request_type(GetEngineNameRequest, self.handle_get_engine_name_request)
77
+ event_manager.assign_manager_to_request_type(SetEngineNameRequest, self.handle_set_engine_name_request)
31
78
 
32
- @classmethod
33
- def get_active_engine_id(cls) -> str | None:
79
+ @property
80
+ def active_engine_id(self) -> str | None:
34
81
  """Get the active engine ID.
35
82
 
36
83
  Returns:
37
84
  str | None: The active engine ID or None if not set
38
85
  """
39
- return cls._active_engine_id
86
+ return self._active_engine_id
40
87
 
41
- @classmethod
42
- def set_active_engine_id(cls, engine_id: str) -> None:
88
+ @active_engine_id.setter
89
+ def active_engine_id(self, engine_id: str) -> None:
43
90
  """Set the active engine ID.
44
91
 
45
92
  Args:
46
93
  engine_id: The engine ID to set as active
47
94
  """
48
- cls._active_engine_id = engine_id
95
+ self._active_engine_id = engine_id
49
96
  logger.debug("Set active engine ID to: %s", engine_id)
50
97
 
51
- @classmethod
52
- def initialize_engine_id(cls) -> str:
53
- """Initialize the engine ID if not already set."""
54
- if cls._active_engine_id is None:
55
- engine_id = EngineIdentity.get_engine_id()
56
- BaseEvent._engine_id = engine_id
57
- cls._active_engine_id = engine_id
58
- logger.debug("Initialized engine ID: %s", engine_id)
59
-
60
- return cls._active_engine_id
61
-
62
- @classmethod
63
- def get_engine_data(cls) -> dict:
64
- """Get the current engine data, creating default if it doesn't exist.
98
+ @property
99
+ def engine_id(self) -> str:
100
+ """Get the engine ID.
65
101
 
66
102
  Returns:
67
- dict: The current engine data
103
+ str: The engine ID (UUID)
68
104
  """
69
- return EngineIdentity.get_engine_data()
105
+ return self._current_engine_data.id
70
106
 
71
- @classmethod
72
- def get_engine_name(cls) -> str:
107
+ @property
108
+ def engine_name(self) -> str:
73
109
  """Get the engine name.
74
110
 
75
111
  Returns:
76
112
  str: The engine name
77
113
  """
78
- return EngineIdentity.get_engine_name()
114
+ return self._current_engine_data.name
79
115
 
80
- @classmethod
81
- def set_engine_name(cls, engine_name: str) -> None:
116
+ @engine_name.setter
117
+ def engine_name(self, engine_name: str) -> None:
82
118
  """Set and persist the current engine name.
83
119
 
84
120
  Args:
85
121
  engine_name: The new engine name to set
86
122
  """
87
- EngineIdentity.set_engine_name(engine_name)
123
+ # Update cached engine data
124
+ self._current_engine_data.name = engine_name
125
+ self._current_engine_data.updated_at = datetime.now(tz=UTC).isoformat()
126
+
127
+ # Save updated engine data
128
+ self._add_or_update_engine(self._current_engine_data)
88
129
  logger.info("Updated engine name to: %s", engine_name)
89
130
 
90
- @classmethod
91
- def get_all_engines(cls) -> list[dict]:
131
+ @property
132
+ def all_engines(self) -> list[EngineData]:
92
133
  """Get all registered engines.
93
134
 
94
135
  Returns:
95
- list[dict]: List of all engine data
136
+ list[EngineData]: List of all engine data
96
137
  """
97
- return EngineIdentity.get_all_engines()
138
+ return self._engines_data.engines
98
139
 
99
- @classmethod
100
- def get_default_engine_id(cls) -> str | None:
101
- """Get the default engine ID.
140
+ def handle_get_engine_name_request(self, request: GetEngineNameRequest) -> ResultPayload: # noqa: ARG002
141
+ """Handle requests to get the current engine name."""
142
+ try:
143
+ engine_name = self.engine_name
144
+ return GetEngineNameResultSuccess(
145
+ engine_name=engine_name, result_details="Engine name retrieved successfully."
146
+ )
147
+ except Exception as err:
148
+ error_message = f"Failed to get engine name: {err}"
149
+ logger.error(error_message)
150
+ return GetEngineNameResultFailure(error_message=error_message, result_details=error_message)
151
+
152
+ def handle_set_engine_name_request(self, request: SetEngineNameRequest) -> ResultPayload:
153
+ """Handle requests to set a new engine name."""
154
+ try:
155
+ if not request.engine_name or not request.engine_name.strip():
156
+ error_message = "Engine name cannot be empty"
157
+ logger.warning(error_message)
158
+ return SetEngineNameResultFailure(error_message=error_message, result_details=error_message)
159
+
160
+ self.engine_name = request.engine_name.strip()
161
+ details = f"Engine name set to: {request.engine_name.strip()}"
162
+ return SetEngineNameResultSuccess(
163
+ engine_name=request.engine_name.strip(),
164
+ result_details=ResultDetails(message=details, level=logging.INFO),
165
+ )
166
+
167
+ except Exception as err:
168
+ error_message = f"Failed to set engine name: {err}"
169
+ logger.error(error_message)
170
+ return SetEngineNameResultFailure(error_message=error_message, result_details=error_message)
171
+
172
+ def _get_or_initialize_engine_data(self) -> EngineData:
173
+ """Get the current engine data, creating default if it doesn't exist.
102
174
 
103
175
  Returns:
104
- str | None: The default engine ID or None if not set
176
+ EngineData: The current engine data
105
177
  """
106
- return EngineIdentity.get_default_engine_id()
107
-
108
- @classmethod
109
- def set_default_engine_id(cls, engine_id: str) -> None:
110
- """Set the default engine ID.
178
+ engine_data = None
179
+
180
+ # Step 1: Determine which engine ID to use
181
+ target_engine_id = os.getenv("GTN_ENGINE_ID")
182
+ if not target_engine_id:
183
+ # Use default or first available
184
+ if self._engines_data.default_engine_id:
185
+ target_engine_id = self._engines_data.default_engine_id
186
+ elif self._engines_data.engines:
187
+ engine_data = self._engines_data.engines[0]
188
+ else:
189
+ # No engines exist, will create new one
190
+ target_engine_id = str(uuid.uuid4())
191
+
192
+ # Step 2: Try to find existing engine if we don't already have one
193
+ if engine_data is None and target_engine_id is not None:
194
+ engine_data = self._find_engine_by_id(self._engines_data, target_engine_id)
195
+
196
+ # Step 3: Create new engine if not found
197
+ if engine_data is None:
198
+ # If target_engine_id is still None, generate a new UUID
199
+ if target_engine_id is None:
200
+ target_engine_id = str(uuid.uuid4())
201
+ engine_data = EngineData(
202
+ id=target_engine_id,
203
+ name=generate_engine_name(),
204
+ created_at=datetime.now(tz=UTC).isoformat(),
205
+ )
206
+ self._add_or_update_engine(engine_data)
207
+
208
+ # Register engine with BaseEvent
209
+ BaseEvent._engine_id = engine_data.id
210
+ self._active_engine_id = engine_data.id
211
+ logger.debug("Initialized engine ID: %s", engine_data.id)
212
+ return engine_data
213
+
214
+ def _add_or_update_engine(self, engine_data: EngineData) -> None:
215
+ """Add or update an engine in the engines data structure.
111
216
 
112
217
  Args:
113
- engine_id: The engine ID to set as default
114
-
115
- Raises:
116
- ValueError: If engine_id is not found in registered engines
218
+ engine_data: The engine data to add or update
117
219
  """
118
- try:
119
- EngineIdentity.set_default_engine_id(engine_id)
120
- logger.info("Set default engine ID to: %s", engine_id)
121
- except ValueError as e:
122
- logger.error("Failed to set default engine ID: %s", e)
123
- raise
220
+ # Find existing engine
221
+ existing_engine = self._find_engine_by_id(self._engines_data, engine_data.id)
222
+
223
+ if existing_engine:
224
+ # Update existing engine
225
+ existing_engine.name = engine_data.name
226
+ existing_engine.created_at = engine_data.created_at
227
+ existing_engine.updated_at = datetime.now(tz=UTC).isoformat()
228
+ else:
229
+ # Add new engine
230
+ self._engines_data.engines.append(engine_data)
231
+
232
+ # Set as default if it's the first engine
233
+ if self._engines_data.default_engine_id is None and len(self._engines_data.engines) == 1:
234
+ self._engines_data.default_engine_id = engine_data.id
235
+
236
+ self._save_engines_data(self._engines_data)
124
237
 
125
- @classmethod
126
- def get_engine_data_file_path(cls) -> Path:
127
- """Get the path where engine data is stored (for debugging/inspection).
238
+ def _load_engines_data(self) -> EnginesStorage:
239
+ """Load engines data from storage.
128
240
 
129
241
  Returns:
130
- Path: The path to the engine data file
242
+ EnginesStorage: Engines data structure with engines array and default_engine_id
131
243
  """
132
- return EngineIdentity.get_engine_data_file_path()
244
+ engine_data_file = self._get_engine_data_file()
133
245
 
134
- @classmethod
135
- def ensure_engine_initialized(cls) -> str:
136
- """Ensure engine is initialized and return the engine ID.
246
+ if engine_data_file.exists():
247
+ try:
248
+ with engine_data_file.open("r") as f:
249
+ data = json.load(f)
250
+ if isinstance(data, dict) and "engines" in data:
251
+ return EnginesStorage.model_validate(data)
252
+ except (json.JSONDecodeError, OSError):
253
+ pass
254
+
255
+ return EnginesStorage(engines=[], default_engine_id=None)
256
+
257
+ def _save_engines_data(self, engines_data: EnginesStorage) -> None:
258
+ """Save engines data to storage.
259
+
260
+ Args:
261
+ engines_data: Engines data structure to save
262
+ """
263
+ engine_data_dir = self._get_engine_data_dir()
264
+ engine_data_dir.mkdir(parents=True, exist_ok=True)
265
+
266
+ engine_data_file = self._get_engine_data_file()
267
+ with engine_data_file.open("w") as f:
268
+ json.dump(engines_data.model_dump(exclude_none=True), f, indent=2)
269
+
270
+ # Update in-memory copy
271
+ self._engines_data = engines_data
272
+
273
+ @staticmethod
274
+ def _get_engine_data_dir() -> Path:
275
+ """Get the XDG data directory for engine identity storage."""
276
+ return xdg_data_home() / "griptape_nodes"
277
+
278
+ @staticmethod
279
+ def _get_engine_data_file() -> Path:
280
+ """Get the path to the engine data storage file."""
281
+ return EngineIdentityManager._get_engine_data_dir() / EngineIdentityManager._ENGINE_DATA_FILE
282
+
283
+ @staticmethod
284
+ def _find_engine_by_id(engines_data: EnginesStorage, engine_id: str) -> EngineData | None:
285
+ """Find an engine by ID in the engines data.
286
+
287
+ Args:
288
+ engines_data: The engines data structure
289
+ engine_id: The engine ID to find
137
290
 
138
291
  Returns:
139
- str: The initialized engine ID
292
+ EngineData | None: The engine data if found, None otherwise
140
293
  """
141
- cls.initialize_engine_id()
142
- engine_id = cls.get_active_engine_id()
143
- if engine_id is None:
144
- msg = "Failed to initialize engine ID"
145
- raise RuntimeError(msg)
146
- return engine_id
294
+ for engine in engines_data.engines:
295
+ if engine.id == engine_id:
296
+ return engine
297
+ return None