griptape-nodes 0.65.5__py3-none-any.whl → 0.66.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.
- griptape_nodes/common/node_executor.py +352 -27
- griptape_nodes/drivers/storage/base_storage_driver.py +12 -3
- griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +18 -2
- griptape_nodes/drivers/storage/local_storage_driver.py +42 -5
- griptape_nodes/exe_types/base_iterative_nodes.py +0 -1
- griptape_nodes/exe_types/connections.py +42 -0
- griptape_nodes/exe_types/core_types.py +2 -2
- griptape_nodes/exe_types/node_groups/__init__.py +2 -1
- griptape_nodes/exe_types/node_groups/base_iterative_node_group.py +177 -0
- griptape_nodes/exe_types/node_groups/base_node_group.py +1 -0
- griptape_nodes/exe_types/node_groups/subflow_node_group.py +35 -2
- griptape_nodes/exe_types/param_types/parameter_audio.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_bool.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_button.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_float.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_image.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_int.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_number.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_string.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_three_d.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_video.py +1 -1
- griptape_nodes/machines/control_flow.py +5 -4
- griptape_nodes/machines/dag_builder.py +121 -55
- griptape_nodes/machines/fsm.py +10 -0
- griptape_nodes/machines/parallel_resolution.py +39 -38
- griptape_nodes/machines/sequential_resolution.py +29 -3
- griptape_nodes/node_library/library_registry.py +41 -2
- griptape_nodes/retained_mode/events/library_events.py +147 -8
- griptape_nodes/retained_mode/events/os_events.py +12 -4
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +2 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/incompatible_requirements_problem.py +34 -0
- griptape_nodes/retained_mode/managers/flow_manager.py +133 -20
- griptape_nodes/retained_mode/managers/library_manager.py +1324 -564
- griptape_nodes/retained_mode/managers/node_manager.py +9 -3
- griptape_nodes/retained_mode/managers/os_manager.py +429 -65
- griptape_nodes/retained_mode/managers/resource_types/compute_resource.py +82 -0
- griptape_nodes/retained_mode/managers/resource_types/os_resource.py +17 -0
- griptape_nodes/retained_mode/managers/static_files_manager.py +21 -8
- griptape_nodes/retained_mode/managers/version_compatibility_manager.py +3 -3
- griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +5 -5
- griptape_nodes/version_compatibility/versions/v0_65_4/__init__.py +5 -0
- griptape_nodes/version_compatibility/versions/v0_65_4/run_in_parallel_to_run_in_order.py +79 -0
- griptape_nodes/version_compatibility/versions/v0_65_5/__init__.py +5 -0
- griptape_nodes/version_compatibility/versions/v0_65_5/flux_2_removed_parameters.py +85 -0
- {griptape_nodes-0.65.5.dist-info → griptape_nodes-0.66.0.dist-info}/METADATA +1 -1
- {griptape_nodes-0.65.5.dist-info → griptape_nodes-0.66.0.dist-info}/RECORD +48 -53
- griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +0 -45
- griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +0 -191
- griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +0 -346
- griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +0 -439
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +0 -17
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +0 -82
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +0 -116
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +0 -367
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +0 -104
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +0 -155
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +0 -18
- griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +0 -12
- {griptape_nodes-0.65.5.dist-info → griptape_nodes-0.66.0.dist-info}/WHEEL +0 -0
- {griptape_nodes-0.65.5.dist-info → griptape_nodes-0.66.0.dist-info}/entry_points.txt +0 -0
|
@@ -6,6 +6,8 @@ from urllib.parse import urljoin
|
|
|
6
6
|
import httpx
|
|
7
7
|
|
|
8
8
|
from griptape_nodes.drivers.storage.base_storage_driver import BaseStorageDriver, CreateSignedUploadUrlResponse
|
|
9
|
+
from griptape_nodes.retained_mode.events.os_events import ExistingFilePolicy, WriteFileRequest
|
|
10
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
9
11
|
|
|
10
12
|
logger = logging.getLogger("griptape_nodes")
|
|
11
13
|
|
|
@@ -38,24 +40,59 @@ class LocalStorageDriver(BaseStorageDriver):
|
|
|
38
40
|
else:
|
|
39
41
|
self.base_url = base_url
|
|
40
42
|
|
|
41
|
-
def create_signed_upload_url(
|
|
43
|
+
def create_signed_upload_url(
|
|
44
|
+
self, path: Path, existing_file_policy: ExistingFilePolicy = ExistingFilePolicy.OVERWRITE
|
|
45
|
+
) -> CreateSignedUploadUrlResponse:
|
|
46
|
+
# on_write_file_request seems to work most reliably with an absolute path.
|
|
47
|
+
absolute_path = path if path.is_absolute() else self.workspace_directory / path
|
|
48
|
+
|
|
49
|
+
# Always delegate to OSManager for file path resolution and policy handling.
|
|
50
|
+
# Creating an empty file before the upload url gives us a chance to claim ownership
|
|
51
|
+
# of that particular file when creating the upload url. The file policy is not
|
|
52
|
+
# checked when actually uploading the file, it will always overwrite.
|
|
53
|
+
os_manager = GriptapeNodes.OSManager()
|
|
54
|
+
write_request = WriteFileRequest(
|
|
55
|
+
file_path=str(absolute_path),
|
|
56
|
+
content=b"", # Empty content for URL generation
|
|
57
|
+
existing_file_policy=existing_file_policy,
|
|
58
|
+
)
|
|
59
|
+
result = os_manager.on_write_file_request(write_request)
|
|
60
|
+
|
|
61
|
+
if not result.succeeded():
|
|
62
|
+
msg = f"WriteFileRequest failed: {result.result_details}"
|
|
63
|
+
raise FileExistsError(msg)
|
|
64
|
+
|
|
65
|
+
# Use the resolved filename from OSManager
|
|
66
|
+
# Type checker: result is WriteFileResultSuccess when succeeded() is True
|
|
67
|
+
resolved_path = Path(result.final_file_path) # type: ignore[attr-defined]
|
|
68
|
+
|
|
69
|
+
# WriteFileRequest always returns an absolute path, but the url needs
|
|
70
|
+
# to be workspace relative if passed in path was workspace relative.
|
|
71
|
+
if not path.is_absolute():
|
|
72
|
+
resolved_path = resolved_path.relative_to(self.workspace_directory)
|
|
73
|
+
|
|
42
74
|
static_url = urljoin(self.base_url, "/static-upload-urls")
|
|
43
75
|
try:
|
|
44
|
-
response = httpx.post(static_url, json={"file_path": str(
|
|
76
|
+
response = httpx.post(static_url, json={"file_path": str(resolved_path)})
|
|
45
77
|
response.raise_for_status()
|
|
46
78
|
except httpx.HTTPStatusError as e:
|
|
47
|
-
msg = f"Failed to create upload URL for file {
|
|
79
|
+
msg = f"Failed to create upload URL for file {resolved_path}: {e}"
|
|
48
80
|
logger.error(msg)
|
|
49
81
|
raise RuntimeError(msg) from e
|
|
50
82
|
|
|
51
83
|
response_data = response.json()
|
|
52
84
|
url = response_data.get("url")
|
|
53
85
|
if url is None:
|
|
54
|
-
msg = f"Failed to get upload URL for file {
|
|
86
|
+
msg = f"Failed to get upload URL for file {resolved_path}: {response_data}"
|
|
55
87
|
logger.error(msg)
|
|
56
88
|
raise ValueError(msg)
|
|
57
89
|
|
|
58
|
-
return {
|
|
90
|
+
return {
|
|
91
|
+
"url": url,
|
|
92
|
+
"headers": response_data.get("headers", {}),
|
|
93
|
+
"method": "PUT",
|
|
94
|
+
"file_path": str(resolved_path),
|
|
95
|
+
}
|
|
59
96
|
|
|
60
97
|
def create_signed_download_url(self, path: Path) -> str:
|
|
61
98
|
# The base_url already includes the /static path, so just append the path
|
|
@@ -108,7 +108,6 @@ class BaseIterativeStartNode(BaseNode):
|
|
|
108
108
|
_current_iteration_count: int
|
|
109
109
|
_total_iterations: int
|
|
110
110
|
_flow: ControlFlow | None = None
|
|
111
|
-
is_parallel: bool = False # Sequential by default
|
|
112
111
|
|
|
113
112
|
def __init__(self, name: str, metadata: dict[Any, Any] | None = None) -> None:
|
|
114
113
|
super().__init__(name, metadata)
|
|
@@ -403,3 +403,45 @@ class Connections:
|
|
|
403
403
|
for conn_id in connection_ids:
|
|
404
404
|
connections.append(self.connections[conn_id]) # noqa: PERF401, Keeping loop for understanding.
|
|
405
405
|
return connections
|
|
406
|
+
|
|
407
|
+
def is_node_in_forward_control_path(
|
|
408
|
+
self, start_node: BaseNode, target_node: BaseNode, visited: set[str] | None = None
|
|
409
|
+
) -> bool:
|
|
410
|
+
"""Check if target_node is reachable from start_node through control flow connections.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
start_node: The node to start traversal from
|
|
414
|
+
target_node: The node to check if reachable
|
|
415
|
+
visited: Set of already visited node names (used internally for recursion)
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
True if target_node is in the forward control path from start_node, False otherwise
|
|
419
|
+
"""
|
|
420
|
+
if visited is None:
|
|
421
|
+
visited = set()
|
|
422
|
+
|
|
423
|
+
if start_node.name in visited:
|
|
424
|
+
return False
|
|
425
|
+
visited.add(start_node.name)
|
|
426
|
+
|
|
427
|
+
# Check ALL outgoing control connections
|
|
428
|
+
# This handles IfElse nodes that have multiple possible control outputs
|
|
429
|
+
if start_node.name in self.outgoing_index:
|
|
430
|
+
for param_name, connection_ids in self.outgoing_index[start_node.name].items():
|
|
431
|
+
# Find the parameter to check if it's a control type
|
|
432
|
+
param = start_node.get_parameter_by_name(param_name)
|
|
433
|
+
if param and param.output_type == ParameterTypeBuiltin.CONTROL_TYPE.value:
|
|
434
|
+
# This is a control parameter - check all its connections
|
|
435
|
+
for connection_id in connection_ids:
|
|
436
|
+
if connection_id in self.connections:
|
|
437
|
+
connection = self.connections[connection_id]
|
|
438
|
+
next_node = connection.target_node
|
|
439
|
+
|
|
440
|
+
if next_node.name == target_node.name:
|
|
441
|
+
return True
|
|
442
|
+
|
|
443
|
+
# Recursively check the forward path
|
|
444
|
+
if self.is_node_in_forward_control_path(next_node, target_node, visited):
|
|
445
|
+
return True
|
|
446
|
+
|
|
447
|
+
return False
|
|
@@ -1967,7 +1967,7 @@ class ParameterContainer(Parameter, ABC):
|
|
|
1967
1967
|
converters: list[Callable[[Any], Any]] | None = None,
|
|
1968
1968
|
validators: list[Callable[[Parameter, Any], None]] | None = None,
|
|
1969
1969
|
*,
|
|
1970
|
-
hide: bool =
|
|
1970
|
+
hide: bool | None = None,
|
|
1971
1971
|
settable: bool = True,
|
|
1972
1972
|
user_defined: bool = False,
|
|
1973
1973
|
element_id: str | None = None,
|
|
@@ -2037,7 +2037,7 @@ class ParameterList(ParameterContainer):
|
|
|
2037
2037
|
converters: list[Callable[[Any], Any]] | None = None,
|
|
2038
2038
|
validators: list[Callable[[Parameter, Any], None]] | None = None,
|
|
2039
2039
|
*,
|
|
2040
|
-
hide: bool =
|
|
2040
|
+
hide: bool | None = None,
|
|
2041
2041
|
settable: bool = True,
|
|
2042
2042
|
user_defined: bool = False,
|
|
2043
2043
|
element_id: str | None = None,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Node group implementations for managing collections of nodes."""
|
|
2
2
|
|
|
3
|
+
from .base_iterative_node_group import BaseIterativeNodeGroup
|
|
3
4
|
from .base_node_group import BaseNodeGroup
|
|
4
5
|
from .subflow_node_group import SubflowNodeGroup
|
|
5
6
|
|
|
6
|
-
__all__ = ["BaseNodeGroup", "SubflowNodeGroup"]
|
|
7
|
+
__all__ = ["BaseIterativeNodeGroup", "BaseNodeGroup", "SubflowNodeGroup"]
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Base class for iterative node groups (ForEach, ForLoop, etc.)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from abc import abstractmethod
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from griptape_nodes.exe_types.core_types import (
|
|
10
|
+
Parameter,
|
|
11
|
+
ParameterMode,
|
|
12
|
+
ParameterTypeBuiltin,
|
|
13
|
+
)
|
|
14
|
+
from griptape_nodes.exe_types.node_groups.subflow_node_group import SubflowNodeGroup
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger("griptape_nodes")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BaseIterativeNodeGroup(SubflowNodeGroup):
|
|
20
|
+
"""Base class for iterative node groups (ForEach, ForLoop, etc.).
|
|
21
|
+
|
|
22
|
+
Combines the functionality of BaseIterativeStartNode and BaseIterativeEndNode
|
|
23
|
+
into a single group node that encapsulates the loop body as child nodes.
|
|
24
|
+
|
|
25
|
+
This provides a simpler user experience than separate start/end nodes while
|
|
26
|
+
maintaining the same execution capabilities (sequential/parallel, local/private/cloud).
|
|
27
|
+
|
|
28
|
+
The NodeExecutor detects instances of this class and handles iteration execution
|
|
29
|
+
via handle_iterative_group_execution(), similar to how it handles BaseIterativeEndNode.
|
|
30
|
+
|
|
31
|
+
Subclasses must implement:
|
|
32
|
+
- _get_iteration_items(): Return the list of items to iterate over
|
|
33
|
+
- _get_current_item_value(iteration_index): Get the value for current iteration
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# Iteration state
|
|
37
|
+
_items: list[Any]
|
|
38
|
+
_current_iteration_count: int
|
|
39
|
+
_total_iterations: int
|
|
40
|
+
is_parallel: bool
|
|
41
|
+
|
|
42
|
+
# Results storage
|
|
43
|
+
_results_list: list[Any]
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
name: str,
|
|
48
|
+
metadata: dict[Any, Any] | None = None,
|
|
49
|
+
) -> None:
|
|
50
|
+
super().__init__(name, metadata)
|
|
51
|
+
|
|
52
|
+
# Initialize iteration state
|
|
53
|
+
self._items = []
|
|
54
|
+
self._current_iteration_count = 0
|
|
55
|
+
self._total_iterations = 0
|
|
56
|
+
self.is_parallel = False
|
|
57
|
+
self._results_list = []
|
|
58
|
+
|
|
59
|
+
# Add parallel execution control parameter
|
|
60
|
+
self.run_in_order = Parameter(
|
|
61
|
+
name="run_in_order",
|
|
62
|
+
tooltip="Execute all iterations in order or concurrently",
|
|
63
|
+
type=ParameterTypeBuiltin.BOOL.value,
|
|
64
|
+
allowed_modes={ParameterMode.PROPERTY},
|
|
65
|
+
default_value=True,
|
|
66
|
+
ui_options={"display_name": "Run in Order"},
|
|
67
|
+
)
|
|
68
|
+
self.add_parameter(self.run_in_order)
|
|
69
|
+
|
|
70
|
+
# Index parameter - available in all iterative nodes (left side - feeds into group)
|
|
71
|
+
self.index_param = Parameter(
|
|
72
|
+
name="index",
|
|
73
|
+
tooltip="Current index of the iteration",
|
|
74
|
+
type=ParameterTypeBuiltin.INT.value,
|
|
75
|
+
allowed_modes={ParameterMode.OUTPUT},
|
|
76
|
+
settable=False,
|
|
77
|
+
default_value=0,
|
|
78
|
+
)
|
|
79
|
+
self.add_parameter(self.index_param)
|
|
80
|
+
|
|
81
|
+
# Track left parameters for UI layout
|
|
82
|
+
if "left_parameters" not in self.metadata:
|
|
83
|
+
self.metadata["left_parameters"] = []
|
|
84
|
+
self.metadata["left_parameters"].append("index")
|
|
85
|
+
|
|
86
|
+
# Results collection parameters (right side - collects from group)
|
|
87
|
+
self.new_item_to_add = Parameter(
|
|
88
|
+
name="new_item_to_add",
|
|
89
|
+
tooltip="Item to add to results list for each iteration",
|
|
90
|
+
type=ParameterTypeBuiltin.ANY.value,
|
|
91
|
+
allowed_modes={ParameterMode.INPUT},
|
|
92
|
+
)
|
|
93
|
+
self.add_parameter(self.new_item_to_add)
|
|
94
|
+
|
|
95
|
+
self.results = Parameter(
|
|
96
|
+
name="results",
|
|
97
|
+
tooltip="Collected results from all iterations",
|
|
98
|
+
output_type="list",
|
|
99
|
+
allowed_modes={ParameterMode.OUTPUT},
|
|
100
|
+
)
|
|
101
|
+
self.add_parameter(self.results)
|
|
102
|
+
|
|
103
|
+
# Track right parameters for UI layout
|
|
104
|
+
if "right_parameters" not in self.metadata:
|
|
105
|
+
self.metadata["right_parameters"] = []
|
|
106
|
+
self.metadata["right_parameters"].extend(["new_item_to_add", "results"])
|
|
107
|
+
|
|
108
|
+
def after_value_set(self, parameter: Parameter, value: Any) -> None:
|
|
109
|
+
"""Handle parameter value changes."""
|
|
110
|
+
super().after_value_set(parameter, value)
|
|
111
|
+
if parameter == self.run_in_order:
|
|
112
|
+
self.is_parallel = not value
|
|
113
|
+
|
|
114
|
+
@abstractmethod
|
|
115
|
+
def _get_iteration_items(self) -> list[Any]:
|
|
116
|
+
"""Get the list of items to iterate over.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
List of items for iteration. Empty list if no items.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
@abstractmethod
|
|
123
|
+
def _get_current_item_value(self, iteration_index: int) -> Any:
|
|
124
|
+
"""Get the value for a specific iteration.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
iteration_index: 0-based iteration index
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
The value to use for this iteration
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
def _initialize_iteration_data(self) -> None:
|
|
134
|
+
"""Initialize iteration-specific data and state."""
|
|
135
|
+
self._items = self._get_iteration_items()
|
|
136
|
+
self._total_iterations = len(self._items) if self._items else 0
|
|
137
|
+
self._current_iteration_count = 0
|
|
138
|
+
self._results_list = []
|
|
139
|
+
|
|
140
|
+
def _get_total_iterations(self) -> int:
|
|
141
|
+
"""Return the total number of iterations for this loop."""
|
|
142
|
+
return self._total_iterations
|
|
143
|
+
|
|
144
|
+
def get_all_iteration_values(self) -> list[int]:
|
|
145
|
+
"""Calculate and return all iteration index values.
|
|
146
|
+
|
|
147
|
+
For ForEach nodes, this returns indices 0, 1, 2, ...
|
|
148
|
+
For ForLoop nodes, this could return actual loop values.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
List of integer values for each iteration
|
|
152
|
+
"""
|
|
153
|
+
return list(range(self._get_total_iterations()))
|
|
154
|
+
|
|
155
|
+
def _output_results_list(self) -> None:
|
|
156
|
+
"""Output the current results list to the results parameter."""
|
|
157
|
+
import copy
|
|
158
|
+
|
|
159
|
+
self.parameter_output_values["results"] = copy.deepcopy(self._results_list)
|
|
160
|
+
|
|
161
|
+
def reset_for_workflow_run(self) -> None:
|
|
162
|
+
"""Reset state for a fresh workflow run."""
|
|
163
|
+
self._results_list = []
|
|
164
|
+
self._current_iteration_count = 0
|
|
165
|
+
self._total_iterations = 0
|
|
166
|
+
self._output_results_list()
|
|
167
|
+
|
|
168
|
+
async def aprocess(self) -> None:
|
|
169
|
+
"""Execute the iterative node group.
|
|
170
|
+
|
|
171
|
+
Note: This method is typically not called directly. The NodeExecutor
|
|
172
|
+
detects BaseIterativeNodeGroup instances and calls handle_iterative_group_execution()
|
|
173
|
+
instead. This implementation exists as a fallback for direct local execution.
|
|
174
|
+
"""
|
|
175
|
+
# For direct local execution (when NodeExecutor doesn't intercept),
|
|
176
|
+
# just execute the subflow once. The NodeExecutor handles iteration logic.
|
|
177
|
+
await self.execute_subflow()
|
|
@@ -14,6 +14,7 @@ from griptape_nodes.exe_types.core_types import (
|
|
|
14
14
|
from griptape_nodes.exe_types.node_groups.base_node_group import BaseNodeGroup
|
|
15
15
|
from griptape_nodes.exe_types.node_types import (
|
|
16
16
|
LOCAL_EXECUTION,
|
|
17
|
+
PRIVATE_EXECUTION,
|
|
17
18
|
get_library_names_with_publish_handlers,
|
|
18
19
|
)
|
|
19
20
|
from griptape_nodes.retained_mode.events.connection_events import (
|
|
@@ -76,6 +77,7 @@ class SubflowNodeGroup(BaseNodeGroup, ABC):
|
|
|
76
77
|
"start_flow_node": "StartFlow",
|
|
77
78
|
"parameter_names": {},
|
|
78
79
|
}
|
|
80
|
+
self.metadata["executable"] = True
|
|
79
81
|
|
|
80
82
|
# Don't create subflow in __init__ - it will be created on-demand when nodes are added
|
|
81
83
|
# or restored during deserialization
|
|
@@ -298,6 +300,26 @@ class SubflowNodeGroup(BaseNodeGroup, ABC):
|
|
|
298
300
|
|
|
299
301
|
return proxy_param
|
|
300
302
|
|
|
303
|
+
def add_parameter_to_group_settings(self, parameter: Parameter) -> None:
|
|
304
|
+
"""Add a parameter to the Group settings panel.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
parameter: The parameter to add to settings
|
|
308
|
+
"""
|
|
309
|
+
if ParameterMode.PROPERTY not in parameter.allowed_modes:
|
|
310
|
+
msg = f"Parameter '{parameter.name}' must allow PROPERTY mode to be added to settings."
|
|
311
|
+
raise ValueError(msg)
|
|
312
|
+
|
|
313
|
+
execution_environment: dict = self.metadata.get("execution_environment", {})
|
|
314
|
+
if LOCAL_EXECUTION not in execution_environment:
|
|
315
|
+
execution_environment[LOCAL_EXECUTION] = {"parameter_names": []}
|
|
316
|
+
if PRIVATE_EXECUTION not in execution_environment:
|
|
317
|
+
execution_environment[PRIVATE_EXECUTION] = {"parameter_names": []}
|
|
318
|
+
|
|
319
|
+
for library in execution_environment:
|
|
320
|
+
parameter_names = self.metadata["execution_environment"][library].get("parameter_names", [])
|
|
321
|
+
self.metadata["execution_environment"][library]["parameter_names"] = [parameter.name, *parameter_names]
|
|
322
|
+
|
|
301
323
|
def get_all_nodes(self) -> dict[str, BaseNode]:
|
|
302
324
|
all_nodes = {}
|
|
303
325
|
for node_name, node in self.nodes.items():
|
|
@@ -945,15 +967,26 @@ class SubflowNodeGroup(BaseNodeGroup, ABC):
|
|
|
945
967
|
|
|
946
968
|
Can be called by concrete subclasses in their aprocess() implementation.
|
|
947
969
|
"""
|
|
948
|
-
from griptape_nodes.retained_mode.events.execution_events import
|
|
970
|
+
from griptape_nodes.retained_mode.events.execution_events import (
|
|
971
|
+
StartLocalSubflowRequest,
|
|
972
|
+
StartLocalSubflowResultFailure,
|
|
973
|
+
)
|
|
949
974
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
950
975
|
|
|
951
976
|
subflow = self.metadata.get("subflow_name")
|
|
952
977
|
if subflow is not None and isinstance(subflow, str):
|
|
953
|
-
await GriptapeNodes.FlowManager().on_start_local_subflow_request(
|
|
978
|
+
result = await GriptapeNodes.FlowManager().on_start_local_subflow_request(
|
|
954
979
|
StartLocalSubflowRequest(flow_name=subflow)
|
|
955
980
|
)
|
|
956
981
|
|
|
982
|
+
if isinstance(result, StartLocalSubflowResultFailure):
|
|
983
|
+
logger.error("%s: %s", self.name, result.result_details)
|
|
984
|
+
# Clear partial outputs to prevent inconsistent state
|
|
985
|
+
self.parameter_output_values.clear()
|
|
986
|
+
# Re-raise the error message directly without wrapping
|
|
987
|
+
msg = result.result_details
|
|
988
|
+
raise RuntimeError(msg)
|
|
989
|
+
|
|
957
990
|
# After subflow execution, collect output values from internal nodes
|
|
958
991
|
# and set them on the NodeGroup's output (right) proxy parameters
|
|
959
992
|
connections = GriptapeNodes.FlowManager().get_connections()
|
|
@@ -46,7 +46,7 @@ class ParameterAudio(Parameter):
|
|
|
46
46
|
microphone_capture_audio: bool = False,
|
|
47
47
|
edit_audio: bool = False,
|
|
48
48
|
accept_any: bool = True,
|
|
49
|
-
hide: bool =
|
|
49
|
+
hide: bool | None = None,
|
|
50
50
|
hide_label: bool = False,
|
|
51
51
|
hide_property: bool = False,
|
|
52
52
|
allow_input: bool = True,
|
|
@@ -42,7 +42,7 @@ class ParameterBool(Parameter):
|
|
|
42
42
|
on_label: str | None = None,
|
|
43
43
|
off_label: str | None = None,
|
|
44
44
|
accept_any: bool = True,
|
|
45
|
-
hide: bool =
|
|
45
|
+
hide: bool | None = None,
|
|
46
46
|
hide_label: bool = False,
|
|
47
47
|
hide_property: bool = False,
|
|
48
48
|
allow_input: bool = True,
|
|
@@ -88,7 +88,7 @@ class ParameterButton(Parameter):
|
|
|
88
88
|
on_click: Button.OnClickCallback | None = None,
|
|
89
89
|
get_button_state: Button.GetButtonStateCallback | None = None,
|
|
90
90
|
href: str | None = None,
|
|
91
|
-
hide: bool =
|
|
91
|
+
hide: bool | None = None,
|
|
92
92
|
hide_label: bool = False,
|
|
93
93
|
hide_property: bool = False,
|
|
94
94
|
allow_input: bool = False,
|
|
@@ -47,7 +47,7 @@ class ParameterFloat(ParameterNumber):
|
|
|
47
47
|
max_val: float = 100,
|
|
48
48
|
validate_min_max: bool = False,
|
|
49
49
|
accept_any: bool = True,
|
|
50
|
-
hide: bool =
|
|
50
|
+
hide: bool | None = None,
|
|
51
51
|
hide_label: bool = False,
|
|
52
52
|
hide_property: bool = False,
|
|
53
53
|
allow_input: bool = True,
|
|
@@ -46,7 +46,7 @@ class ParameterImage(Parameter):
|
|
|
46
46
|
webcam_capture_image: bool = False,
|
|
47
47
|
edit_mask: bool = False,
|
|
48
48
|
accept_any: bool = True,
|
|
49
|
-
hide: bool =
|
|
49
|
+
hide: bool | None = None,
|
|
50
50
|
hide_label: bool = False,
|
|
51
51
|
hide_property: bool = False,
|
|
52
52
|
allow_input: bool = True,
|
|
@@ -47,7 +47,7 @@ class ParameterInt(ParameterNumber):
|
|
|
47
47
|
max_val: float = 100,
|
|
48
48
|
validate_min_max: bool = False,
|
|
49
49
|
accept_any: bool = True,
|
|
50
|
-
hide: bool =
|
|
50
|
+
hide: bool | None = None,
|
|
51
51
|
hide_label: bool = False,
|
|
52
52
|
hide_property: bool = False,
|
|
53
53
|
allow_input: bool = True,
|
|
@@ -41,7 +41,7 @@ class ParameterNumber(Parameter):
|
|
|
41
41
|
max_val: float = 100,
|
|
42
42
|
validate_min_max: bool = False,
|
|
43
43
|
accept_any: bool = True,
|
|
44
|
-
hide: bool =
|
|
44
|
+
hide: bool | None = None,
|
|
45
45
|
hide_label: bool = False,
|
|
46
46
|
hide_property: bool = False,
|
|
47
47
|
allow_input: bool = True,
|
|
@@ -46,7 +46,7 @@ class ParameterString(Parameter):
|
|
|
46
46
|
placeholder_text: str | None = None,
|
|
47
47
|
is_full_width: bool = False,
|
|
48
48
|
accept_any: bool = True,
|
|
49
|
-
hide: bool =
|
|
49
|
+
hide: bool | None = None,
|
|
50
50
|
hide_label: bool = False,
|
|
51
51
|
hide_property: bool = False,
|
|
52
52
|
allow_input: bool = True,
|
|
@@ -44,7 +44,7 @@ class Parameter3D(Parameter):
|
|
|
44
44
|
clickable_file_browser: bool = True,
|
|
45
45
|
expander: bool = False,
|
|
46
46
|
accept_any: bool = True,
|
|
47
|
-
hide: bool =
|
|
47
|
+
hide: bool | None = None,
|
|
48
48
|
hide_label: bool = False,
|
|
49
49
|
hide_property: bool = False,
|
|
50
50
|
allow_input: bool = True,
|
|
@@ -46,7 +46,7 @@ class ParameterVideo(Parameter):
|
|
|
46
46
|
webcam_capture_video: bool = False,
|
|
47
47
|
edit_video: bool = False,
|
|
48
48
|
accept_any: bool = True,
|
|
49
|
-
hide: bool =
|
|
49
|
+
hide: bool | None = None,
|
|
50
50
|
hide_label: bool = False,
|
|
51
51
|
hide_property: bool = False,
|
|
52
52
|
allow_input: bool = True,
|
|
@@ -480,14 +480,15 @@ class ControlFlowMachine(FSM[ControlFlowContext]):
|
|
|
480
480
|
# Figure out which graph the data node belongs to, if it belongs to a graph.
|
|
481
481
|
for graph_start_node_name in dag_builder.graphs:
|
|
482
482
|
graph_start_node = node_manager.get_node_by_name(graph_start_node_name)
|
|
483
|
-
|
|
483
|
+
# Get boundary nodes (empty list if not connected)
|
|
484
|
+
boundary_nodes = flow_manager.is_node_connected(graph_start_node, node)
|
|
484
485
|
# This means this node is in the downstream connection of one of this graph.
|
|
485
|
-
if
|
|
486
|
+
if boundary_nodes:
|
|
486
487
|
# Is the node connected to a graph?
|
|
487
488
|
disconnected = False
|
|
488
489
|
if node.name not in dag_builder.start_node_candidates:
|
|
489
|
-
dag_builder.start_node_candidates[node.name] =
|
|
490
|
-
dag_builder.start_node_candidates[node.name]
|
|
490
|
+
dag_builder.start_node_candidates[node.name] = {}
|
|
491
|
+
dag_builder.start_node_candidates[node.name][graph_start_node_name] = set(boundary_nodes)
|
|
491
492
|
if disconnected:
|
|
492
493
|
# If the node is not connected to any graph, we can add it as it's own graph here.
|
|
493
494
|
# It will not cause any overlapping confusion with existing graphs.
|