griptape-nodes 0.41.0__py3-none-any.whl → 0.43.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/__init__.py +0 -0
- griptape_nodes/app/.python-version +0 -0
- griptape_nodes/app/__init__.py +1 -10
- griptape_nodes/app/api.py +199 -0
- griptape_nodes/app/app.py +140 -222
- griptape_nodes/app/watch.py +4 -2
- griptape_nodes/bootstrap/__init__.py +0 -0
- griptape_nodes/bootstrap/bootstrap_script.py +0 -0
- griptape_nodes/bootstrap/register_libraries_script.py +0 -0
- griptape_nodes/bootstrap/structure_config.yaml +0 -0
- griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
- griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +0 -0
- griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +0 -0
- griptape_nodes/bootstrap/workflow_runners/__init__.py +0 -0
- griptape_nodes/bootstrap/workflow_runners/bootstrap_workflow_runner.py +0 -0
- griptape_nodes/bootstrap/workflow_runners/local_workflow_runner.py +0 -0
- griptape_nodes/bootstrap/workflow_runners/subprocess_workflow_runner.py +6 -2
- griptape_nodes/bootstrap/workflow_runners/workflow_runner.py +0 -0
- griptape_nodes/drivers/__init__.py +0 -0
- griptape_nodes/drivers/storage/__init__.py +0 -0
- griptape_nodes/drivers/storage/base_storage_driver.py +0 -0
- griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +0 -0
- griptape_nodes/drivers/storage/local_storage_driver.py +5 -3
- griptape_nodes/drivers/storage/storage_backend.py +0 -0
- griptape_nodes/exe_types/__init__.py +0 -0
- griptape_nodes/exe_types/connections.py +0 -0
- griptape_nodes/exe_types/core_types.py +0 -0
- griptape_nodes/exe_types/flow.py +68 -368
- griptape_nodes/exe_types/node_types.py +17 -1
- griptape_nodes/exe_types/type_validator.py +0 -0
- griptape_nodes/machines/__init__.py +0 -0
- griptape_nodes/machines/control_flow.py +52 -20
- griptape_nodes/machines/fsm.py +16 -2
- griptape_nodes/machines/node_resolution.py +16 -14
- griptape_nodes/mcp_server/__init__.py +1 -0
- griptape_nodes/mcp_server/server.py +126 -0
- griptape_nodes/mcp_server/ws_request_manager.py +268 -0
- griptape_nodes/node_library/__init__.py +0 -0
- griptape_nodes/node_library/advanced_node_library.py +0 -0
- griptape_nodes/node_library/library_registry.py +0 -0
- griptape_nodes/node_library/workflow_registry.py +2 -2
- griptape_nodes/py.typed +0 -0
- griptape_nodes/retained_mode/__init__.py +0 -0
- griptape_nodes/retained_mode/events/__init__.py +0 -0
- griptape_nodes/retained_mode/events/agent_events.py +70 -8
- griptape_nodes/retained_mode/events/app_events.py +137 -12
- griptape_nodes/retained_mode/events/arbitrary_python_events.py +23 -0
- griptape_nodes/retained_mode/events/base_events.py +13 -31
- griptape_nodes/retained_mode/events/config_events.py +87 -11
- griptape_nodes/retained_mode/events/connection_events.py +56 -5
- griptape_nodes/retained_mode/events/context_events.py +27 -4
- griptape_nodes/retained_mode/events/execution_events.py +99 -14
- griptape_nodes/retained_mode/events/flow_events.py +165 -7
- griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
- griptape_nodes/retained_mode/events/library_events.py +195 -17
- griptape_nodes/retained_mode/events/logger_events.py +11 -0
- griptape_nodes/retained_mode/events/node_events.py +242 -22
- griptape_nodes/retained_mode/events/object_events.py +40 -4
- griptape_nodes/retained_mode/events/os_events.py +116 -3
- griptape_nodes/retained_mode/events/parameter_events.py +212 -8
- griptape_nodes/retained_mode/events/payload_registry.py +0 -0
- griptape_nodes/retained_mode/events/secrets_events.py +59 -7
- griptape_nodes/retained_mode/events/static_file_events.py +57 -4
- griptape_nodes/retained_mode/events/validation_events.py +39 -4
- griptape_nodes/retained_mode/events/workflow_events.py +188 -17
- griptape_nodes/retained_mode/griptape_nodes.py +89 -363
- griptape_nodes/retained_mode/managers/__init__.py +0 -0
- griptape_nodes/retained_mode/managers/agent_manager.py +49 -23
- griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
- griptape_nodes/retained_mode/managers/config_manager.py +0 -0
- griptape_nodes/retained_mode/managers/context_manager.py +0 -0
- griptape_nodes/retained_mode/managers/engine_identity_manager.py +146 -0
- griptape_nodes/retained_mode/managers/event_manager.py +14 -2
- griptape_nodes/retained_mode/managers/flow_manager.py +751 -64
- griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +45 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +191 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +346 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +439 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +17 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +82 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +116 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +352 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +104 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +155 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +18 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +12 -0
- griptape_nodes/retained_mode/managers/library_manager.py +255 -40
- griptape_nodes/retained_mode/managers/node_manager.py +120 -103
- griptape_nodes/retained_mode/managers/object_manager.py +11 -3
- griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
- griptape_nodes/retained_mode/managers/os_manager.py +582 -8
- griptape_nodes/retained_mode/managers/secrets_manager.py +4 -0
- griptape_nodes/retained_mode/managers/session_manager.py +328 -0
- griptape_nodes/retained_mode/managers/settings.py +7 -0
- griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
- griptape_nodes/retained_mode/managers/version_compatibility_manager.py +2 -2
- griptape_nodes/retained_mode/managers/workflow_manager.py +722 -456
- griptape_nodes/retained_mode/retained_mode.py +44 -0
- griptape_nodes/retained_mode/utils/__init__.py +0 -0
- griptape_nodes/retained_mode/utils/engine_identity.py +141 -27
- griptape_nodes/retained_mode/utils/name_generator.py +0 -0
- griptape_nodes/traits/__init__.py +0 -0
- griptape_nodes/traits/add_param_button.py +0 -0
- griptape_nodes/traits/button.py +0 -0
- griptape_nodes/traits/clamp.py +0 -0
- griptape_nodes/traits/compare.py +0 -0
- griptape_nodes/traits/compare_images.py +0 -0
- griptape_nodes/traits/file_system_picker.py +127 -0
- griptape_nodes/traits/minmax.py +0 -0
- griptape_nodes/traits/options.py +0 -0
- griptape_nodes/traits/slider.py +0 -0
- griptape_nodes/traits/trait_registry.py +0 -0
- griptape_nodes/traits/traits.json +0 -0
- griptape_nodes/updater/__init__.py +2 -2
- griptape_nodes/updater/__main__.py +0 -0
- griptape_nodes/utils/__init__.py +0 -0
- griptape_nodes/utils/dict_utils.py +0 -0
- griptape_nodes/utils/image_preview.py +128 -0
- griptape_nodes/utils/metaclasses.py +0 -0
- griptape_nodes/version_compatibility/__init__.py +0 -0
- griptape_nodes/version_compatibility/versions/__init__.py +0 -0
- griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
- griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +5 -5
- griptape_nodes-0.43.0.dist-info/METADATA +90 -0
- griptape_nodes-0.43.0.dist-info/RECORD +129 -0
- griptape_nodes-0.43.0.dist-info/WHEEL +4 -0
- {griptape_nodes-0.41.0.dist-info → griptape_nodes-0.43.0.dist-info}/entry_points.txt +1 -0
- griptape_nodes/app/app_sessions.py +0 -458
- griptape_nodes/retained_mode/utils/session_persistence.py +0 -105
- griptape_nodes-0.41.0.dist-info/METADATA +0 -78
- griptape_nodes-0.41.0.dist-info/RECORD +0 -112
- griptape_nodes-0.41.0.dist-info/WHEEL +0 -4
- griptape_nodes-0.41.0.dist-info/licenses/LICENSE +0 -201
|
@@ -1,40 +1,28 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
|
-
import importlib.metadata
|
|
5
|
-
import json
|
|
6
4
|
import logging
|
|
7
|
-
import os
|
|
8
5
|
import pickle
|
|
9
6
|
import pkgutil
|
|
10
7
|
import re
|
|
11
|
-
import shutil
|
|
12
|
-
import subprocess
|
|
13
|
-
import tempfile
|
|
14
8
|
from dataclasses import dataclass, field, fields, is_dataclass
|
|
15
9
|
from datetime import UTC, datetime
|
|
16
10
|
from enum import StrEnum
|
|
17
|
-
from importlib import resources
|
|
18
11
|
from inspect import getmodule, isclass
|
|
19
12
|
from pathlib import Path
|
|
20
|
-
from typing import TYPE_CHECKING, Any, ClassVar,
|
|
21
|
-
from urllib.parse import urljoin
|
|
13
|
+
from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, TypeVar, cast
|
|
22
14
|
|
|
23
|
-
import httpx
|
|
24
15
|
import tomlkit
|
|
25
|
-
from dotenv import get_key, set_key
|
|
26
|
-
from dotenv.main import DotEnv
|
|
27
16
|
from rich.box import HEAVY_EDGE
|
|
28
17
|
from rich.console import Console
|
|
29
18
|
from rich.panel import Panel
|
|
30
19
|
from rich.table import Table
|
|
31
20
|
from rich.text import Text
|
|
32
|
-
from xdg_base_dirs import xdg_config_home
|
|
33
21
|
|
|
34
22
|
from griptape_nodes.drivers.storage import StorageBackend
|
|
35
23
|
from griptape_nodes.exe_types.core_types import ParameterTypeBuiltin
|
|
24
|
+
from griptape_nodes.exe_types.flow import ControlFlow
|
|
36
25
|
from griptape_nodes.exe_types.node_types import BaseNode, EndNode, StartNode
|
|
37
|
-
from griptape_nodes.node_library.library_registry import LibraryNameAndVersion, LibraryRegistry
|
|
38
26
|
from griptape_nodes.node_library.workflow_registry import Workflow, WorkflowMetadata, WorkflowRegistry
|
|
39
27
|
from griptape_nodes.retained_mode.events.app_events import (
|
|
40
28
|
GetEngineVersionRequest,
|
|
@@ -48,20 +36,21 @@ from griptape_nodes.retained_mode.events.flow_events import (
|
|
|
48
36
|
SerializedNodeCommands,
|
|
49
37
|
SerializeFlowToCommandsRequest,
|
|
50
38
|
SerializeFlowToCommandsResultSuccess,
|
|
39
|
+
SetFlowMetadataRequest,
|
|
40
|
+
SetFlowMetadataResultSuccess,
|
|
51
41
|
)
|
|
52
42
|
from griptape_nodes.retained_mode.events.library_events import (
|
|
53
43
|
GetLibraryMetadataRequest,
|
|
54
44
|
GetLibraryMetadataResultSuccess,
|
|
55
45
|
)
|
|
56
46
|
from griptape_nodes.retained_mode.events.object_events import ClearAllObjectStateRequest
|
|
57
|
-
from griptape_nodes.retained_mode.events.secrets_events import (
|
|
58
|
-
GetAllSecretValuesRequest,
|
|
59
|
-
GetAllSecretValuesResultSuccess,
|
|
60
|
-
)
|
|
61
47
|
from griptape_nodes.retained_mode.events.workflow_events import (
|
|
62
48
|
DeleteWorkflowRequest,
|
|
63
49
|
DeleteWorkflowResultFailure,
|
|
64
50
|
DeleteWorkflowResultSuccess,
|
|
51
|
+
ImportWorkflowAsReferencedSubFlowRequest,
|
|
52
|
+
ImportWorkflowAsReferencedSubFlowResultFailure,
|
|
53
|
+
ImportWorkflowAsReferencedSubFlowResultSuccess,
|
|
65
54
|
ListAllWorkflowsRequest,
|
|
66
55
|
ListAllWorkflowsResultFailure,
|
|
67
56
|
ListAllWorkflowsResultSuccess,
|
|
@@ -70,7 +59,6 @@ from griptape_nodes.retained_mode.events.workflow_events import (
|
|
|
70
59
|
LoadWorkflowMetadataResultSuccess,
|
|
71
60
|
PublishWorkflowRequest,
|
|
72
61
|
PublishWorkflowResultFailure,
|
|
73
|
-
PublishWorkflowResultSuccess,
|
|
74
62
|
RegisterWorkflowRequest,
|
|
75
63
|
RegisterWorkflowResultFailure,
|
|
76
64
|
RegisterWorkflowResultSuccess,
|
|
@@ -94,12 +82,14 @@ from griptape_nodes.retained_mode.griptape_nodes import (
|
|
|
94
82
|
GriptapeNodes,
|
|
95
83
|
Version,
|
|
96
84
|
)
|
|
85
|
+
from griptape_nodes.retained_mode.managers.os_manager import OSManager
|
|
97
86
|
|
|
98
87
|
if TYPE_CHECKING:
|
|
99
88
|
from collections.abc import Callable, Sequence
|
|
100
89
|
from types import TracebackType
|
|
101
90
|
|
|
102
91
|
from griptape_nodes.exe_types.core_types import Parameter
|
|
92
|
+
from griptape_nodes.node_library.library_registry import LibraryNameAndVersion
|
|
103
93
|
from griptape_nodes.retained_mode.events.base_events import ResultPayload
|
|
104
94
|
from griptape_nodes.retained_mode.managers.event_manager import EventManager
|
|
105
95
|
|
|
@@ -176,6 +166,28 @@ class WorkflowManager:
|
|
|
176
166
|
|
|
177
167
|
_squelch_workflow_altered_count: int = 0
|
|
178
168
|
|
|
169
|
+
# Track referenced workflow import context stack
|
|
170
|
+
class ReferencedWorkflowContext:
|
|
171
|
+
"""Context manager for tracking workflow import operations."""
|
|
172
|
+
|
|
173
|
+
def __init__(self, manager: WorkflowManager, workflow_name: str):
|
|
174
|
+
self.manager = manager
|
|
175
|
+
self.workflow_name = workflow_name
|
|
176
|
+
|
|
177
|
+
def __enter__(self) -> WorkflowManager.ReferencedWorkflowContext:
|
|
178
|
+
self.manager._referenced_workflow_stack.append(self.workflow_name)
|
|
179
|
+
return self
|
|
180
|
+
|
|
181
|
+
def __exit__(
|
|
182
|
+
self,
|
|
183
|
+
exc_type: type[BaseException] | None,
|
|
184
|
+
exc_value: BaseException | None,
|
|
185
|
+
exc_traceback: TracebackType | None,
|
|
186
|
+
) -> None:
|
|
187
|
+
self.manager._referenced_workflow_stack.pop()
|
|
188
|
+
|
|
189
|
+
_referenced_workflow_stack: list[str] = field(default_factory=list)
|
|
190
|
+
|
|
179
191
|
class WorkflowExecutionResult(NamedTuple):
|
|
180
192
|
"""Result of a workflow execution."""
|
|
181
193
|
|
|
@@ -185,6 +197,7 @@ class WorkflowManager:
|
|
|
185
197
|
def __init__(self, event_manager: EventManager) -> None:
|
|
186
198
|
self._workflow_file_path_to_info = {}
|
|
187
199
|
self._squelch_workflow_altered_count = 0
|
|
200
|
+
self._referenced_workflow_stack = []
|
|
188
201
|
|
|
189
202
|
event_manager.assign_manager_to_request_type(
|
|
190
203
|
RunWorkflowFromScratchRequest, self.on_run_workflow_from_scratch_request
|
|
@@ -223,6 +236,22 @@ class WorkflowManager:
|
|
|
223
236
|
PublishWorkflowRequest,
|
|
224
237
|
self.on_publish_workflow_request,
|
|
225
238
|
)
|
|
239
|
+
event_manager.assign_manager_to_request_type(
|
|
240
|
+
ImportWorkflowAsReferencedSubFlowRequest,
|
|
241
|
+
self.on_import_workflow_as_referenced_sub_flow_request,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
def has_current_referenced_workflow(self) -> bool:
|
|
245
|
+
"""Check if there is currently a referenced workflow context active."""
|
|
246
|
+
return len(self._referenced_workflow_stack) > 0
|
|
247
|
+
|
|
248
|
+
def get_current_referenced_workflow(self) -> str:
|
|
249
|
+
"""Get the current workflow source path from the context stack.
|
|
250
|
+
|
|
251
|
+
Raises:
|
|
252
|
+
IndexError: If no referenced workflow context is active.
|
|
253
|
+
"""
|
|
254
|
+
return self._referenced_workflow_stack[-1]
|
|
226
255
|
|
|
227
256
|
def on_libraries_initialization_complete(self) -> None:
|
|
228
257
|
# All of the libraries have loaded, and any workflows they came with have been registered.
|
|
@@ -367,6 +396,42 @@ class WorkflowManager:
|
|
|
367
396
|
def should_squelch_workflow_altered(self) -> bool:
|
|
368
397
|
return self._squelch_workflow_altered_count > 0
|
|
369
398
|
|
|
399
|
+
def _ensure_workflow_context_established(self) -> None:
|
|
400
|
+
"""Ensure there's a current workflow and flow context after workflow execution."""
|
|
401
|
+
context_manager = GriptapeNodes.ContextManager()
|
|
402
|
+
|
|
403
|
+
# First check: Do we have a current workflow? If not, that's a critical failure.
|
|
404
|
+
if not context_manager.has_current_workflow():
|
|
405
|
+
error_message = "Workflow execution completed but no current workflow is established in context"
|
|
406
|
+
raise RuntimeError(error_message)
|
|
407
|
+
|
|
408
|
+
# Second check: Do we have a current flow? If not, try to establish one.
|
|
409
|
+
if not context_manager.has_current_flow():
|
|
410
|
+
# Use the proper request to get the top-level flow
|
|
411
|
+
from griptape_nodes.retained_mode.events.flow_events import (
|
|
412
|
+
GetTopLevelFlowRequest,
|
|
413
|
+
GetTopLevelFlowResultSuccess,
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
top_level_flow_request = GetTopLevelFlowRequest()
|
|
417
|
+
top_level_flow_result = GriptapeNodes.handle_request(top_level_flow_request)
|
|
418
|
+
|
|
419
|
+
if (
|
|
420
|
+
isinstance(top_level_flow_result, GetTopLevelFlowResultSuccess)
|
|
421
|
+
and top_level_flow_result.flow_name is not None
|
|
422
|
+
):
|
|
423
|
+
# Push the flow to the context stack permanently using FlowManager
|
|
424
|
+
flow_manager = GriptapeNodes.FlowManager()
|
|
425
|
+
flow_obj = flow_manager.get_flow_by_name(top_level_flow_result.flow_name)
|
|
426
|
+
context_manager.push_flow(flow_obj)
|
|
427
|
+
details = f"Workflow execution completed. Set '{top_level_flow_result.flow_name}' as current context."
|
|
428
|
+
logger.debug(details)
|
|
429
|
+
|
|
430
|
+
# If we still don't have a flow, that's a critical error
|
|
431
|
+
if not context_manager.has_current_flow():
|
|
432
|
+
error_message = "Workflow execution completed but no current flow context could be established"
|
|
433
|
+
raise RuntimeError(error_message)
|
|
434
|
+
|
|
370
435
|
def run_workflow(self, relative_file_path: str) -> WorkflowExecutionResult:
|
|
371
436
|
relative_file_path_obj = Path(relative_file_path)
|
|
372
437
|
if relative_file_path_obj.is_absolute():
|
|
@@ -379,6 +444,12 @@ class WorkflowManager:
|
|
|
379
444
|
with Path(complete_file_path).open(encoding="utf-8") as file:
|
|
380
445
|
workflow_content = file.read()
|
|
381
446
|
exec(workflow_content) # noqa: S102
|
|
447
|
+
|
|
448
|
+
# After workflow execution, ensure there's always a current context by pushing
|
|
449
|
+
# the top-level flow if the context is empty. This fixes regressions where
|
|
450
|
+
# with Workflow Schema version 0.6.0+ workflows expect context to be established.
|
|
451
|
+
self._ensure_workflow_context_established()
|
|
452
|
+
|
|
382
453
|
except Exception as e:
|
|
383
454
|
return WorkflowManager.WorkflowExecutionResult(
|
|
384
455
|
execution_successful=False,
|
|
@@ -829,9 +900,9 @@ class WorkflowManager:
|
|
|
829
900
|
if workflow_metadata.is_griptape_provided:
|
|
830
901
|
workflow_metadata.image = workflow_metadata.image
|
|
831
902
|
else:
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
903
|
+
# For user workflows, the image should be just the filename, not a full path
|
|
904
|
+
# The frontend now sends just filenames, so we don't need to prepend the workspace path
|
|
905
|
+
workflow_metadata.image = workflow_metadata.image
|
|
835
906
|
|
|
836
907
|
# Register it as a success.
|
|
837
908
|
workflow_register_request = RegisterWorkflowRequest(
|
|
@@ -860,66 +931,38 @@ class WorkflowManager:
|
|
|
860
931
|
|
|
861
932
|
return import_statements
|
|
862
933
|
|
|
863
|
-
def
|
|
864
|
-
|
|
865
|
-
|
|
934
|
+
def _generate_workflow_file_contents_and_metadata( # noqa: C901, PLR0912, PLR0915
|
|
935
|
+
self, file_name: str, creation_date: datetime, image_path: str | None = None
|
|
936
|
+
) -> tuple[str, WorkflowMetadata]:
|
|
937
|
+
"""Generate the contents of a workflow file.
|
|
866
938
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
prior_workflow = None
|
|
872
|
-
creation_date = None
|
|
873
|
-
if file_name and WorkflowRegistry.has_workflow_with_name(file_name):
|
|
874
|
-
# Get the metadata.
|
|
875
|
-
prior_workflow = WorkflowRegistry.get_workflow_by_name(file_name)
|
|
876
|
-
# We'll use its creation date.
|
|
877
|
-
creation_date = prior_workflow.metadata.creation_date
|
|
878
|
-
|
|
879
|
-
if (creation_date is None) or (creation_date == WorkflowManager.EPOCH_START):
|
|
880
|
-
# Either a new workflow, or a backcompat situation.
|
|
881
|
-
creation_date = datetime.now(tz=local_tz)
|
|
882
|
-
|
|
883
|
-
# Let's see if this is a template file; if so, re-route it as a copy in the customer's workflow directory.
|
|
884
|
-
if prior_workflow and prior_workflow.metadata.is_template:
|
|
885
|
-
# Aha! User is attempting to save a template. Create a differently-named file in their workspace.
|
|
886
|
-
# Find the first available file name that doesn't conflict.
|
|
887
|
-
curr_idx = 1
|
|
888
|
-
free_file_found = False
|
|
889
|
-
while not free_file_found:
|
|
890
|
-
# Composite a new candidate file name to test.
|
|
891
|
-
new_file_name = f"{file_name}_{curr_idx}"
|
|
892
|
-
new_file_name_with_extension = f"{new_file_name}.py"
|
|
893
|
-
new_file_full_path = GriptapeNodes.ConfigManager().workspace_path.joinpath(new_file_name_with_extension)
|
|
894
|
-
if new_file_full_path.exists():
|
|
895
|
-
# Keep going.
|
|
896
|
-
curr_idx += 1
|
|
897
|
-
else:
|
|
898
|
-
free_file_found = True
|
|
899
|
-
file_name = new_file_name
|
|
939
|
+
Args:
|
|
940
|
+
file_name: The name of the workflow file
|
|
941
|
+
creation_date: The creation date for the workflow
|
|
942
|
+
image_path: Optional; the path to an image to include in the workflow metadata
|
|
900
943
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
file_name = datetime.now(tz=local_tz).strftime("%d.%m_%H.%M")
|
|
904
|
-
relative_file_path = f"{file_name}.py"
|
|
905
|
-
file_path = GriptapeNodes.ConfigManager().workspace_path.joinpath(relative_file_path)
|
|
944
|
+
Returns:
|
|
945
|
+
A tuple of (workflow_file_contents, workflow_metadata)
|
|
906
946
|
|
|
947
|
+
Raises:
|
|
948
|
+
ValueError, TypeError: If workflow generation fails
|
|
949
|
+
"""
|
|
907
950
|
# Get the engine version.
|
|
908
951
|
engine_version_request = GetEngineVersionRequest()
|
|
909
952
|
engine_version_result = GriptapeNodes.handle_request(request=engine_version_request)
|
|
910
953
|
if not isinstance(engine_version_result, GetEngineVersionResultSuccess):
|
|
911
|
-
details = f"
|
|
954
|
+
details = f"Failed getting the engine version for workflow '{file_name}'."
|
|
912
955
|
logger.error(details)
|
|
913
|
-
|
|
956
|
+
raise TypeError(details)
|
|
914
957
|
try:
|
|
915
958
|
engine_version_success = cast("GetEngineVersionResultSuccess", engine_version_result)
|
|
916
959
|
engine_version = (
|
|
917
960
|
f"{engine_version_success.major}.{engine_version_success.minor}.{engine_version_success.patch}"
|
|
918
961
|
)
|
|
919
962
|
except Exception as err:
|
|
920
|
-
details = f"
|
|
963
|
+
details = f"Failed getting the engine version for workflow '{file_name}': {err}"
|
|
921
964
|
logger.error(details)
|
|
922
|
-
|
|
965
|
+
raise ValueError(details) from err
|
|
923
966
|
|
|
924
967
|
# Keep track of all of the nodes we create and the generated variable names for them.
|
|
925
968
|
node_uuid_to_node_variable_name: dict[SerializedNodeCommands.NodeUUID, str] = {}
|
|
@@ -931,44 +974,46 @@ class WorkflowManager:
|
|
|
931
974
|
top_level_flow_request = GetTopLevelFlowRequest()
|
|
932
975
|
top_level_flow_result = GriptapeNodes.handle_request(top_level_flow_request)
|
|
933
976
|
if not isinstance(top_level_flow_result, GetTopLevelFlowResultSuccess):
|
|
934
|
-
details =
|
|
935
|
-
f"Attempted to save workflow '{relative_file_path}'. Failed when requesting to get top level flow."
|
|
936
|
-
)
|
|
977
|
+
details = f"Failed when requesting to get top level flow for workflow '{file_name}'."
|
|
937
978
|
logger.error(details)
|
|
938
|
-
|
|
979
|
+
raise TypeError(details)
|
|
939
980
|
top_level_flow_name = top_level_flow_result.flow_name
|
|
940
981
|
serialized_flow_request = SerializeFlowToCommandsRequest(
|
|
941
982
|
flow_name=top_level_flow_name, include_create_flow_command=True
|
|
942
983
|
)
|
|
943
984
|
serialized_flow_result = GriptapeNodes.handle_request(serialized_flow_request)
|
|
944
985
|
if not isinstance(serialized_flow_result, SerializeFlowToCommandsResultSuccess):
|
|
945
|
-
details = f"
|
|
986
|
+
details = f"Failed when serializing flow for workflow '{file_name}'."
|
|
946
987
|
logger.error(details)
|
|
947
|
-
|
|
988
|
+
raise TypeError(details)
|
|
948
989
|
serialized_flow_commands = serialized_flow_result.serialized_flow_commands
|
|
949
990
|
|
|
950
991
|
# Create the Workflow Metadata header.
|
|
992
|
+
workflows_referenced = None
|
|
993
|
+
if serialized_flow_commands.referenced_workflows:
|
|
994
|
+
workflows_referenced = list(serialized_flow_commands.referenced_workflows)
|
|
995
|
+
|
|
951
996
|
workflow_metadata = self._generate_workflow_metadata(
|
|
952
997
|
file_name=file_name,
|
|
953
998
|
engine_version=engine_version,
|
|
954
999
|
creation_date=creation_date,
|
|
955
1000
|
node_libraries_referenced=list(serialized_flow_commands.node_libraries_used),
|
|
956
|
-
|
|
1001
|
+
workflows_referenced=workflows_referenced,
|
|
957
1002
|
)
|
|
958
1003
|
if workflow_metadata is None:
|
|
959
|
-
details = f"
|
|
1004
|
+
details = f"Failed to generate metadata for workflow '{file_name}'."
|
|
960
1005
|
logger.error(details)
|
|
961
|
-
|
|
1006
|
+
raise ValueError(details)
|
|
962
1007
|
|
|
963
1008
|
# Set the image if provided
|
|
964
|
-
if
|
|
965
|
-
workflow_metadata.image =
|
|
1009
|
+
if image_path:
|
|
1010
|
+
workflow_metadata.image = image_path
|
|
966
1011
|
|
|
967
1012
|
metadata_block = self._generate_workflow_metadata_header(workflow_metadata=workflow_metadata)
|
|
968
1013
|
if metadata_block is None:
|
|
969
|
-
details = f"
|
|
1014
|
+
details = f"Failed to generate metadata block for workflow '{file_name}'."
|
|
970
1015
|
logger.error(details)
|
|
971
|
-
|
|
1016
|
+
raise ValueError(details)
|
|
972
1017
|
|
|
973
1018
|
import_recorder = ImportRecorder()
|
|
974
1019
|
import_recorder.add_from_import("griptape_nodes.retained_mode.griptape_nodes", "GriptapeNodes")
|
|
@@ -989,26 +1034,42 @@ class WorkflowManager:
|
|
|
989
1034
|
)
|
|
990
1035
|
ast_container.add_node(unique_values_node)
|
|
991
1036
|
|
|
992
|
-
# See if this serialized flow has a
|
|
993
|
-
|
|
1037
|
+
# See if this serialized flow has a flow initialization command; if it does, we'll need to insert that.
|
|
1038
|
+
flow_initialization_command = serialized_flow_commands.flow_initialization_command
|
|
994
1039
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1040
|
+
match flow_initialization_command:
|
|
1041
|
+
case CreateFlowRequest():
|
|
1042
|
+
# Generate create flow context AST module
|
|
1043
|
+
create_flow_context_module = self._generate_create_flow(
|
|
1044
|
+
flow_initialization_command, import_recorder, flow_creation_index
|
|
1045
|
+
)
|
|
1046
|
+
for node in create_flow_context_module.body:
|
|
1047
|
+
ast_container.add_node(node)
|
|
1048
|
+
case ImportWorkflowAsReferencedSubFlowRequest():
|
|
1049
|
+
# Generate import workflow context AST module
|
|
1050
|
+
import_workflow_context_module = self._generate_import_workflow(
|
|
1051
|
+
flow_initialization_command, import_recorder, flow_creation_index
|
|
1052
|
+
)
|
|
1053
|
+
for node in import_workflow_context_module.body:
|
|
1054
|
+
ast_container.add_node(node)
|
|
1055
|
+
case None:
|
|
1056
|
+
# No initialization command, deserialize into current context
|
|
1057
|
+
pass
|
|
1001
1058
|
|
|
1002
1059
|
# Generate assign flow context AST node, if we have any children commands.
|
|
1003
|
-
|
|
1060
|
+
# Skip content generation for referenced workflows - they should only have the import command
|
|
1061
|
+
is_referenced_workflow = isinstance(flow_initialization_command, ImportWorkflowAsReferencedSubFlowRequest)
|
|
1062
|
+
has_content_to_serialize = (
|
|
1004
1063
|
len(serialized_flow_commands.serialized_node_commands) > 0
|
|
1005
1064
|
or len(serialized_flow_commands.serialized_connections) > 0
|
|
1006
1065
|
or len(serialized_flow_commands.set_parameter_value_commands) > 0
|
|
1007
1066
|
or len(serialized_flow_commands.sub_flows_commands) > 0
|
|
1008
|
-
)
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
if not is_referenced_workflow and has_content_to_serialize:
|
|
1009
1070
|
# Create the "with..." statement
|
|
1010
1071
|
assign_flow_context_node = self._generate_assign_flow_context(
|
|
1011
|
-
|
|
1072
|
+
flow_initialization_command=flow_initialization_command, flow_creation_index=flow_creation_index
|
|
1012
1073
|
)
|
|
1013
1074
|
|
|
1014
1075
|
# Generate nodes in flow AST node. This will create the node and apply all element modifiers.
|
|
@@ -1018,24 +1079,44 @@ class WorkflowManager:
|
|
|
1018
1079
|
|
|
1019
1080
|
# Add the nodes to the body of the Current Context flow's "with" statement
|
|
1020
1081
|
assign_flow_context_node.body.extend(nodes_in_flow)
|
|
1021
|
-
ast_container.add_node(assign_flow_context_node)
|
|
1022
1082
|
|
|
1023
|
-
#
|
|
1083
|
+
# Process sub-flows - for each sub-flow, generate its initialization command
|
|
1084
|
+
for sub_flow_index, sub_flow_commands in enumerate(serialized_flow_commands.sub_flows_commands):
|
|
1085
|
+
sub_flow_creation_index = flow_creation_index + 1 + sub_flow_index
|
|
1086
|
+
|
|
1087
|
+
# Generate initialization command for the sub-flow
|
|
1088
|
+
sub_flow_initialization_command = sub_flow_commands.flow_initialization_command
|
|
1089
|
+
if sub_flow_initialization_command is not None:
|
|
1090
|
+
match sub_flow_initialization_command:
|
|
1091
|
+
case CreateFlowRequest():
|
|
1092
|
+
sub_flow_create_node = self._generate_create_flow(
|
|
1093
|
+
sub_flow_initialization_command, import_recorder, sub_flow_creation_index
|
|
1094
|
+
)
|
|
1095
|
+
assign_flow_context_node.body.append(cast("ast.stmt", sub_flow_create_node))
|
|
1096
|
+
case ImportWorkflowAsReferencedSubFlowRequest():
|
|
1097
|
+
sub_flow_import_node = self._generate_import_workflow(
|
|
1098
|
+
sub_flow_initialization_command, import_recorder, sub_flow_creation_index
|
|
1099
|
+
)
|
|
1100
|
+
assign_flow_context_node.body.append(cast("ast.stmt", sub_flow_import_node))
|
|
1101
|
+
|
|
1102
|
+
# Now generate the connection code and add it to the flow context.
|
|
1024
1103
|
connection_asts = self._generate_connections_code(
|
|
1025
1104
|
serialized_connections=serialized_flow_commands.serialized_connections,
|
|
1026
1105
|
node_uuid_to_node_variable_name=node_uuid_to_node_variable_name,
|
|
1027
1106
|
import_recorder=import_recorder,
|
|
1028
1107
|
)
|
|
1029
|
-
|
|
1108
|
+
assign_flow_context_node.body.extend(connection_asts)
|
|
1030
1109
|
|
|
1031
|
-
# Now generate all the set parameter value code.
|
|
1110
|
+
# Now generate all the set parameter value code and add it to the flow context.
|
|
1032
1111
|
set_parameter_value_asts = self._generate_set_parameter_value_code(
|
|
1033
1112
|
set_parameter_value_commands=serialized_flow_commands.set_parameter_value_commands,
|
|
1034
1113
|
node_uuid_to_node_variable_name=node_uuid_to_node_variable_name,
|
|
1035
1114
|
unique_values_dict_name="top_level_unique_values_dict",
|
|
1036
1115
|
import_recorder=import_recorder,
|
|
1037
1116
|
)
|
|
1038
|
-
|
|
1117
|
+
assign_flow_context_node.body.extend(set_parameter_value_asts)
|
|
1118
|
+
|
|
1119
|
+
ast_container.add_node(assign_flow_context_node)
|
|
1039
1120
|
|
|
1040
1121
|
workflow_execution_code = (
|
|
1041
1122
|
self._generate_workflow_execution(
|
|
@@ -1056,18 +1137,96 @@ class WorkflowManager:
|
|
|
1056
1137
|
import_output = import_recorder.generate_imports()
|
|
1057
1138
|
final_code_output = f"{metadata_block}\n\n{import_output}\n\n{ast_output}\n"
|
|
1058
1139
|
|
|
1140
|
+
return final_code_output, workflow_metadata
|
|
1141
|
+
|
|
1142
|
+
def on_save_workflow_request(self, request: SaveWorkflowRequest) -> ResultPayload: # noqa: C901, PLR0912, PLR0915
|
|
1143
|
+
local_tz = datetime.now().astimezone().tzinfo
|
|
1144
|
+
|
|
1145
|
+
# Start with the file name provided; we may change it.
|
|
1146
|
+
file_name = request.file_name
|
|
1147
|
+
|
|
1148
|
+
# See if we had an existing workflow for this.
|
|
1149
|
+
prior_workflow = None
|
|
1150
|
+
creation_date = None
|
|
1151
|
+
if file_name and WorkflowRegistry.has_workflow_with_name(file_name):
|
|
1152
|
+
# Get the metadata.
|
|
1153
|
+
prior_workflow = WorkflowRegistry.get_workflow_by_name(file_name)
|
|
1154
|
+
# We'll use its creation date.
|
|
1155
|
+
creation_date = prior_workflow.metadata.creation_date
|
|
1156
|
+
|
|
1157
|
+
if (creation_date is None) or (creation_date == WorkflowManager.EPOCH_START):
|
|
1158
|
+
# Either a new workflow, or a backcompat situation.
|
|
1159
|
+
creation_date = datetime.now(tz=local_tz)
|
|
1160
|
+
|
|
1161
|
+
# Let's see if this is a template file; if so, re-route it as a copy in the customer's workflow directory.
|
|
1162
|
+
if prior_workflow and prior_workflow.metadata.is_template:
|
|
1163
|
+
# Aha! User is attempting to save a template. Create a differently-named file in their workspace.
|
|
1164
|
+
# Find the first available file name that doesn't conflict.
|
|
1165
|
+
curr_idx = 1
|
|
1166
|
+
free_file_found = False
|
|
1167
|
+
while not free_file_found:
|
|
1168
|
+
# Composite a new candidate file name to test.
|
|
1169
|
+
new_file_name = f"{file_name}_{curr_idx}"
|
|
1170
|
+
new_file_name_with_extension = f"{new_file_name}.py"
|
|
1171
|
+
new_file_full_path = GriptapeNodes.ConfigManager().workspace_path.joinpath(new_file_name_with_extension)
|
|
1172
|
+
if new_file_full_path.exists():
|
|
1173
|
+
# Keep going.
|
|
1174
|
+
curr_idx += 1
|
|
1175
|
+
else:
|
|
1176
|
+
free_file_found = True
|
|
1177
|
+
file_name = new_file_name
|
|
1178
|
+
|
|
1179
|
+
# Get file name stuff prepped.
|
|
1180
|
+
if not file_name:
|
|
1181
|
+
file_name = datetime.now(tz=local_tz).strftime("%d.%m_%H.%M")
|
|
1182
|
+
relative_file_path = f"{file_name}.py"
|
|
1183
|
+
file_path = GriptapeNodes.ConfigManager().workspace_path.joinpath(relative_file_path)
|
|
1184
|
+
|
|
1185
|
+
# Generate the workflow file contents
|
|
1186
|
+
try:
|
|
1187
|
+
final_code_output, workflow_metadata = self._generate_workflow_file_contents_and_metadata(
|
|
1188
|
+
file_name=file_name, creation_date=creation_date, image_path=request.image_path
|
|
1189
|
+
)
|
|
1190
|
+
except Exception as err:
|
|
1191
|
+
details = f"Attempted to save workflow '{relative_file_path}', but {err}"
|
|
1192
|
+
logger.error(details)
|
|
1193
|
+
return SaveWorkflowResultFailure()
|
|
1194
|
+
|
|
1059
1195
|
# Create the pathing and write the file
|
|
1060
|
-
|
|
1196
|
+
try:
|
|
1197
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1198
|
+
except OSError as e:
|
|
1199
|
+
logger.error("Attempted to save workflow '%s'. Failed when creating directory: %s", file_name, str(e))
|
|
1200
|
+
return SaveWorkflowResultFailure()
|
|
1061
1201
|
|
|
1062
1202
|
relative_serialized_file_path = f"{file_name}.py"
|
|
1063
1203
|
serialized_file_path = GriptapeNodes.ConfigManager().workspace_path.joinpath(relative_serialized_file_path)
|
|
1064
|
-
|
|
1065
|
-
|
|
1204
|
+
|
|
1205
|
+
# Check disk space before writing
|
|
1206
|
+
config_manager = GriptapeNodes.ConfigManager()
|
|
1207
|
+
min_space_gb = config_manager.get_config_value("minimum_disk_space_gb_workflows")
|
|
1208
|
+
if not OSManager.check_available_disk_space(serialized_file_path.parent, min_space_gb):
|
|
1209
|
+
error_msg = OSManager.format_disk_space_error(serialized_file_path.parent)
|
|
1210
|
+
logger.error(
|
|
1211
|
+
"Attempted to save workflow '%s' (requires %.1f GB). Failed: %s", file_name, min_space_gb, error_msg
|
|
1212
|
+
)
|
|
1213
|
+
return SaveWorkflowResultFailure()
|
|
1214
|
+
|
|
1215
|
+
try:
|
|
1216
|
+
with serialized_file_path.open("w", encoding="utf-8") as file:
|
|
1217
|
+
file.write(final_code_output)
|
|
1218
|
+
except OSError as e:
|
|
1219
|
+
logger.error("Attempted to save workflow '%s'. Failed when writing file: %s", file_name, str(e))
|
|
1220
|
+
return SaveWorkflowResultFailure()
|
|
1066
1221
|
|
|
1067
1222
|
# save the created workflow as an entry in the JSON config file.
|
|
1068
1223
|
registered_workflows = WorkflowRegistry.list_workflows()
|
|
1069
1224
|
if file_name not in registered_workflows:
|
|
1070
|
-
|
|
1225
|
+
try:
|
|
1226
|
+
GriptapeNodes.ConfigManager().save_user_workflow_json(str(file_path))
|
|
1227
|
+
except OSError as e:
|
|
1228
|
+
logger.error("Attempted to save workflow '%s'. Failed when saving configuration: %s", file_name, str(e))
|
|
1229
|
+
return SaveWorkflowResultFailure()
|
|
1071
1230
|
WorkflowRegistry.generate_new_workflow(metadata=workflow_metadata, file_path=relative_file_path)
|
|
1072
1231
|
details = f"Successfully saved workflow to: {serialized_file_path}"
|
|
1073
1232
|
logger.info(details)
|
|
@@ -1079,7 +1238,7 @@ class WorkflowManager:
|
|
|
1079
1238
|
engine_version: str,
|
|
1080
1239
|
creation_date: datetime,
|
|
1081
1240
|
node_libraries_referenced: list[LibraryNameAndVersion],
|
|
1082
|
-
|
|
1241
|
+
workflows_referenced: list[str] | None = None,
|
|
1083
1242
|
) -> WorkflowMetadata | None:
|
|
1084
1243
|
local_tz = datetime.now().astimezone().tzinfo
|
|
1085
1244
|
workflow_metadata = WorkflowMetadata(
|
|
@@ -1087,9 +1246,9 @@ class WorkflowManager:
|
|
|
1087
1246
|
schema_version=WorkflowMetadata.LATEST_SCHEMA_VERSION,
|
|
1088
1247
|
engine_version_created_with=engine_version,
|
|
1089
1248
|
node_libraries_referenced=node_libraries_referenced,
|
|
1249
|
+
workflows_referenced=workflows_referenced,
|
|
1090
1250
|
creation_date=creation_date,
|
|
1091
1251
|
last_modified_date=datetime.now(tz=local_tz),
|
|
1092
|
-
published_workflow_id=published_workflow_id,
|
|
1093
1252
|
)
|
|
1094
1253
|
|
|
1095
1254
|
return workflow_metadata
|
|
@@ -1131,30 +1290,40 @@ class WorkflowManager:
|
|
|
1131
1290
|
) -> list[ast.AST] | None:
|
|
1132
1291
|
"""Generates execute_workflow(...) and the __main__ guard."""
|
|
1133
1292
|
try:
|
|
1134
|
-
workflow_shape = self.
|
|
1293
|
+
workflow_shape = self.extract_workflow_shape(flow_name)
|
|
1135
1294
|
except ValueError:
|
|
1136
1295
|
logger.info("Workflow shape does not have required Start or End Nodes. Skipping local execution block.")
|
|
1137
1296
|
return None
|
|
1138
1297
|
|
|
1139
1298
|
# === imports ===
|
|
1140
1299
|
import_recorder.add_import("argparse")
|
|
1141
|
-
import_recorder.add_import("json")
|
|
1142
1300
|
import_recorder.add_from_import(
|
|
1143
1301
|
"griptape_nodes.bootstrap.workflow_executors.local_workflow_executor", "LocalWorkflowExecutor"
|
|
1144
1302
|
)
|
|
1303
|
+
import_recorder.add_from_import(
|
|
1304
|
+
"griptape_nodes.bootstrap.workflow_executors.workflow_executor", "WorkflowExecutor"
|
|
1305
|
+
)
|
|
1145
1306
|
|
|
1146
|
-
# === 1) build the `def execute_workflow(input: dict, storage_backend: str = StorageBackend.LOCAL) -> dict | None:` ===
|
|
1307
|
+
# === 1) build the `def execute_workflow(input: dict, storage_backend: str = StorageBackend.LOCAL, workflow_executor: WorkflowExecutor | None = None) -> dict | None:` ===
|
|
1147
1308
|
# args
|
|
1148
1309
|
arg_input = ast.arg(arg="input", annotation=ast.Name(id="dict", ctx=ast.Load()))
|
|
1149
1310
|
arg_storage_backend = ast.arg(arg="storage_backend", annotation=ast.Name(id="str", ctx=ast.Load()))
|
|
1311
|
+
arg_workflow_executor = ast.arg(
|
|
1312
|
+
arg="workflow_executor",
|
|
1313
|
+
annotation=ast.BinOp(
|
|
1314
|
+
left=ast.Name(id="WorkflowExecutor", ctx=ast.Load()),
|
|
1315
|
+
op=ast.BitOr(),
|
|
1316
|
+
right=ast.Constant(value=None),
|
|
1317
|
+
),
|
|
1318
|
+
)
|
|
1150
1319
|
args = ast.arguments(
|
|
1151
1320
|
posonlyargs=[],
|
|
1152
|
-
args=[arg_input, arg_storage_backend],
|
|
1321
|
+
args=[arg_input, arg_storage_backend, arg_workflow_executor],
|
|
1153
1322
|
vararg=None,
|
|
1154
1323
|
kwonlyargs=[],
|
|
1155
1324
|
kw_defaults=[],
|
|
1156
1325
|
kwarg=None,
|
|
1157
|
-
defaults=[ast.Constant(StorageBackend.LOCAL.value)],
|
|
1326
|
+
defaults=[ast.Constant(StorageBackend.LOCAL.value), ast.Constant(value=None)],
|
|
1158
1327
|
)
|
|
1159
1328
|
# return annotation: dict | None
|
|
1160
1329
|
return_annotation = ast.BinOp(
|
|
@@ -1163,12 +1332,22 @@ class WorkflowManager:
|
|
|
1163
1332
|
right=ast.Constant(value=None),
|
|
1164
1333
|
)
|
|
1165
1334
|
|
|
1335
|
+
# Generate the ensure flow context function call
|
|
1336
|
+
ensure_context_call = self._generate_ensure_flow_context_call()
|
|
1337
|
+
|
|
1338
|
+
# Create conditional logic: workflow_executor = workflow_executor or LocalWorkflowExecutor()
|
|
1166
1339
|
executor_assign = ast.Assign(
|
|
1167
1340
|
targets=[ast.Name(id="workflow_executor", ctx=ast.Store())],
|
|
1168
|
-
value=ast.
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1341
|
+
value=ast.BoolOp(
|
|
1342
|
+
op=ast.Or(),
|
|
1343
|
+
values=[
|
|
1344
|
+
ast.Name(id="workflow_executor", ctx=ast.Load()),
|
|
1345
|
+
ast.Call(
|
|
1346
|
+
func=ast.Name(id="LocalWorkflowExecutor", ctx=ast.Load()),
|
|
1347
|
+
args=[],
|
|
1348
|
+
keywords=[],
|
|
1349
|
+
),
|
|
1350
|
+
],
|
|
1172
1351
|
),
|
|
1173
1352
|
)
|
|
1174
1353
|
run_call = ast.Expr(
|
|
@@ -1197,7 +1376,7 @@ class WorkflowManager:
|
|
|
1197
1376
|
func_def = ast.FunctionDef(
|
|
1198
1377
|
name="execute_workflow",
|
|
1199
1378
|
args=args,
|
|
1200
|
-
body=[executor_assign, run_call, return_stmt],
|
|
1379
|
+
body=[ensure_context_call, executor_assign, run_call, return_stmt],
|
|
1201
1380
|
decorator_list=[],
|
|
1202
1381
|
returns=return_annotation,
|
|
1203
1382
|
type_params=[],
|
|
@@ -1416,7 +1595,201 @@ class WorkflowManager:
|
|
|
1416
1595
|
)
|
|
1417
1596
|
ast.fix_missing_locations(if_node)
|
|
1418
1597
|
|
|
1419
|
-
|
|
1598
|
+
# Generate the ensure flow context function
|
|
1599
|
+
ensure_context_func = self._generate_ensure_flow_context_function(import_recorder)
|
|
1600
|
+
|
|
1601
|
+
return [ensure_context_func, func_def, if_node]
|
|
1602
|
+
|
|
1603
|
+
def _generate_ensure_flow_context_function(
|
|
1604
|
+
self,
|
|
1605
|
+
import_recorder: ImportRecorder,
|
|
1606
|
+
) -> ast.FunctionDef:
|
|
1607
|
+
"""Generates the _ensure_workflow_context function for the serialized workflow file."""
|
|
1608
|
+
import_recorder.add_from_import("griptape_nodes.retained_mode.events.flow_events", "GetTopLevelFlowRequest")
|
|
1609
|
+
import_recorder.add_from_import(
|
|
1610
|
+
"griptape_nodes.retained_mode.events.flow_events", "GetTopLevelFlowResultSuccess"
|
|
1611
|
+
)
|
|
1612
|
+
|
|
1613
|
+
# Function signature: def _ensure_workflow_context():
|
|
1614
|
+
func_def = ast.FunctionDef(
|
|
1615
|
+
name="_ensure_workflow_context",
|
|
1616
|
+
args=ast.arguments(
|
|
1617
|
+
posonlyargs=[],
|
|
1618
|
+
args=[],
|
|
1619
|
+
vararg=None,
|
|
1620
|
+
kwonlyargs=[],
|
|
1621
|
+
kw_defaults=[],
|
|
1622
|
+
kwarg=None,
|
|
1623
|
+
defaults=[],
|
|
1624
|
+
),
|
|
1625
|
+
body=[],
|
|
1626
|
+
decorator_list=[],
|
|
1627
|
+
returns=None,
|
|
1628
|
+
type_params=[],
|
|
1629
|
+
)
|
|
1630
|
+
|
|
1631
|
+
context_manager_assign = ast.Assign(
|
|
1632
|
+
targets=[ast.Name(id="context_manager", ctx=ast.Store())],
|
|
1633
|
+
value=ast.Call(
|
|
1634
|
+
func=ast.Attribute(
|
|
1635
|
+
value=ast.Name(id="GriptapeNodes", ctx=ast.Load()),
|
|
1636
|
+
attr="ContextManager",
|
|
1637
|
+
ctx=ast.Load(),
|
|
1638
|
+
),
|
|
1639
|
+
args=[],
|
|
1640
|
+
keywords=[],
|
|
1641
|
+
),
|
|
1642
|
+
)
|
|
1643
|
+
|
|
1644
|
+
# if not context_manager.has_current_flow():
|
|
1645
|
+
has_flow_check = ast.UnaryOp(
|
|
1646
|
+
op=ast.Not(),
|
|
1647
|
+
operand=ast.Call(
|
|
1648
|
+
func=ast.Attribute(
|
|
1649
|
+
value=ast.Name(id="context_manager", ctx=ast.Load()),
|
|
1650
|
+
attr="has_current_flow",
|
|
1651
|
+
ctx=ast.Load(),
|
|
1652
|
+
),
|
|
1653
|
+
args=[],
|
|
1654
|
+
keywords=[],
|
|
1655
|
+
),
|
|
1656
|
+
)
|
|
1657
|
+
|
|
1658
|
+
# top_level_flow_request = GetTopLevelFlowRequest() # noqa: ERA001
|
|
1659
|
+
flow_request_assign = ast.Assign(
|
|
1660
|
+
targets=[ast.Name(id="top_level_flow_request", ctx=ast.Store())],
|
|
1661
|
+
value=ast.Call(
|
|
1662
|
+
func=ast.Name(id="GetTopLevelFlowRequest", ctx=ast.Load()),
|
|
1663
|
+
args=[],
|
|
1664
|
+
keywords=[],
|
|
1665
|
+
),
|
|
1666
|
+
)
|
|
1667
|
+
|
|
1668
|
+
# top_level_flow_result = GriptapeNodes.handle_request(top_level_flow_request) # noqa: ERA001
|
|
1669
|
+
flow_result_assign = ast.Assign(
|
|
1670
|
+
targets=[ast.Name(id="top_level_flow_result", ctx=ast.Store())],
|
|
1671
|
+
value=ast.Call(
|
|
1672
|
+
func=ast.Attribute(
|
|
1673
|
+
value=ast.Name(id="GriptapeNodes", ctx=ast.Load()),
|
|
1674
|
+
attr="handle_request",
|
|
1675
|
+
ctx=ast.Load(),
|
|
1676
|
+
),
|
|
1677
|
+
args=[ast.Name(id="top_level_flow_request", ctx=ast.Load())],
|
|
1678
|
+
keywords=[],
|
|
1679
|
+
),
|
|
1680
|
+
)
|
|
1681
|
+
|
|
1682
|
+
# isinstance check and flow_name is not None
|
|
1683
|
+
isinstance_check = ast.Call(
|
|
1684
|
+
func=ast.Name(id="isinstance", ctx=ast.Load()),
|
|
1685
|
+
args=[
|
|
1686
|
+
ast.Name(id="top_level_flow_result", ctx=ast.Load()),
|
|
1687
|
+
ast.Name(id="GetTopLevelFlowResultSuccess", ctx=ast.Load()),
|
|
1688
|
+
],
|
|
1689
|
+
keywords=[],
|
|
1690
|
+
)
|
|
1691
|
+
|
|
1692
|
+
flow_name_check = ast.Compare(
|
|
1693
|
+
left=ast.Attribute(
|
|
1694
|
+
value=ast.Name(id="top_level_flow_result", ctx=ast.Load()),
|
|
1695
|
+
attr="flow_name",
|
|
1696
|
+
ctx=ast.Load(),
|
|
1697
|
+
),
|
|
1698
|
+
ops=[ast.IsNot()],
|
|
1699
|
+
comparators=[ast.Constant(value=None)],
|
|
1700
|
+
)
|
|
1701
|
+
|
|
1702
|
+
success_condition = ast.BoolOp(
|
|
1703
|
+
op=ast.And(),
|
|
1704
|
+
values=[isinstance_check, flow_name_check],
|
|
1705
|
+
)
|
|
1706
|
+
|
|
1707
|
+
# flow_manager = GriptapeNodes.FlowManager() # noqa: ERA001
|
|
1708
|
+
flow_manager_assign = ast.Assign(
|
|
1709
|
+
targets=[ast.Name(id="flow_manager", ctx=ast.Store())],
|
|
1710
|
+
value=ast.Call(
|
|
1711
|
+
func=ast.Attribute(
|
|
1712
|
+
value=ast.Name(id="GriptapeNodes", ctx=ast.Load()),
|
|
1713
|
+
attr="FlowManager",
|
|
1714
|
+
ctx=ast.Load(),
|
|
1715
|
+
),
|
|
1716
|
+
args=[],
|
|
1717
|
+
keywords=[],
|
|
1718
|
+
),
|
|
1719
|
+
)
|
|
1720
|
+
|
|
1721
|
+
# flow_obj = flow_manager.get_flow_by_name(top_level_flow_result.flow_name) # noqa: ERA001
|
|
1722
|
+
flow_obj_assign = ast.Assign(
|
|
1723
|
+
targets=[ast.Name(id="flow_obj", ctx=ast.Store())],
|
|
1724
|
+
value=ast.Call(
|
|
1725
|
+
func=ast.Attribute(
|
|
1726
|
+
value=ast.Name(id="flow_manager", ctx=ast.Load()),
|
|
1727
|
+
attr="get_flow_by_name",
|
|
1728
|
+
ctx=ast.Load(),
|
|
1729
|
+
),
|
|
1730
|
+
args=[
|
|
1731
|
+
ast.Attribute(
|
|
1732
|
+
value=ast.Name(id="top_level_flow_result", ctx=ast.Load()),
|
|
1733
|
+
attr="flow_name",
|
|
1734
|
+
ctx=ast.Load(),
|
|
1735
|
+
)
|
|
1736
|
+
],
|
|
1737
|
+
keywords=[],
|
|
1738
|
+
),
|
|
1739
|
+
)
|
|
1740
|
+
|
|
1741
|
+
# context_manager.push_flow(flow_obj) # noqa: ERA001
|
|
1742
|
+
push_flow_call = ast.Expr(
|
|
1743
|
+
value=ast.Call(
|
|
1744
|
+
func=ast.Attribute(
|
|
1745
|
+
value=ast.Name(id="context_manager", ctx=ast.Load()),
|
|
1746
|
+
attr="push_flow",
|
|
1747
|
+
ctx=ast.Load(),
|
|
1748
|
+
),
|
|
1749
|
+
args=[ast.Name(id="flow_obj", ctx=ast.Load())],
|
|
1750
|
+
keywords=[],
|
|
1751
|
+
),
|
|
1752
|
+
)
|
|
1753
|
+
|
|
1754
|
+
# Build the inner if statement for success condition
|
|
1755
|
+
success_if = ast.If(
|
|
1756
|
+
test=success_condition,
|
|
1757
|
+
body=[
|
|
1758
|
+
flow_manager_assign,
|
|
1759
|
+
flow_obj_assign,
|
|
1760
|
+
push_flow_call,
|
|
1761
|
+
],
|
|
1762
|
+
orelse=[],
|
|
1763
|
+
)
|
|
1764
|
+
|
|
1765
|
+
# Build the main if statement
|
|
1766
|
+
main_if = ast.If(
|
|
1767
|
+
test=has_flow_check,
|
|
1768
|
+
body=[
|
|
1769
|
+
flow_request_assign,
|
|
1770
|
+
flow_result_assign,
|
|
1771
|
+
success_if,
|
|
1772
|
+
],
|
|
1773
|
+
orelse=[],
|
|
1774
|
+
)
|
|
1775
|
+
|
|
1776
|
+
# Set the function body
|
|
1777
|
+
func_def.body = [context_manager_assign, main_if]
|
|
1778
|
+
ast.fix_missing_locations(func_def)
|
|
1779
|
+
|
|
1780
|
+
return func_def
|
|
1781
|
+
|
|
1782
|
+
def _generate_ensure_flow_context_call(
|
|
1783
|
+
self,
|
|
1784
|
+
) -> ast.Expr:
|
|
1785
|
+
"""Generates the call to _ensure_workflow_context() function."""
|
|
1786
|
+
return ast.Expr(
|
|
1787
|
+
value=ast.Call(
|
|
1788
|
+
func=ast.Name(id="_ensure_workflow_context", ctx=ast.Load()),
|
|
1789
|
+
args=[],
|
|
1790
|
+
keywords=[],
|
|
1791
|
+
)
|
|
1792
|
+
)
|
|
1420
1793
|
|
|
1421
1794
|
def _generate_workflow_run_prerequisite_code(
|
|
1422
1795
|
self,
|
|
@@ -1679,7 +2052,7 @@ class WorkflowManager:
|
|
|
1679
2052
|
|
|
1680
2053
|
def _generate_create_flow(
|
|
1681
2054
|
self, create_flow_command: CreateFlowRequest, import_recorder: ImportRecorder, flow_creation_index: int
|
|
1682
|
-
) -> ast.
|
|
2055
|
+
) -> ast.Module:
|
|
1683
2056
|
import_recorder.add_from_import("griptape_nodes.retained_mode.events.flow_events", "CreateFlowRequest")
|
|
1684
2057
|
|
|
1685
2058
|
# Prepare arguments for CreateFlowRequest
|
|
@@ -1694,6 +2067,17 @@ class WorkflowManager:
|
|
|
1694
2067
|
ast.keyword(arg=field.name, value=ast.Constant(value=field_value, lineno=1, col_offset=0))
|
|
1695
2068
|
)
|
|
1696
2069
|
|
|
2070
|
+
# Create a comment explaining the behavior
|
|
2071
|
+
comment_ast = ast.Expr(
|
|
2072
|
+
value=ast.Constant(
|
|
2073
|
+
value="# Create the Flow, then do work within it as context.",
|
|
2074
|
+
lineno=1,
|
|
2075
|
+
col_offset=0,
|
|
2076
|
+
),
|
|
2077
|
+
lineno=1,
|
|
2078
|
+
col_offset=0,
|
|
2079
|
+
)
|
|
2080
|
+
|
|
1697
2081
|
# Construct the AST for creating the flow
|
|
1698
2082
|
flow_variable_name = f"flow{flow_creation_index}_name"
|
|
1699
2083
|
create_flow_result = ast.Assign(
|
|
@@ -1729,10 +2113,92 @@ class WorkflowManager:
|
|
|
1729
2113
|
col_offset=0,
|
|
1730
2114
|
)
|
|
1731
2115
|
|
|
1732
|
-
|
|
2116
|
+
# Return both the comment and the assignment as a module
|
|
2117
|
+
return ast.Module(body=[comment_ast, create_flow_result], type_ignores=[])
|
|
2118
|
+
|
|
2119
|
+
def _generate_import_workflow(
|
|
2120
|
+
self,
|
|
2121
|
+
import_workflow_command: ImportWorkflowAsReferencedSubFlowRequest,
|
|
2122
|
+
import_recorder: ImportRecorder,
|
|
2123
|
+
flow_creation_index: int,
|
|
2124
|
+
) -> ast.Module:
|
|
2125
|
+
"""Generate AST code for importing a referenced workflow.
|
|
2126
|
+
|
|
2127
|
+
Creates an assignment statement that executes an ImportWorkflowAsReferencedSubFlowRequest
|
|
2128
|
+
and stores the resulting flow name in a variable.
|
|
2129
|
+
|
|
2130
|
+
Args:
|
|
2131
|
+
import_workflow_command: The import request containing the workflow file path
|
|
2132
|
+
import_recorder: Tracks imports needed for the generated code
|
|
2133
|
+
flow_creation_index: Index used to generate unique variable names
|
|
2134
|
+
|
|
2135
|
+
Returns:
|
|
2136
|
+
AST assignment node representing the import workflow command
|
|
2137
|
+
|
|
2138
|
+
Example output:
|
|
2139
|
+
flow1_name = GriptapeNodes.handle_request(ImportWorkflowAsReferencedSubFlowRequest(
|
|
2140
|
+
file_path='/path/to/workflow.py'
|
|
2141
|
+
)).created_flow_name
|
|
2142
|
+
"""
|
|
2143
|
+
import_recorder.add_from_import(
|
|
2144
|
+
"griptape_nodes.retained_mode.events.workflow_events", "ImportWorkflowAsReferencedSubFlowRequest"
|
|
2145
|
+
)
|
|
2146
|
+
|
|
2147
|
+
# Prepare arguments for ImportWorkflowAsReferencedSubFlowRequest
|
|
2148
|
+
import_workflow_request_args = []
|
|
2149
|
+
|
|
2150
|
+
# Omit values that match default values.
|
|
2151
|
+
if is_dataclass(import_workflow_command):
|
|
2152
|
+
for field in fields(import_workflow_command):
|
|
2153
|
+
field_value = getattr(import_workflow_command, field.name)
|
|
2154
|
+
if field_value != field.default:
|
|
2155
|
+
import_workflow_request_args.append(
|
|
2156
|
+
ast.keyword(arg=field.name, value=ast.Constant(value=field_value, lineno=1, col_offset=0))
|
|
2157
|
+
)
|
|
2158
|
+
|
|
2159
|
+
# Construct the AST for importing the workflow
|
|
2160
|
+
flow_variable_name = f"flow{flow_creation_index}_name"
|
|
2161
|
+
import_workflow_result = ast.Assign(
|
|
2162
|
+
targets=[ast.Name(id=flow_variable_name, ctx=ast.Store(), lineno=1, col_offset=0)],
|
|
2163
|
+
value=ast.Attribute(
|
|
2164
|
+
value=ast.Call(
|
|
2165
|
+
func=ast.Attribute(
|
|
2166
|
+
value=ast.Name(id="GriptapeNodes", ctx=ast.Load(), lineno=1, col_offset=0),
|
|
2167
|
+
attr="handle_request",
|
|
2168
|
+
ctx=ast.Load(),
|
|
2169
|
+
lineno=1,
|
|
2170
|
+
col_offset=0,
|
|
2171
|
+
),
|
|
2172
|
+
args=[
|
|
2173
|
+
ast.Call(
|
|
2174
|
+
func=ast.Name(
|
|
2175
|
+
id="ImportWorkflowAsReferencedSubFlowRequest", ctx=ast.Load(), lineno=1, col_offset=0
|
|
2176
|
+
),
|
|
2177
|
+
args=[],
|
|
2178
|
+
keywords=import_workflow_request_args,
|
|
2179
|
+
lineno=1,
|
|
2180
|
+
col_offset=0,
|
|
2181
|
+
)
|
|
2182
|
+
],
|
|
2183
|
+
keywords=[],
|
|
2184
|
+
lineno=1,
|
|
2185
|
+
col_offset=0,
|
|
2186
|
+
),
|
|
2187
|
+
attr="created_flow_name",
|
|
2188
|
+
ctx=ast.Load(),
|
|
2189
|
+
lineno=1,
|
|
2190
|
+
col_offset=0,
|
|
2191
|
+
),
|
|
2192
|
+
lineno=1,
|
|
2193
|
+
col_offset=0,
|
|
2194
|
+
)
|
|
2195
|
+
|
|
2196
|
+
return ast.Module(body=[import_workflow_result], type_ignores=[])
|
|
1733
2197
|
|
|
1734
2198
|
def _generate_assign_flow_context(
|
|
1735
|
-
self,
|
|
2199
|
+
self,
|
|
2200
|
+
flow_initialization_command: CreateFlowRequest | ImportWorkflowAsReferencedSubFlowRequest | None,
|
|
2201
|
+
flow_creation_index: int,
|
|
1736
2202
|
) -> ast.With:
|
|
1737
2203
|
context_manager = ast.Attribute(
|
|
1738
2204
|
value=ast.Name(id="GriptapeNodes", ctx=ast.Load(), lineno=1, col_offset=0),
|
|
@@ -1742,7 +2208,7 @@ class WorkflowManager:
|
|
|
1742
2208
|
col_offset=0,
|
|
1743
2209
|
)
|
|
1744
2210
|
|
|
1745
|
-
if
|
|
2211
|
+
if flow_initialization_command is None:
|
|
1746
2212
|
# Construct AST for "GriptapeNodes.ContextManager().flow(GriptapeNodes.ContextManager().get_current_flow_name())"
|
|
1747
2213
|
flow_call = ast.Call(
|
|
1748
2214
|
func=ast.Attribute(
|
|
@@ -2199,7 +2665,7 @@ class WorkflowManager:
|
|
|
2199
2665
|
}
|
|
2200
2666
|
return workflow_shape
|
|
2201
2667
|
|
|
2202
|
-
def
|
|
2668
|
+
def extract_workflow_shape(self, workflow_name: str) -> dict[str, Any]:
|
|
2203
2669
|
"""Extracts the input and output shape for a workflow.
|
|
2204
2670
|
|
|
2205
2671
|
Here we gather information about the Workflow's exposed input and output Parameters
|
|
@@ -2249,365 +2715,165 @@ class WorkflowManager:
|
|
|
2249
2715
|
|
|
2250
2716
|
return workflow_shape
|
|
2251
2717
|
|
|
2252
|
-
def
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
"""Copies the libraries to the specified path for the workflow, returning the list of library paths.
|
|
2260
|
-
|
|
2261
|
-
This is used to package the workflow for publishing.
|
|
2262
|
-
"""
|
|
2263
|
-
library_paths: list[str] = []
|
|
2264
|
-
|
|
2265
|
-
for library_ref in node_libraries:
|
|
2266
|
-
library = GriptapeNodes.LibraryManager().get_library_info_by_library_name(library_ref.library_name)
|
|
2267
|
-
|
|
2268
|
-
if library is None:
|
|
2269
|
-
details = f"Attempted to publish workflow '{workflow.metadata.name}', but failed gathering library info for library '{library_ref.library_name}'."
|
|
2270
|
-
logger.error(details)
|
|
2271
|
-
raise ValueError(details)
|
|
2272
|
-
|
|
2273
|
-
library_data = LibraryRegistry.get_library(library_ref.library_name).get_library_data()
|
|
2274
|
-
|
|
2275
|
-
library_path = Path(library.library_path)
|
|
2276
|
-
absolute_library_path = library_path.resolve()
|
|
2277
|
-
abs_paths = [absolute_library_path]
|
|
2278
|
-
for node in library_data.nodes:
|
|
2279
|
-
p = (library_path.parent / Path(node.file_path)).resolve()
|
|
2280
|
-
abs_paths.append(p)
|
|
2281
|
-
common_root = Path(os.path.commonpath([str(p) for p in abs_paths]))
|
|
2282
|
-
dest = destination_path / common_root.name
|
|
2283
|
-
shutil.copytree(common_root, dest, dirs_exist_ok=True)
|
|
2284
|
-
library_path_relative_to_common_root = absolute_library_path.relative_to(common_root)
|
|
2285
|
-
library_paths.append(str(runtime_env_path / common_root.name / library_path_relative_to_common_root))
|
|
2718
|
+
def on_publish_workflow_request(self, request: PublishWorkflowRequest) -> ResultPayload:
|
|
2719
|
+
try:
|
|
2720
|
+
publisher_name = request.publisher_name
|
|
2721
|
+
event_handler_mappings = GriptapeNodes.LibraryManager().get_registered_event_handlers(
|
|
2722
|
+
request_type=type(request)
|
|
2723
|
+
)
|
|
2724
|
+
publishing_handler = event_handler_mappings.get(publisher_name)
|
|
2286
2725
|
|
|
2287
|
-
|
|
2726
|
+
if publishing_handler is None:
|
|
2727
|
+
msg = f"No publishing handler found for '{publisher_name}' in request type '{type(request).__name__}'."
|
|
2728
|
+
raise ValueError(msg) # noqa: TRY301
|
|
2288
2729
|
|
|
2289
|
-
|
|
2290
|
-
"""Determines the install source of the Griptape Nodes package.
|
|
2730
|
+
return publishing_handler.handler(request)
|
|
2291
2731
|
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
direct_url_text = dist.read_text("direct_url.json")
|
|
2297
|
-
# installing from pypi doesn't have a direct_url.json file
|
|
2298
|
-
if direct_url_text is None:
|
|
2299
|
-
logger.debug("No direct_url.json file found, assuming pypi install")
|
|
2300
|
-
return "pypi", None
|
|
2301
|
-
|
|
2302
|
-
direct_url_info = json.loads(direct_url_text)
|
|
2303
|
-
url = direct_url_info.get("url")
|
|
2304
|
-
if url.startswith("file://"):
|
|
2305
|
-
try:
|
|
2306
|
-
pkg_dir = Path(str(dist.locate_file(""))).resolve()
|
|
2307
|
-
git_root = next(p for p in (pkg_dir, *pkg_dir.parents) if (p / ".git").is_dir())
|
|
2308
|
-
commit = (
|
|
2309
|
-
subprocess.check_output( # noqa: S603
|
|
2310
|
-
["git", "rev-parse", "--short", "HEAD"], # noqa: S607
|
|
2311
|
-
cwd=git_root,
|
|
2312
|
-
stderr=subprocess.DEVNULL,
|
|
2313
|
-
)
|
|
2314
|
-
.decode()
|
|
2315
|
-
.strip()
|
|
2316
|
-
)
|
|
2317
|
-
except (StopIteration, subprocess.CalledProcessError):
|
|
2318
|
-
logger.debug("File URL but no git repo → file")
|
|
2319
|
-
return "file", None
|
|
2320
|
-
else:
|
|
2321
|
-
logger.debug("Detected git repo at %s (commit %s)", git_root, commit)
|
|
2322
|
-
return "git", commit
|
|
2323
|
-
if "vcs_info" in direct_url_info:
|
|
2324
|
-
logger.debug("Detected git repo at %s", url)
|
|
2325
|
-
return "git", direct_url_info["vcs_info"].get("commit_id")[:7]
|
|
2326
|
-
# Fall back to pypi if no other source is found
|
|
2327
|
-
logger.debug("Failed to detect install source, assuming pypi")
|
|
2328
|
-
return "pypi", None
|
|
2329
|
-
|
|
2330
|
-
def _get_merged_env_file_mapping(self, workspace_env_file_path: Path) -> dict[str, Any]:
|
|
2331
|
-
"""Merges the secrets from the workspace env file with the secrets from the GriptapeNodes SecretsManager.
|
|
2332
|
-
|
|
2333
|
-
This is used to create a single .env file for the workflow. We can gather all secrets explicitly defined in the .env file
|
|
2334
|
-
and by the settings/SecretsManager, but we will not gather all secrets from the OS env for the purpose of publishing.
|
|
2335
|
-
"""
|
|
2336
|
-
env_file_dict = {}
|
|
2337
|
-
if workspace_env_file_path.exists():
|
|
2338
|
-
env_file = DotEnv(workspace_env_file_path)
|
|
2339
|
-
env_file_dict = env_file.dict()
|
|
2340
|
-
|
|
2341
|
-
get_all_secrets_request = GetAllSecretValuesRequest()
|
|
2342
|
-
get_all_secrets_result = GriptapeNodes.handle_request(request=get_all_secrets_request)
|
|
2343
|
-
if not isinstance(get_all_secrets_result, GetAllSecretValuesResultSuccess):
|
|
2344
|
-
details = "Failed to get all secret values."
|
|
2345
|
-
logger.error(details)
|
|
2346
|
-
raise TypeError(details)
|
|
2347
|
-
|
|
2348
|
-
secret_values = get_all_secrets_result.values
|
|
2349
|
-
for secret_name, secret_value in secret_values.items():
|
|
2350
|
-
if secret_name not in env_file_dict:
|
|
2351
|
-
env_file_dict[secret_name] = secret_value
|
|
2352
|
-
|
|
2353
|
-
return env_file_dict
|
|
2732
|
+
except Exception as e:
|
|
2733
|
+
details = f"Failed to publish workflow '{request.workflow_name}': {e!s}"
|
|
2734
|
+
logger.exception(details)
|
|
2735
|
+
return PublishWorkflowResultFailure(exception=e)
|
|
2354
2736
|
|
|
2355
|
-
def
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2737
|
+
def on_import_workflow_as_referenced_sub_flow_request(
|
|
2738
|
+
self, request: ImportWorkflowAsReferencedSubFlowRequest
|
|
2739
|
+
) -> ResultPayload:
|
|
2740
|
+
"""Import a registered workflow as a new referenced sub flow in the current context."""
|
|
2741
|
+
# Validate prerequisites
|
|
2742
|
+
validation_error = self._validate_import_prerequisites(request)
|
|
2743
|
+
if validation_error:
|
|
2744
|
+
return validation_error
|
|
2745
|
+
|
|
2746
|
+
# Get the workflow (validation passed, so we know it exists)
|
|
2747
|
+
workflow = self._get_workflow_by_name(request.workflow_name)
|
|
2748
|
+
|
|
2749
|
+
# Determine target flow name
|
|
2750
|
+
if request.flow_name is not None:
|
|
2751
|
+
flow_name = request.flow_name
|
|
2752
|
+
else:
|
|
2753
|
+
flow_name = GriptapeNodes.ContextManager().get_current_flow().name
|
|
2359
2754
|
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
secrets_manager = GriptapeNodes.get_instance()._secrets_manager
|
|
2363
|
-
workflow = WorkflowRegistry.get_workflow_by_name(workflow_name)
|
|
2755
|
+
# Execute the import
|
|
2756
|
+
return self._execute_workflow_import(request, workflow, flow_name)
|
|
2364
2757
|
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2758
|
+
def _validate_import_prerequisites(self, request: ImportWorkflowAsReferencedSubFlowRequest) -> ResultPayload | None:
|
|
2759
|
+
"""Validate all prerequisites for import. Returns error result or None if valid."""
|
|
2760
|
+
# Check workflow exists and get it
|
|
2761
|
+
try:
|
|
2762
|
+
workflow = self._get_workflow_by_name(request.workflow_name)
|
|
2763
|
+
except KeyError:
|
|
2764
|
+
logger.error(
|
|
2765
|
+
"Attempted to import workflow '%s' as referenced sub flow. Failed because workflow is not registered",
|
|
2766
|
+
request.workflow_name,
|
|
2371
2767
|
)
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
)
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
with resources.as_file(bootstrap_script_traversable) as script_path:
|
|
2386
|
-
root_griptape_nodes_path = Path(script_path).parent.parent
|
|
2387
|
-
|
|
2388
|
-
structure_file_path = root_griptape_nodes_path / "bootstrap" / "bootstrap_script.py"
|
|
2389
|
-
structure_config_file_path = root_griptape_nodes_path / "bootstrap" / "structure_config.yaml"
|
|
2390
|
-
pre_build_install_script_path = root_griptape_nodes_path / "bootstrap" / "pre_build_install_script.sh"
|
|
2391
|
-
post_build_install_script_path = root_griptape_nodes_path / "bootstrap" / "post_build_install_script.sh"
|
|
2392
|
-
register_libraries_script_path = root_griptape_nodes_path / "bootstrap" / "register_libraries_script.py"
|
|
2393
|
-
full_workflow_file_path = WorkflowRegistry.get_complete_file_path(workflow.file_path)
|
|
2394
|
-
|
|
2395
|
-
env_file_mapping = self._get_merged_env_file_mapping(secrets_manager.workspace_env_path)
|
|
2396
|
-
|
|
2397
|
-
config = config_manager.user_config
|
|
2398
|
-
config["workspace_directory"] = packaged_top_level_dir
|
|
2399
|
-
|
|
2400
|
-
# Create a temporary directory to perform the packaging
|
|
2401
|
-
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
2402
|
-
tmp_dir_path = Path(tmp_dir)
|
|
2403
|
-
temp_workflow_file_path = tmp_dir_path / "workflow.py"
|
|
2404
|
-
temp_structure_path = tmp_dir_path / "structure.py"
|
|
2405
|
-
temp_pre_build_install_script_path = tmp_dir_path / "pre_build_install_script.sh"
|
|
2406
|
-
temp_post_build_install_script_path = tmp_dir_path / "post_build_install_script.sh"
|
|
2407
|
-
temp_register_libraries_script_path = tmp_dir_path / "register_libraries_script.py"
|
|
2408
|
-
config_file_path = tmp_dir_path / "GriptapeNodes" / "griptape_nodes_config.json"
|
|
2409
|
-
init_file_path = tmp_dir_path / "__init__.py"
|
|
2768
|
+
return ImportWorkflowAsReferencedSubFlowResultFailure()
|
|
2769
|
+
|
|
2770
|
+
# Check workflow version - Schema version 0.6.0+ required for referenced workflow imports
|
|
2771
|
+
# (workflow schema was fixed in 0.6.0 to support importing workflows)
|
|
2772
|
+
required_version = Version(major=0, minor=6, patch=0)
|
|
2773
|
+
workflow_version = Version.from_string(workflow.metadata.schema_version)
|
|
2774
|
+
if workflow_version is None or workflow_version < required_version:
|
|
2775
|
+
logger.error(
|
|
2776
|
+
"Attempted to import workflow '%s' as referenced sub flow. Failed because workflow version '%s' is less than required version '0.6.0'. To remedy, open the workflow you are attempting to import and save it again to upgrade it to the latest version.",
|
|
2777
|
+
request.workflow_name,
|
|
2778
|
+
workflow.metadata.schema_version,
|
|
2779
|
+
)
|
|
2780
|
+
return ImportWorkflowAsReferencedSubFlowResultFailure()
|
|
2410
2781
|
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
shutil.copyfile(structure_config_file_path, tmp_dir_path / "structure_config.yaml")
|
|
2419
|
-
|
|
2420
|
-
# Write the environment variables to the .env file
|
|
2421
|
-
self._write_env_file(tmp_dir_path / ".env", env_file_mapping)
|
|
2422
|
-
|
|
2423
|
-
# Get the library paths
|
|
2424
|
-
library_paths = self._copy_libraries_to_path_for_workflow(
|
|
2425
|
-
node_libraries=workflow.metadata.node_libraries_referenced,
|
|
2426
|
-
destination_path=tmp_dir_path / "libraries",
|
|
2427
|
-
runtime_env_path=Path(packaged_top_level_dir) / "libraries",
|
|
2428
|
-
workflow=workflow,
|
|
2782
|
+
# Check target flow
|
|
2783
|
+
flow_name = request.flow_name
|
|
2784
|
+
if flow_name is None:
|
|
2785
|
+
if not GriptapeNodes.ContextManager().has_current_flow():
|
|
2786
|
+
logger.error(
|
|
2787
|
+
"Attempted to import workflow '%s' into Current Context. Failed because Current Context was empty",
|
|
2788
|
+
request.workflow_name,
|
|
2429
2789
|
)
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
config_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
2442
|
-
with config_file_path.open("w", encoding="utf-8") as config_file:
|
|
2443
|
-
config_file.write(json.dumps(config, indent=4))
|
|
2444
|
-
|
|
2445
|
-
init_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
2446
|
-
with init_file_path.open("w", encoding="utf-8") as init_file:
|
|
2447
|
-
init_file.write('"""This is a temporary __init__.py file for the structure."""\n')
|
|
2448
|
-
|
|
2449
|
-
shutil.copyfile(config_file_path, tmp_dir_path / "griptape_nodes_config.json")
|
|
2450
|
-
|
|
2451
|
-
except Exception as e:
|
|
2452
|
-
details = f"Failed to copy files to temporary directory. Error: {e}"
|
|
2453
|
-
logger.exception(details)
|
|
2454
|
-
raise
|
|
2455
|
-
|
|
2456
|
-
# Create the requirements.txt file using the correct engine version
|
|
2457
|
-
source, commit_id = self.__get_install_source()
|
|
2458
|
-
if source == "git" and commit_id is not None:
|
|
2459
|
-
engine_version = commit_id
|
|
2460
|
-
requirements_file_path = tmp_dir_path / "requirements.txt"
|
|
2461
|
-
with requirements_file_path.open("w", encoding="utf-8") as requirements_file:
|
|
2462
|
-
requirements_file.write(
|
|
2463
|
-
f"griptape-nodes @ git+https://github.com/griptape-ai/griptape-nodes.git@{engine_version}\n"
|
|
2790
|
+
return ImportWorkflowAsReferencedSubFlowResultFailure()
|
|
2791
|
+
else:
|
|
2792
|
+
# Validate that the specified flow exists
|
|
2793
|
+
flow_manager = GriptapeNodes.FlowManager()
|
|
2794
|
+
try:
|
|
2795
|
+
flow_manager.get_flow_by_name(flow_name)
|
|
2796
|
+
except KeyError:
|
|
2797
|
+
logger.error(
|
|
2798
|
+
"Attempted to import workflow '%s' into flow '%s'. Failed because target flow does not exist",
|
|
2799
|
+
request.workflow_name,
|
|
2800
|
+
flow_name,
|
|
2464
2801
|
)
|
|
2802
|
+
return ImportWorkflowAsReferencedSubFlowResultFailure()
|
|
2465
2803
|
|
|
2466
|
-
|
|
2467
|
-
shutil.make_archive(str(archive_base_name), "zip", tmp_dir)
|
|
2468
|
-
return str(archive_base_name) + ".zip"
|
|
2469
|
-
|
|
2470
|
-
def _get_publish_workflow_request(self, base_url: str, files: httpx._types.RequestFiles) -> httpx.Request:
|
|
2471
|
-
endpoint = urljoin(
|
|
2472
|
-
base_url,
|
|
2473
|
-
"/api/workflows",
|
|
2474
|
-
)
|
|
2475
|
-
return httpx.Request(
|
|
2476
|
-
method="post",
|
|
2477
|
-
url=endpoint,
|
|
2478
|
-
files=files,
|
|
2479
|
-
)
|
|
2480
|
-
|
|
2481
|
-
def _get_update_workflow_request(
|
|
2482
|
-
self, base_url: str, files: httpx._types.RequestFiles, workflow_id: str
|
|
2483
|
-
) -> httpx.Request:
|
|
2484
|
-
endpoint = urljoin(
|
|
2485
|
-
base_url,
|
|
2486
|
-
f"/api/workflows/{workflow_id}",
|
|
2487
|
-
)
|
|
2488
|
-
return httpx.Request(
|
|
2489
|
-
method="patch",
|
|
2490
|
-
url=endpoint,
|
|
2491
|
-
files=files,
|
|
2492
|
-
)
|
|
2804
|
+
return None
|
|
2493
2805
|
|
|
2494
|
-
def
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
f"/api/workflows/{workflow_id}",
|
|
2498
|
-
)
|
|
2499
|
-
request = httpx.Request(
|
|
2500
|
-
method="get",
|
|
2501
|
-
url=endpoint,
|
|
2502
|
-
)
|
|
2503
|
-
request.headers["Authorization"] = f"Bearer {api_key}"
|
|
2504
|
-
request.headers["Accept"] = "application/json"
|
|
2806
|
+
def _get_workflow_by_name(self, workflow_name: str) -> Workflow:
|
|
2807
|
+
"""Get workflow by name from the registry."""
|
|
2808
|
+
return WorkflowRegistry.get_workflow_by_name(workflow_name)
|
|
2505
2809
|
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2810
|
+
def _execute_workflow_import(
|
|
2811
|
+
self, request: ImportWorkflowAsReferencedSubFlowRequest, workflow: Workflow, flow_name: str
|
|
2812
|
+
) -> ResultPayload:
|
|
2813
|
+
"""Execute the actual workflow import."""
|
|
2814
|
+
# Get current flows before importing
|
|
2815
|
+
obj_manager = GriptapeNodes.ObjectManager()
|
|
2816
|
+
flows_before = set(obj_manager.get_filtered_subset(type=ControlFlow).keys())
|
|
2817
|
+
|
|
2818
|
+
# Execute the workflow within the target flow context and referenced context
|
|
2819
|
+
with GriptapeNodes.ContextManager().flow(flow_name): # noqa: SIM117
|
|
2820
|
+
with self.ReferencedWorkflowContext(self, request.workflow_name):
|
|
2821
|
+
workflow_result = self.run_workflow(workflow.file_path)
|
|
2822
|
+
|
|
2823
|
+
if not workflow_result.execution_successful:
|
|
2824
|
+
logger.error(
|
|
2825
|
+
"Attempted to import workflow '%s' as referenced sub flow. Failed because workflow execution failed: %s",
|
|
2826
|
+
request.workflow_name,
|
|
2827
|
+
workflow_result.execution_details,
|
|
2828
|
+
)
|
|
2829
|
+
return ImportWorkflowAsReferencedSubFlowResultFailure()
|
|
2520
2830
|
|
|
2521
|
-
#
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
if not api_key:
|
|
2525
|
-
details = "Failed to get API key from environment variables."
|
|
2526
|
-
logger.error(details)
|
|
2527
|
-
raise ValueError(details)
|
|
2831
|
+
# Get flows after importing to find the new referenced sub flow
|
|
2832
|
+
flows_after = set(obj_manager.get_filtered_subset(type=ControlFlow).keys())
|
|
2833
|
+
new_flows = flows_after - flows_before
|
|
2528
2834
|
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
"file": ("workflow.zip", file.read()),
|
|
2534
|
-
}
|
|
2535
|
-
|
|
2536
|
-
request: httpx.Request = (
|
|
2537
|
-
self._get_update_workflow_request(
|
|
2538
|
-
base_url=base_url,
|
|
2539
|
-
files=parts,
|
|
2540
|
-
workflow_id=workflow_id,
|
|
2835
|
+
if not new_flows:
|
|
2836
|
+
logger.error(
|
|
2837
|
+
"Attempted to import workflow '%s' as referenced sub flow. Failed because no new flow was created",
|
|
2838
|
+
request.workflow_name,
|
|
2541
2839
|
)
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2840
|
+
return ImportWorkflowAsReferencedSubFlowResultFailure()
|
|
2841
|
+
|
|
2842
|
+
# For now, use the first created flow as the main imported flow
|
|
2843
|
+
# This handles nested workflows correctly since sub-flows are expected
|
|
2844
|
+
created_flow_name = next(iter(new_flows))
|
|
2845
|
+
|
|
2846
|
+
if len(new_flows) > 1:
|
|
2847
|
+
logger.debug(
|
|
2848
|
+
"Multiple flows created during import of '%s'. Main flow: %s, Sub-flows: %s",
|
|
2849
|
+
request.workflow_name,
|
|
2850
|
+
created_flow_name,
|
|
2851
|
+
[flow for flow in new_flows if flow != created_flow_name],
|
|
2546
2852
|
)
|
|
2547
|
-
)
|
|
2548
|
-
request.headers["Authorization"] = f"Bearer {api_key}"
|
|
2549
|
-
request.headers["Accept"] = "application/json"
|
|
2550
2853
|
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
response.raise_for_status()
|
|
2556
|
-
return response.json()
|
|
2557
|
-
except Exception:
|
|
2558
|
-
status_code = response.status_code if response else "Unknown"
|
|
2559
|
-
response_text = response.text if response else "No response text"
|
|
2560
|
-
details = f"Failed to publish workflow. Status code: {status_code}, Response: {response_text}"
|
|
2561
|
-
logger.error(details)
|
|
2562
|
-
raise
|
|
2563
|
-
|
|
2564
|
-
def _update_workflow_metadata_with_published_id(self, workflow_name: str, published_workflow_id: str) -> None:
|
|
2565
|
-
workflow = WorkflowRegistry.get_workflow_by_name(workflow_name)
|
|
2566
|
-
if workflow.metadata.published_workflow_id != published_workflow_id:
|
|
2567
|
-
workflow.metadata.published_workflow_id = published_workflow_id
|
|
2568
|
-
|
|
2569
|
-
file_name = Path(workflow.file_path).name
|
|
2570
|
-
file_name = file_name.replace(".py", "") if workflow.file_path.endswith(".py") else workflow.file_path
|
|
2571
|
-
save_workflow_request = SaveWorkflowRequest(file_name=file_name)
|
|
2572
|
-
save_workflow_result = self.on_save_workflow_request(save_workflow_request)
|
|
2573
|
-
if save_workflow_result.failed():
|
|
2574
|
-
details = f"Failed to update workflow metadata with published ID for workflow '{workflow_name}'."
|
|
2575
|
-
logger.error(details)
|
|
2576
|
-
raise ValueError(details)
|
|
2577
|
-
|
|
2578
|
-
def on_publish_workflow_request(self, request: PublishWorkflowRequest) -> ResultPayload:
|
|
2579
|
-
try:
|
|
2580
|
-
# Get the workflow shape
|
|
2581
|
-
workflow_shape = self._extract_workflow_shape(request.workflow_name)
|
|
2582
|
-
logger.info("Workflow shape: %s", workflow_shape)
|
|
2583
|
-
|
|
2584
|
-
# Package the workflow
|
|
2585
|
-
package_path = self._package_workflow(request.workflow_name)
|
|
2586
|
-
logger.info("Workflow packaged to path: %s", package_path)
|
|
2587
|
-
|
|
2588
|
-
input_data = {
|
|
2589
|
-
"name": request.workflow_name,
|
|
2590
|
-
}
|
|
2591
|
-
session_id = GriptapeNodes.get_session_id()
|
|
2592
|
-
if session_id is not None:
|
|
2593
|
-
input_data["session_id"] = session_id
|
|
2594
|
-
input_data.update(workflow_shape)
|
|
2595
|
-
response = self._deploy_workflow_to_cloud(request.workflow_name, package_path, input_data)
|
|
2596
|
-
logger.info("Workflow '%s' published successfully: %s", request.workflow_name, response)
|
|
2597
|
-
|
|
2598
|
-
self._update_workflow_metadata_with_published_id(
|
|
2599
|
-
workflow_name=request.workflow_name,
|
|
2600
|
-
published_workflow_id=response["id"],
|
|
2854
|
+
# Apply imported flow metadata if provided
|
|
2855
|
+
if request.imported_flow_metadata:
|
|
2856
|
+
set_metadata_request = SetFlowMetadataRequest(
|
|
2857
|
+
flow_name=created_flow_name, metadata=request.imported_flow_metadata
|
|
2601
2858
|
)
|
|
2602
|
-
|
|
2859
|
+
set_metadata_result = GriptapeNodes.handle_request(set_metadata_request)
|
|
2603
2860
|
|
|
2604
|
-
|
|
2605
|
-
|
|
2861
|
+
if not isinstance(set_metadata_result, SetFlowMetadataResultSuccess):
|
|
2862
|
+
logger.error(
|
|
2863
|
+
"Attempted to import workflow '%s' as referenced sub flow. Failed because metadata could not be applied to created flow '%s'",
|
|
2864
|
+
request.workflow_name,
|
|
2865
|
+
created_flow_name,
|
|
2866
|
+
)
|
|
2867
|
+
return ImportWorkflowAsReferencedSubFlowResultFailure()
|
|
2868
|
+
|
|
2869
|
+
logger.debug(
|
|
2870
|
+
"Applied imported flow metadata to '%s': %s", created_flow_name, request.imported_flow_metadata
|
|
2606
2871
|
)
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2872
|
+
|
|
2873
|
+
logger.info(
|
|
2874
|
+
"Successfully imported workflow '%s' as referenced sub flow '%s'", request.workflow_name, created_flow_name
|
|
2875
|
+
)
|
|
2876
|
+
return ImportWorkflowAsReferencedSubFlowResultSuccess(created_flow_name=created_flow_name)
|
|
2611
2877
|
|
|
2612
2878
|
def _walk_object_tree(
|
|
2613
2879
|
self, obj: Any, process_class_fn: Callable[[type, Any], None], visited: set[int] | None = None
|