vellum-ai 1.11.2__py3-none-any.whl → 1.13.5__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.
Potentially problematic release.
This version of vellum-ai might be problematic. Click here for more details.
- vellum/__init__.py +18 -0
- vellum/client/README.md +1 -1
- vellum/client/core/client_wrapper.py +2 -2
- vellum/client/core/force_multipart.py +4 -2
- vellum/client/core/http_response.py +1 -1
- vellum/client/core/pydantic_utilities.py +7 -4
- vellum/client/errors/too_many_requests_error.py +1 -2
- vellum/client/reference.md +677 -76
- vellum/client/resources/container_images/client.py +299 -0
- vellum/client/resources/container_images/raw_client.py +286 -0
- vellum/client/resources/documents/client.py +20 -10
- vellum/client/resources/documents/raw_client.py +20 -10
- vellum/client/resources/events/raw_client.py +4 -4
- vellum/client/resources/integration_auth_configs/client.py +2 -0
- vellum/client/resources/integration_auth_configs/raw_client.py +2 -0
- vellum/client/resources/integration_providers/client.py +28 -2
- vellum/client/resources/integration_providers/raw_client.py +24 -0
- vellum/client/resources/integrations/client.py +52 -4
- vellum/client/resources/integrations/raw_client.py +61 -0
- vellum/client/resources/workflow_deployments/client.py +156 -0
- vellum/client/resources/workflow_deployments/raw_client.py +334 -0
- vellum/client/resources/workflows/client.py +212 -8
- vellum/client/resources/workflows/raw_client.py +343 -6
- vellum/client/types/__init__.py +18 -0
- vellum/client/types/api_actor_type_enum.py +1 -1
- vellum/client/types/check_workflow_execution_status_error.py +21 -0
- vellum/client/types/check_workflow_execution_status_response.py +29 -0
- vellum/client/types/code_execution_package_request.py +21 -0
- vellum/client/types/composio_execute_tool_request.py +5 -0
- vellum/client/types/composio_tool_definition.py +1 -0
- vellum/client/types/container_image_build_config.py +1 -0
- vellum/client/types/container_image_container_image_tag.py +1 -0
- vellum/client/types/dataset_row_push_request.py +3 -0
- vellum/client/types/document_document_to_document_index.py +1 -0
- vellum/client/types/integration_name.py +24 -0
- vellum/client/types/node_execution_fulfilled_body.py +1 -0
- vellum/client/types/node_execution_log_body.py +24 -0
- vellum/client/types/node_execution_log_event.py +47 -0
- vellum/client/types/prompt_deployment_release_prompt_deployment.py +1 -0
- vellum/client/types/runner_config_request.py +24 -0
- vellum/client/types/severity_enum.py +5 -0
- vellum/client/types/slim_composio_tool_definition.py +1 -0
- vellum/client/types/slim_document_document_to_document_index.py +2 -0
- vellum/client/types/type_checker_enum.py +5 -0
- vellum/client/types/vellum_audio.py +5 -1
- vellum/client/types/vellum_audio_request.py +5 -1
- vellum/client/types/vellum_document.py +5 -1
- vellum/client/types/vellum_document_request.py +5 -1
- vellum/client/types/vellum_image.py +5 -1
- vellum/client/types/vellum_image_request.py +5 -1
- vellum/client/types/vellum_node_execution_event.py +2 -0
- vellum/client/types/vellum_variable.py +5 -0
- vellum/client/types/vellum_variable_extensions.py +1 -0
- vellum/client/types/vellum_variable_type.py +1 -0
- vellum/client/types/vellum_video.py +5 -1
- vellum/client/types/vellum_video_request.py +5 -1
- vellum/client/types/workflow_deployment_release_workflow_deployment.py +1 -0
- vellum/client/types/workflow_event.py +2 -0
- vellum/client/types/workflow_execution_fulfilled_body.py +1 -0
- vellum/client/types/workflow_result_event_output_data_array.py +1 -1
- vellum/client/types/workflow_result_event_output_data_chat_history.py +1 -1
- vellum/client/types/workflow_result_event_output_data_error.py +1 -1
- vellum/client/types/workflow_result_event_output_data_function_call.py +1 -1
- vellum/client/types/workflow_result_event_output_data_json.py +1 -1
- vellum/client/types/workflow_result_event_output_data_number.py +1 -1
- vellum/client/types/workflow_result_event_output_data_search_results.py +1 -1
- vellum/client/types/workflow_result_event_output_data_string.py +1 -1
- vellum/client/types/workflow_sandbox_execute_node_response.py +8 -0
- vellum/plugins/vellum_mypy.py +37 -2
- vellum/types/check_workflow_execution_status_error.py +3 -0
- vellum/types/check_workflow_execution_status_response.py +3 -0
- vellum/types/code_execution_package_request.py +3 -0
- vellum/types/node_execution_log_body.py +3 -0
- vellum/types/node_execution_log_event.py +3 -0
- vellum/types/runner_config_request.py +3 -0
- vellum/types/severity_enum.py +3 -0
- vellum/types/type_checker_enum.py +3 -0
- vellum/types/workflow_sandbox_execute_node_response.py +3 -0
- vellum/utils/files/mixin.py +26 -0
- vellum/utils/files/tests/test_mixin.py +62 -0
- vellum/utils/tests/test_vellum_client.py +95 -0
- vellum/utils/uuid.py +19 -2
- vellum/utils/vellum_client.py +10 -3
- vellum/workflows/__init__.py +7 -1
- vellum/workflows/descriptors/base.py +86 -0
- vellum/workflows/descriptors/tests/test_utils.py +9 -0
- vellum/workflows/errors/tests/__init__.py +0 -0
- vellum/workflows/errors/tests/test_types.py +52 -0
- vellum/workflows/errors/types.py +1 -0
- vellum/workflows/events/node.py +24 -0
- vellum/workflows/events/tests/test_event.py +123 -0
- vellum/workflows/events/types.py +2 -1
- vellum/workflows/events/workflow.py +28 -2
- vellum/workflows/expressions/add.py +3 -0
- vellum/workflows/expressions/tests/test_add.py +24 -0
- vellum/workflows/graph/graph.py +26 -5
- vellum/workflows/graph/tests/test_graph.py +228 -1
- vellum/workflows/inputs/base.py +22 -6
- vellum/workflows/inputs/dataset_row.py +121 -16
- vellum/workflows/inputs/tests/test_inputs.py +3 -3
- vellum/workflows/integrations/tests/test_vellum_integration_service.py +84 -0
- vellum/workflows/integrations/vellum_integration_service.py +12 -1
- vellum/workflows/loaders/base.py +2 -0
- vellum/workflows/nodes/bases/base.py +37 -16
- vellum/workflows/nodes/bases/tests/test_base_node.py +104 -1
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py +1 -0
- vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +1 -1
- vellum/workflows/nodes/core/map_node/node.py +7 -5
- vellum/workflows/nodes/core/map_node/tests/test_node.py +33 -0
- vellum/workflows/nodes/core/retry_node/node.py +1 -0
- vellum/workflows/nodes/core/try_node/node.py +1 -0
- vellum/workflows/nodes/displayable/api_node/node.py +3 -2
- vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +38 -0
- vellum/workflows/nodes/displayable/bases/api_node/node.py +1 -1
- vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +18 -1
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +109 -2
- vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +13 -2
- vellum/workflows/nodes/displayable/code_execution_node/node.py +9 -15
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_node.py +65 -24
- vellum/workflows/nodes/displayable/code_execution_node/utils.py +3 -0
- vellum/workflows/nodes/displayable/final_output_node/node.py +24 -69
- vellum/workflows/nodes/displayable/final_output_node/tests/test_node.py +53 -3
- vellum/workflows/nodes/displayable/note_node/node.py +4 -1
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +16 -5
- vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +47 -0
- vellum/workflows/nodes/displayable/tool_calling_node/node.py +74 -34
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py +204 -8
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py +92 -71
- vellum/workflows/nodes/mocks.py +47 -213
- vellum/workflows/nodes/tests/test_mocks.py +0 -177
- vellum/workflows/nodes/utils.py +23 -8
- vellum/workflows/outputs/base.py +36 -3
- vellum/workflows/references/environment_variable.py +1 -11
- vellum/workflows/references/lazy.py +8 -0
- vellum/workflows/references/state_value.py +24 -1
- vellum/workflows/references/tests/test_lazy.py +58 -0
- vellum/workflows/references/trigger.py +8 -3
- vellum/workflows/references/workflow_input.py +8 -0
- vellum/workflows/resolvers/resolver.py +13 -3
- vellum/workflows/resolvers/tests/test_resolver.py +31 -0
- vellum/workflows/runner/runner.py +159 -14
- vellum/workflows/runner/tests/__init__.py +0 -0
- vellum/workflows/runner/tests/test_runner.py +170 -0
- vellum/workflows/sandbox.py +7 -8
- vellum/workflows/state/base.py +89 -30
- vellum/workflows/state/context.py +74 -3
- vellum/workflows/state/tests/test_state.py +269 -1
- vellum/workflows/tests/test_dataset_row.py +8 -7
- vellum/workflows/tests/test_sandbox.py +97 -8
- vellum/workflows/triggers/__init__.py +2 -1
- vellum/workflows/triggers/base.py +160 -28
- vellum/workflows/triggers/chat_message.py +141 -0
- vellum/workflows/triggers/integration.py +12 -0
- vellum/workflows/triggers/manual.py +3 -1
- vellum/workflows/triggers/schedule.py +3 -1
- vellum/workflows/triggers/tests/test_chat_message.py +257 -0
- vellum/workflows/types/core.py +18 -0
- vellum/workflows/types/definition.py +6 -13
- vellum/workflows/types/generics.py +12 -0
- vellum/workflows/types/tests/test_utils.py +12 -0
- vellum/workflows/types/utils.py +32 -2
- vellum/workflows/types/workflow_metadata.py +124 -0
- vellum/workflows/utils/functions.py +152 -16
- vellum/workflows/utils/pydantic_schema.py +19 -1
- vellum/workflows/utils/tests/test_functions.py +123 -8
- vellum/workflows/utils/tests/test_validate.py +79 -0
- vellum/workflows/utils/tests/test_vellum_variables.py +62 -2
- vellum/workflows/utils/uuids.py +90 -0
- vellum/workflows/utils/validate.py +108 -0
- vellum/workflows/utils/vellum_variables.py +96 -16
- vellum/workflows/workflows/base.py +177 -35
- vellum/workflows/workflows/tests/test_base_workflow.py +51 -0
- {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/METADATA +6 -1
- {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/RECORD +274 -227
- vellum_cli/__init__.py +21 -0
- vellum_cli/config.py +16 -2
- vellum_cli/pull.py +2 -0
- vellum_cli/push.py +23 -10
- vellum_cli/tests/conftest.py +8 -13
- vellum_cli/tests/test_image_push.py +4 -11
- vellum_cli/tests/test_pull.py +83 -68
- vellum_cli/tests/test_push.py +251 -2
- vellum_ee/assets/node-definitions.json +225 -12
- vellum_ee/scripts/generate_node_definitions.py +15 -3
- vellum_ee/workflows/display/base.py +4 -3
- vellum_ee/workflows/display/nodes/base_node_display.py +44 -11
- vellum_ee/workflows/display/nodes/tests/test_base_node_display.py +93 -0
- vellum_ee/workflows/display/nodes/types.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/__init__.py +0 -2
- vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py +5 -2
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +10 -2
- vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +17 -14
- vellum_ee/workflows/display/nodes/vellum/map_node.py +2 -0
- vellum_ee/workflows/display/nodes/vellum/note_node.py +18 -3
- vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +37 -14
- vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py +62 -2
- vellum_ee/workflows/display/nodes/vellum/tests/test_final_output_node.py +136 -0
- vellum_ee/workflows/display/nodes/vellum/tests/test_note_node.py +44 -7
- vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_node.py +5 -13
- vellum_ee/workflows/display/nodes/vellum/tests/test_subworkflow_deployment_node.py +27 -17
- vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +145 -22
- vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +107 -2
- vellum_ee/workflows/display/nodes/vellum/utils.py +54 -12
- vellum_ee/workflows/display/tests/test_base_workflow_display.py +13 -16
- vellum_ee/workflows/display/tests/test_json_schema_validation.py +190 -0
- vellum_ee/workflows/display/tests/test_mocks.py +912 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +14 -2
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +109 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py +3 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py +187 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +34 -325
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +42 -393
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +13 -315
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_default_state_serialization.py +2 -122
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +24 -115
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +4 -93
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +7 -80
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py +9 -101
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +77 -308
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +62 -324
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +3 -82
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +4 -142
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +1 -61
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_set_state_node_serialization.py +4 -4
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +205 -134
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +34 -146
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +2 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py +8 -6
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +137 -266
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_tool_wrapper_serialization.py +84 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_mcp_serialization.py +55 -16
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +15 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_tool_wrapper_serialization.py +71 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_vellum_integration_serialization.py +119 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_workflow_deployment_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +0 -2
- vellum_ee/workflows/display/tests/workflow_serialization/test_chat_message_dict_reference_serialization.py +22 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_chat_message_trigger_serialization.py +412 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_code_tool_node_reference_error.py +106 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +9 -41
- vellum_ee/workflows/display/tests/workflow_serialization/test_duplicate_trigger_name_validation.py +208 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_final_output_node_not_referenced_by_workflow_outputs.py +45 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_infinite_loop_validation.py +66 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_int_input_serialization.py +40 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_serialization.py +8 -14
- vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_validation.py +173 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_with_entrypoint_node_id.py +16 -13
- vellum_ee/workflows/display/tests/workflow_serialization/test_list_vellum_document_serialization.py +5 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_manual_trigger_serialization.py +12 -2
- vellum_ee/workflows/display/tests/workflow_serialization/test_multi_trigger_same_node_serialization.py +111 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_no_triggers_no_entrypoint_validation.py +64 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_partial_workflow_meta_display_override.py +55 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_dataset_mocks_serialization.py +268 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_invalid_pdf_data_url.py +49 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_validation_errors.py +112 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_scheduled_trigger_serialization.py +25 -16
- vellum_ee/workflows/display/tests/workflow_serialization/test_terminal_node_in_unused_graphs_serialization.py +53 -0
- vellum_ee/workflows/display/utils/exceptions.py +34 -0
- vellum_ee/workflows/display/utils/expressions.py +463 -52
- vellum_ee/workflows/display/utils/metadata.py +98 -33
- vellum_ee/workflows/display/utils/tests/test_metadata.py +31 -0
- vellum_ee/workflows/display/utils/triggers.py +153 -0
- vellum_ee/workflows/display/utils/vellum.py +59 -5
- vellum_ee/workflows/display/workflows/base_workflow_display.py +656 -254
- vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +26 -0
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +77 -29
- vellum_ee/workflows/server/namespaces.py +18 -0
- vellum_ee/workflows/tests/test_display_meta.py +2 -0
- vellum_ee/workflows/tests/test_serialize_module.py +174 -7
- vellum_ee/workflows/tests/test_server.py +0 -3
- vellum_ee/workflows/display/nodes/vellum/function_node.py +0 -14
- {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/LICENSE +0 -0
- {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/WHEEL +0 -0
- {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/entry_points.txt +0 -0
vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_dataset_mocks_serialization.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import sys
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
|
|
7
|
+
from vellum_ee.workflows.server.namespaces import get_random_namespace
|
|
8
|
+
from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _generate_uuid_namespace() -> str:
|
|
12
|
+
"""Generate a UUID namespace like the workflow server uses for some requests."""
|
|
13
|
+
return str(uuid4())
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.parametrize(
|
|
17
|
+
"namespace_generator",
|
|
18
|
+
[
|
|
19
|
+
pytest.param(_generate_uuid_namespace, id="uuid_namespace"),
|
|
20
|
+
pytest.param(get_random_namespace, id="workflow_tmp_namespace"),
|
|
21
|
+
],
|
|
22
|
+
)
|
|
23
|
+
def test_serialize_module__dataset_mocks_are_stable(namespace_generator: Callable[[], str]):
|
|
24
|
+
"""
|
|
25
|
+
Tests that serialization produces stable, deterministic output for DatasetRow mocks
|
|
26
|
+
across multiple serializations, regardless of the namespace format used.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# GIVEN a workflow module with a sandbox.py that has DatasetRow with mocks
|
|
30
|
+
files = {
|
|
31
|
+
"__init__.py": "",
|
|
32
|
+
"workflow.py": """\
|
|
33
|
+
from vellum.workflows import BaseWorkflow
|
|
34
|
+
from vellum.workflows.inputs import BaseInputs
|
|
35
|
+
from vellum.workflows.nodes import BaseNode
|
|
36
|
+
from vellum.workflows.state import BaseState
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Inputs(BaseInputs):
|
|
40
|
+
message: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class CodeExecution(BaseNode[BaseState]):
|
|
44
|
+
class Outputs(BaseNode.Outputs):
|
|
45
|
+
result: str
|
|
46
|
+
log: str
|
|
47
|
+
|
|
48
|
+
def run(self) -> Outputs:
|
|
49
|
+
return self.Outputs(result="executed", log="")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Workflow(BaseWorkflow[Inputs, BaseState]):
|
|
53
|
+
graph = CodeExecution
|
|
54
|
+
|
|
55
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
56
|
+
final_result = CodeExecution.Outputs.result
|
|
57
|
+
""",
|
|
58
|
+
"sandbox.py": """\
|
|
59
|
+
from vellum.workflows import DatasetRow
|
|
60
|
+
|
|
61
|
+
from .workflow import CodeExecution, Inputs
|
|
62
|
+
|
|
63
|
+
dataset = [
|
|
64
|
+
DatasetRow(
|
|
65
|
+
label="With mocked code node",
|
|
66
|
+
inputs=Inputs(message="hello"),
|
|
67
|
+
mocks=[
|
|
68
|
+
CodeExecution.Outputs(result="MOCKED_RESULT", log=""),
|
|
69
|
+
],
|
|
70
|
+
),
|
|
71
|
+
]
|
|
72
|
+
""",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# AND two different namespaces (simulating how the workflow server creates a new namespace per request)
|
|
76
|
+
namespace_1 = namespace_generator()
|
|
77
|
+
namespace_2 = namespace_generator()
|
|
78
|
+
|
|
79
|
+
# AND the virtual file loaders are registered for both namespaces
|
|
80
|
+
sys.meta_path.append(VirtualFileFinder(files, namespace_1))
|
|
81
|
+
sys.meta_path.append(VirtualFileFinder(files, namespace_2))
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
# WHEN we serialize the module with different namespaces (like the workflow server does)
|
|
85
|
+
result_1 = BaseWorkflowDisplay.serialize_module(namespace_1)
|
|
86
|
+
result_2 = BaseWorkflowDisplay.serialize_module(namespace_2)
|
|
87
|
+
|
|
88
|
+
# THEN both serializations should succeed without errors
|
|
89
|
+
assert len(result_1.errors) == 0, f"First serialization had errors: {result_1.errors}"
|
|
90
|
+
assert len(result_2.errors) == 0, f"Second serialization had errors: {result_2.errors}"
|
|
91
|
+
|
|
92
|
+
# AND both should have a dataset
|
|
93
|
+
assert result_1.dataset is not None, "First serialization should have a dataset"
|
|
94
|
+
assert result_2.dataset is not None, "Second serialization should have a dataset"
|
|
95
|
+
|
|
96
|
+
# AND the datasets should be identical (stable serialization)
|
|
97
|
+
assert result_1.dataset == result_2.dataset, (
|
|
98
|
+
f"Datasets should be identical across serializations.\n"
|
|
99
|
+
f"First: {result_1.dataset}\n"
|
|
100
|
+
f"Second: {result_2.dataset}"
|
|
101
|
+
)
|
|
102
|
+
finally:
|
|
103
|
+
# Clean up the virtual file finders
|
|
104
|
+
sys.meta_path = [finder for finder in sys.meta_path if not isinstance(finder, VirtualFileFinder)]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_serialize_module__dataset_with_node_output_mocks_field_name():
|
|
108
|
+
"""
|
|
109
|
+
Tests that DatasetRow with node_output_mocks field (legacy field name) properly
|
|
110
|
+
serializes the mocks. This reproduces an issue where using node_output_mocks
|
|
111
|
+
instead of mocks caused the dataset to return None for the mocks field.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
# GIVEN a workflow module with parallel code execution nodes
|
|
115
|
+
files = {
|
|
116
|
+
"__init__.py": "",
|
|
117
|
+
"inputs.py": """\
|
|
118
|
+
from typing import Any, Optional
|
|
119
|
+
|
|
120
|
+
from vellum.workflows.inputs import BaseInputs
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class Inputs(BaseInputs):
|
|
124
|
+
json: Optional[Any] = None
|
|
125
|
+
""",
|
|
126
|
+
"nodes/__init__.py": "",
|
|
127
|
+
"nodes/code_execution/__init__.py": """\
|
|
128
|
+
from vellum.workflows.nodes.displayable import CodeExecutionNode
|
|
129
|
+
from vellum.workflows.state import BaseState
|
|
130
|
+
from vellum.workflows.types.core import MergeBehavior
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class CodeExecution(CodeExecutionNode[BaseState, str]):
|
|
134
|
+
filepath = "./script.py"
|
|
135
|
+
code_inputs = {}
|
|
136
|
+
runtime = "PYTHON_3_11_6"
|
|
137
|
+
packages = []
|
|
138
|
+
|
|
139
|
+
class Trigger(CodeExecutionNode.Trigger):
|
|
140
|
+
merge_behavior = MergeBehavior.AWAIT_ATTRIBUTES
|
|
141
|
+
""",
|
|
142
|
+
"nodes/code_execution/script.py": '''\
|
|
143
|
+
"""
|
|
144
|
+
You must define a function called `main` whose arguments are named after the
|
|
145
|
+
Input Variables.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
def main() -> str:
|
|
149
|
+
return "5".upper()
|
|
150
|
+
''',
|
|
151
|
+
"nodes/code_execution_2/__init__.py": """\
|
|
152
|
+
from vellum.workflows.nodes.displayable import CodeExecutionNode
|
|
153
|
+
from vellum.workflows.state import BaseState
|
|
154
|
+
from vellum.workflows.types.core import MergeBehavior
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class CodeExecution2(CodeExecutionNode[BaseState, str]):
|
|
158
|
+
filepath = "./script.py"
|
|
159
|
+
code_inputs = {}
|
|
160
|
+
runtime = "PYTHON_3_11_6"
|
|
161
|
+
packages = []
|
|
162
|
+
|
|
163
|
+
class Trigger(CodeExecutionNode.Trigger):
|
|
164
|
+
merge_behavior = MergeBehavior.AWAIT_ATTRIBUTES
|
|
165
|
+
""",
|
|
166
|
+
"nodes/code_execution_2/script.py": '''\
|
|
167
|
+
"""
|
|
168
|
+
You must define a function called `main` whose arguments are named after the
|
|
169
|
+
Input Variables.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
def main() -> str:
|
|
173
|
+
return "TODO"
|
|
174
|
+
''',
|
|
175
|
+
"nodes/output.py": """\
|
|
176
|
+
from vellum.workflows.nodes.displayable import FinalOutputNode
|
|
177
|
+
from vellum.workflows.state import BaseState
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class Output(FinalOutputNode[BaseState, str]):
|
|
181
|
+
class Outputs(FinalOutputNode.Outputs):
|
|
182
|
+
value = None
|
|
183
|
+
""",
|
|
184
|
+
"nodes/output_2.py": """\
|
|
185
|
+
from vellum.workflows.nodes.displayable import FinalOutputNode
|
|
186
|
+
from vellum.workflows.state import BaseState
|
|
187
|
+
from vellum.workflows.types.core import MergeBehavior
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class Output2(FinalOutputNode[BaseState, str]):
|
|
191
|
+
class Outputs(FinalOutputNode.Outputs):
|
|
192
|
+
value = ""
|
|
193
|
+
|
|
194
|
+
class Trigger(FinalOutputNode.Trigger):
|
|
195
|
+
merge_behavior = MergeBehavior.AWAIT_ATTRIBUTES
|
|
196
|
+
""",
|
|
197
|
+
"sandbox.py": """\
|
|
198
|
+
from vellum.workflows.inputs import DatasetRow
|
|
199
|
+
|
|
200
|
+
from .inputs import Inputs
|
|
201
|
+
from .nodes.code_execution import CodeExecution
|
|
202
|
+
from .nodes.code_execution_2 import CodeExecution2
|
|
203
|
+
|
|
204
|
+
dataset = [
|
|
205
|
+
DatasetRow(
|
|
206
|
+
label="With mocked code nodes",
|
|
207
|
+
inputs=Inputs(json={}),
|
|
208
|
+
node_output_mocks=[
|
|
209
|
+
CodeExecution.Outputs(result="hello world", log=""),
|
|
210
|
+
CodeExecution2.Outputs(result="hello world", log=""),
|
|
211
|
+
],
|
|
212
|
+
),
|
|
213
|
+
]
|
|
214
|
+
""",
|
|
215
|
+
"workflow.py": """\
|
|
216
|
+
from vellum.workflows import BaseWorkflow
|
|
217
|
+
from vellum.workflows.state import BaseState
|
|
218
|
+
|
|
219
|
+
from .inputs import Inputs
|
|
220
|
+
from .nodes.code_execution import CodeExecution
|
|
221
|
+
from .nodes.code_execution_2 import CodeExecution2
|
|
222
|
+
from .nodes.output import Output
|
|
223
|
+
from .nodes.output_2 import Output2
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class Workflow(BaseWorkflow[Inputs, BaseState]):
|
|
227
|
+
graph = {
|
|
228
|
+
CodeExecution >> Output,
|
|
229
|
+
CodeExecution2 >> Output2,
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
233
|
+
output = Output.Outputs.value
|
|
234
|
+
output_2 = Output2.Outputs.value
|
|
235
|
+
""",
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
# AND a namespace for the virtual file loader
|
|
239
|
+
namespace = str(uuid4())
|
|
240
|
+
sys.meta_path.append(VirtualFileFinder(files, namespace))
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
# WHEN we serialize the module
|
|
244
|
+
result = BaseWorkflowDisplay.serialize_module(namespace)
|
|
245
|
+
|
|
246
|
+
# THEN the serialization should return exactly 1 deprecation error
|
|
247
|
+
assert len(result.errors) == 1, f"Expected 1 deprecation error, got {len(result.errors)}: {result.errors}"
|
|
248
|
+
|
|
249
|
+
# AND the error should prompt the user to use 'mocks' instead of 'node_output_mocks'
|
|
250
|
+
error_message = str(result.errors[0])
|
|
251
|
+
assert "node_output_mocks" in error_message, f"Error should mention 'node_output_mocks': {error_message}"
|
|
252
|
+
assert "mocks" in error_message, f"Error should mention 'mocks': {error_message}"
|
|
253
|
+
assert "deprecated" in error_message.lower(), f"Error should mention 'deprecated': {error_message}"
|
|
254
|
+
|
|
255
|
+
# AND the dataset should not be None (the deprecated field should still work)
|
|
256
|
+
assert result.dataset is not None, "Dataset should not be None"
|
|
257
|
+
|
|
258
|
+
# AND the dataset should have one row
|
|
259
|
+
assert len(result.dataset) == 1, f"Dataset should have 1 row, got {len(result.dataset)}"
|
|
260
|
+
|
|
261
|
+
# AND the row should have mocks serialized
|
|
262
|
+
row = result.dataset[0]
|
|
263
|
+
assert "mocks" in row, f"Row should have 'mocks' field, got keys: {row.keys()}"
|
|
264
|
+
assert row["mocks"] is not None, "Mocks should not be None"
|
|
265
|
+
assert len(row["mocks"]) == 2, f"Should have 2 mocks, got {len(row['mocks'])}"
|
|
266
|
+
finally:
|
|
267
|
+
# Clean up the virtual file finders
|
|
268
|
+
sys.meta_path = [finder for finder in sys.meta_path if not isinstance(finder, VirtualFileFinder)]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_serialize_module__invalid_pdf_data_url():
|
|
5
|
+
"""
|
|
6
|
+
Tests that serialization returns an error when sandbox.py has an invalid PDF data URL
|
|
7
|
+
without wrecking the whole serialize process, and that valid dataset rows are still returned.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# GIVEN a workflow module with a sandbox.py that has one invalid and one valid PDF data URL
|
|
11
|
+
module = "tests.workflows.test_sandbox_invalid_pdf_data_url"
|
|
12
|
+
|
|
13
|
+
# WHEN we serialize the module
|
|
14
|
+
result = BaseWorkflowDisplay.serialize_module(module)
|
|
15
|
+
|
|
16
|
+
# THEN the result should still contain a valid exec_config
|
|
17
|
+
assert result.exec_config is not None
|
|
18
|
+
assert isinstance(result.exec_config, dict)
|
|
19
|
+
|
|
20
|
+
# AND the result should contain exactly one error about the invalid PDF data URL
|
|
21
|
+
assert len(result.errors) == 1
|
|
22
|
+
error_message = result.errors[0].message
|
|
23
|
+
|
|
24
|
+
# AND the error message should include the dataset row label
|
|
25
|
+
assert 'Dataset row "Scenario with invalid PDF"' in error_message
|
|
26
|
+
|
|
27
|
+
# AND the error message should include the input name
|
|
28
|
+
assert 'input "document"' in error_message
|
|
29
|
+
|
|
30
|
+
# AND the error message should mention the invalid base64 encoding
|
|
31
|
+
assert "Invalid base64 encoding in PDF data URL" in error_message
|
|
32
|
+
|
|
33
|
+
# AND the result should contain both dataset rows
|
|
34
|
+
assert result.dataset is not None
|
|
35
|
+
assert len(result.dataset) == 2
|
|
36
|
+
|
|
37
|
+
# AND the first row should have the valid input (name) serialized but the invalid input (document) skipped
|
|
38
|
+
invalid_row = result.dataset[0]
|
|
39
|
+
assert invalid_row["label"] == "Scenario with invalid PDF"
|
|
40
|
+
assert "document" not in invalid_row["inputs"]
|
|
41
|
+
assert "name" in invalid_row["inputs"]
|
|
42
|
+
assert invalid_row["inputs"]["name"] == "Test User"
|
|
43
|
+
|
|
44
|
+
# AND the second row (valid) should have both inputs serialized
|
|
45
|
+
valid_row = result.dataset[1]
|
|
46
|
+
assert valid_row["label"] == "Scenario with valid PDF"
|
|
47
|
+
assert "document" in valid_row["inputs"]
|
|
48
|
+
assert "name" in valid_row["inputs"]
|
|
49
|
+
assert valid_row["inputs"]["name"] == "Another User"
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_serialize_module__invalid_mocks_reference():
|
|
5
|
+
"""
|
|
6
|
+
Tests that serialization returns an error when sandbox.py has an invalid Mocks reference on a node.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# GIVEN a workflow module with a sandbox.py that references CodeExecution.Mocks.Outputs
|
|
10
|
+
# which doesn't exist - nodes don't have a Mocks nested class
|
|
11
|
+
module = "tests.workflows.test_sandbox_invalid_mocks_reference"
|
|
12
|
+
|
|
13
|
+
# WHEN we serialize the module
|
|
14
|
+
result = BaseWorkflowDisplay.serialize_module(module)
|
|
15
|
+
|
|
16
|
+
# THEN the result should contain an error about the invalid attribute access
|
|
17
|
+
assert len(result.errors) > 0
|
|
18
|
+
|
|
19
|
+
# AND the error should be an AttributeError from trying to access a non-existent attribute
|
|
20
|
+
error_messages = [error.message for error in result.errors]
|
|
21
|
+
assert any(
|
|
22
|
+
"__annotations__" in msg or "has no attribute" in msg for msg in error_messages
|
|
23
|
+
), f"Expected attribute error in error messages, got: {error_messages}"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_serialize_module__invalid_runner_kwarg():
|
|
27
|
+
"""
|
|
28
|
+
Tests that serialization returns an error when sandbox.py uses an invalid kwarg on WorkflowSandboxRunner.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
# GIVEN a workflow module with a sandbox.py that uses 'scenarios' kwarg instead of 'dataset'
|
|
32
|
+
module = "tests.workflows.test_sandbox_invalid_runner_kwarg"
|
|
33
|
+
|
|
34
|
+
# WHEN we serialize the module
|
|
35
|
+
result = BaseWorkflowDisplay.serialize_module(module)
|
|
36
|
+
|
|
37
|
+
# THEN the result should contain an error about the invalid keyword argument
|
|
38
|
+
assert len(result.errors) > 0
|
|
39
|
+
|
|
40
|
+
# AND the error message should mention the unexpected keyword argument 'scenarios'
|
|
41
|
+
error_messages = [error.message for error in result.errors]
|
|
42
|
+
assert any(
|
|
43
|
+
"scenarios" in msg for msg in error_messages
|
|
44
|
+
), f"Expected 'scenarios' in error messages, got: {error_messages}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_serialize_module__state_mutable_default_validation():
|
|
48
|
+
"""
|
|
49
|
+
Tests that serialization returns a validation error when a state variable uses a mutable default
|
|
50
|
+
(e.g., = []) instead of Field(default_factory=list).
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
# GIVEN a workflow module with a state class that uses a mutable default (= [])
|
|
54
|
+
module = "tests.workflows.test_state_mutable_default_validation"
|
|
55
|
+
|
|
56
|
+
# WHEN we serialize the module
|
|
57
|
+
result = BaseWorkflowDisplay.serialize_module(module)
|
|
58
|
+
|
|
59
|
+
# THEN the result should contain an error about mutable defaults
|
|
60
|
+
assert len(result.errors) > 0
|
|
61
|
+
|
|
62
|
+
# AND the error message should mention mutable default, Field(default_factory=list), and the attribute name
|
|
63
|
+
error_messages = [error.message for error in result.errors]
|
|
64
|
+
assert any(
|
|
65
|
+
"Mutable default value detected" in msg and "Field(default_factory=list)" in msg and "State.chat_history" in msg
|
|
66
|
+
for msg in error_messages
|
|
67
|
+
), f"Expected mutable default error in error messages, got: {error_messages}"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_serialize_module__orphan_node_in_workflow_file():
|
|
71
|
+
"""
|
|
72
|
+
Tests that serialization returns an error when a node is defined in workflow.py
|
|
73
|
+
but not included in the workflow's graph or unused_graphs.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
# GIVEN a workflow module with a node defined in workflow.py that is not in graph or unused_graphs
|
|
77
|
+
module = "tests.workflows.test_orphan_node_serialization_error"
|
|
78
|
+
|
|
79
|
+
# WHEN we serialize the module
|
|
80
|
+
result = BaseWorkflowDisplay.serialize_module(module)
|
|
81
|
+
|
|
82
|
+
# THEN the result should contain an error about the orphan node
|
|
83
|
+
assert len(result.errors) > 0
|
|
84
|
+
|
|
85
|
+
# AND the error message should mention the orphan node and that it's not in graph or unused_graphs
|
|
86
|
+
error_messages = [error.message for error in result.errors]
|
|
87
|
+
assert any(
|
|
88
|
+
"OrphanNode" in msg and "not included in" in msg and "graph or unused_graphs" in msg for msg in error_messages
|
|
89
|
+
), f"Expected orphan node error in error messages, got: {error_messages}"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_serialize_module__orphan_node_in_nodes_directory():
|
|
93
|
+
"""
|
|
94
|
+
Tests that serialization returns an error when a node is defined in the nodes/ directory
|
|
95
|
+
but not included in the workflow's graph or unused_graphs.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
# GIVEN a workflow module with a node defined in nodes/orphan_node.py that is not in graph or unused_graphs
|
|
99
|
+
module = "tests.workflows.test_orphan_node_in_nodes_dir"
|
|
100
|
+
|
|
101
|
+
# WHEN we serialize the module
|
|
102
|
+
result = BaseWorkflowDisplay.serialize_module(module)
|
|
103
|
+
|
|
104
|
+
# THEN the result should contain an error about the orphan node
|
|
105
|
+
assert len(result.errors) > 0
|
|
106
|
+
|
|
107
|
+
# AND the error message should mention the orphan node and that it's not in graph or unused_graphs
|
|
108
|
+
error_messages = [error.message for error in result.errors]
|
|
109
|
+
assert any(
|
|
110
|
+
"OrphanNodeInNodesDir" in msg and "not included in" in msg and "graph or unused_graphs" in msg
|
|
111
|
+
for msg in error_messages
|
|
112
|
+
), f"Expected orphan node error in error messages, got: {error_messages}"
|
vellum_ee/workflows/display/tests/workflow_serialization/test_scheduled_trigger_serialization.py
CHANGED
|
@@ -244,24 +244,33 @@ def test_scheduled_trigger_serialization_full():
|
|
|
244
244
|
# THEN we get the expected trigger
|
|
245
245
|
assert len(result["triggers"]) == 1
|
|
246
246
|
trigger = result["triggers"][0]
|
|
247
|
+
|
|
248
|
+
# AND the trigger has the expected structure with attributes
|
|
249
|
+
assert trigger["id"] == "f3e5eddb-75da-42e6-9abf-d616f30c145c"
|
|
250
|
+
assert trigger["type"] == "SCHEDULED"
|
|
251
|
+
assert trigger["cron"] == "0 9 * * *"
|
|
252
|
+
assert trigger["timezone"] == "UTC"
|
|
253
|
+
|
|
254
|
+
# AND attributes are serialized (current_run_at and next_run_at from ScheduleTrigger)
|
|
255
|
+
assert "attributes" in trigger
|
|
256
|
+
attributes = trigger["attributes"]
|
|
257
|
+
assert isinstance(attributes, list)
|
|
258
|
+
assert len(attributes) == 2
|
|
259
|
+
attribute_keys = {attr["key"] for attr in attributes}
|
|
260
|
+
assert attribute_keys == {"current_run_at", "next_run_at"}
|
|
261
|
+
|
|
262
|
+
# AND display_data is serialized correctly
|
|
247
263
|
assert not DeepDiff(
|
|
248
|
-
trigger,
|
|
264
|
+
trigger["display_data"],
|
|
249
265
|
{
|
|
250
|
-
"
|
|
251
|
-
"
|
|
252
|
-
"
|
|
253
|
-
"
|
|
254
|
-
"
|
|
255
|
-
"
|
|
256
|
-
"
|
|
257
|
-
"
|
|
258
|
-
"z_index": 3,
|
|
259
|
-
"icon": "vellum:icon:calendar",
|
|
260
|
-
"color": "#4A90E2",
|
|
261
|
-
"comment": {
|
|
262
|
-
"value": "This is scheduled trigger",
|
|
263
|
-
"expanded": True,
|
|
264
|
-
},
|
|
266
|
+
"label": "Daily Schedule",
|
|
267
|
+
"position": {"x": 100.5, "y": 200.75},
|
|
268
|
+
"z_index": 3,
|
|
269
|
+
"icon": "vellum:icon:calendar",
|
|
270
|
+
"color": "#4A90E2",
|
|
271
|
+
"comment": {
|
|
272
|
+
"value": "This is scheduled trigger",
|
|
273
|
+
"expanded": True,
|
|
265
274
|
},
|
|
266
275
|
},
|
|
267
276
|
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from vellum.workflows.inputs.base import BaseInputs
|
|
2
|
+
from vellum.workflows.nodes.displayable.final_output_node import FinalOutputNode
|
|
3
|
+
from vellum.workflows.state.base import BaseState
|
|
4
|
+
from vellum.workflows.workflows.base import BaseWorkflow
|
|
5
|
+
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Inputs(BaseInputs):
|
|
9
|
+
input_value: str
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UnusedTerminalNode(FinalOutputNode):
|
|
13
|
+
class Outputs(FinalOutputNode.Outputs):
|
|
14
|
+
value = Inputs.input_value
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TerminalNodeInUnusedGraphsWorkflow(BaseWorkflow[Inputs, BaseState]):
|
|
18
|
+
unused_graphs = {UnusedTerminalNode}
|
|
19
|
+
|
|
20
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
21
|
+
result = UnusedTerminalNode.Outputs.value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_serialize_workflow__terminal_node_in_unused_graphs():
|
|
25
|
+
"""
|
|
26
|
+
Tests that a workflow with a single terminal node in unused_graphs serializes correctly.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# GIVEN a Workflow with a single terminal node in unused_graphs
|
|
30
|
+
# WHEN we serialize it
|
|
31
|
+
workflow_display = get_workflow_display(workflow_class=TerminalNodeInUnusedGraphsWorkflow)
|
|
32
|
+
serialized_workflow: dict = workflow_display.serialize()
|
|
33
|
+
|
|
34
|
+
# THEN we should get a serialized representation of the Workflow
|
|
35
|
+
assert serialized_workflow.keys() == {
|
|
36
|
+
"workflow_raw_data",
|
|
37
|
+
"input_variables",
|
|
38
|
+
"state_variables",
|
|
39
|
+
"output_variables",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# AND its output variables should contain the terminal node's output
|
|
43
|
+
output_variables = serialized_workflow["output_variables"]
|
|
44
|
+
assert len(output_variables) == 1
|
|
45
|
+
assert output_variables[0]["key"] == "result"
|
|
46
|
+
assert output_variables[0]["type"] == "STRING"
|
|
47
|
+
|
|
48
|
+
# AND its output values should reference the terminal node's output
|
|
49
|
+
workflow_raw_data = serialized_workflow["workflow_raw_data"]
|
|
50
|
+
output_values = workflow_raw_data["output_values"]
|
|
51
|
+
assert len(output_values) == 1
|
|
52
|
+
assert output_values[0]["output_variable_id"] == output_variables[0]["id"]
|
|
53
|
+
assert output_values[0]["value"]["type"] == "NODE_OUTPUT"
|
|
@@ -24,3 +24,37 @@ class InvalidInputReferenceError(UserFacingException):
|
|
|
24
24
|
self.inputs_class_name = inputs_class_name
|
|
25
25
|
self.attribute_name = attribute_name
|
|
26
26
|
super().__init__(f"Invalid input reference in {inputs_class_name}.{attribute_name}: {message}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class InvalidOutputReferenceError(UserFacingException):
|
|
30
|
+
"""Exception raised when a node references a non-existent output."""
|
|
31
|
+
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class WorkflowValidationError(UserFacingException):
|
|
36
|
+
"""Exception raised when a workflow fails validation during display serialization."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, message: str, workflow_class_name: str):
|
|
39
|
+
self.message = message
|
|
40
|
+
self.workflow_class_name = workflow_class_name
|
|
41
|
+
super().__init__(f"Workflow validation error in {workflow_class_name}: {message}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TriggerValidationError(UserFacingException):
|
|
45
|
+
"""Exception raised when a trigger fails validation during serialization."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, message: str, trigger_class_name: str):
|
|
48
|
+
self.message = message
|
|
49
|
+
self.trigger_class_name = trigger_class_name
|
|
50
|
+
super().__init__(f"Trigger validation error in {trigger_class_name}: {message}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class StateValidationError(UserFacingException):
|
|
54
|
+
"""Exception raised when a state class fails validation during serialization."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, message: str, state_class_name: str, attribute_name: str):
|
|
57
|
+
self.message = message
|
|
58
|
+
self.state_class_name = state_class_name
|
|
59
|
+
self.attribute_name = attribute_name
|
|
60
|
+
super().__init__(f"State validation error in {state_class_name}.{attribute_name}: {message}")
|