vellum-ai 1.2.2__py3-none-any.whl → 1.2.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. vellum/__init__.py +40 -0
  2. vellum/client/core/client_wrapper.py +2 -2
  3. vellum/client/core/pydantic_utilities.py +3 -2
  4. vellum/client/reference.md +16 -0
  5. vellum/client/resources/workflow_executions/client.py +28 -4
  6. vellum/client/resources/workflow_executions/raw_client.py +32 -2
  7. vellum/client/types/__init__.py +40 -0
  8. vellum/client/types/audio_input_request.py +30 -0
  9. vellum/client/types/delimiter_chunker_config.py +20 -0
  10. vellum/client/types/delimiter_chunker_config_request.py +20 -0
  11. vellum/client/types/delimiter_chunking.py +21 -0
  12. vellum/client/types/delimiter_chunking_request.py +21 -0
  13. vellum/client/types/document_index_chunking.py +4 -1
  14. vellum/client/types/document_index_chunking_request.py +2 -1
  15. vellum/client/types/document_input_request.py +30 -0
  16. vellum/client/types/execution_audio_vellum_value.py +31 -0
  17. vellum/client/types/execution_document_vellum_value.py +31 -0
  18. vellum/client/types/execution_image_vellum_value.py +31 -0
  19. vellum/client/types/execution_vellum_value.py +8 -0
  20. vellum/client/types/execution_video_vellum_value.py +31 -0
  21. vellum/client/types/image_input_request.py +30 -0
  22. vellum/client/types/logical_operator.py +1 -0
  23. vellum/client/types/node_input_compiled_audio_value.py +23 -0
  24. vellum/client/types/node_input_compiled_document_value.py +23 -0
  25. vellum/client/types/node_input_compiled_image_value.py +23 -0
  26. vellum/client/types/node_input_compiled_video_value.py +23 -0
  27. vellum/client/types/node_input_variable_compiled_value.py +8 -0
  28. vellum/client/types/prompt_deployment_input_request.py +13 -1
  29. vellum/client/types/prompt_request_audio_input.py +26 -0
  30. vellum/client/types/prompt_request_document_input.py +26 -0
  31. vellum/client/types/prompt_request_image_input.py +26 -0
  32. vellum/client/types/prompt_request_input.py +13 -1
  33. vellum/client/types/prompt_request_video_input.py +26 -0
  34. vellum/client/types/video_input_request.py +30 -0
  35. vellum/types/audio_input_request.py +3 -0
  36. vellum/types/delimiter_chunker_config.py +3 -0
  37. vellum/types/delimiter_chunker_config_request.py +3 -0
  38. vellum/types/delimiter_chunking.py +3 -0
  39. vellum/types/delimiter_chunking_request.py +3 -0
  40. vellum/types/document_input_request.py +3 -0
  41. vellum/types/execution_audio_vellum_value.py +3 -0
  42. vellum/types/execution_document_vellum_value.py +3 -0
  43. vellum/types/execution_image_vellum_value.py +3 -0
  44. vellum/types/execution_video_vellum_value.py +3 -0
  45. vellum/types/image_input_request.py +3 -0
  46. vellum/types/node_input_compiled_audio_value.py +3 -0
  47. vellum/types/node_input_compiled_document_value.py +3 -0
  48. vellum/types/node_input_compiled_image_value.py +3 -0
  49. vellum/types/node_input_compiled_video_value.py +3 -0
  50. vellum/types/prompt_request_audio_input.py +3 -0
  51. vellum/types/prompt_request_document_input.py +3 -0
  52. vellum/types/prompt_request_image_input.py +3 -0
  53. vellum/types/prompt_request_video_input.py +3 -0
  54. vellum/types/video_input_request.py +3 -0
  55. vellum/workflows/context.py +27 -9
  56. vellum/workflows/events/context.py +53 -78
  57. vellum/workflows/events/node.py +5 -5
  58. vellum/workflows/events/relational_threads.py +41 -0
  59. vellum/workflows/events/tests/test_basic_workflow.py +50 -0
  60. vellum/workflows/events/tests/test_event.py +9 -0
  61. vellum/workflows/events/types.py +3 -1
  62. vellum/workflows/events/workflow.py +12 -1
  63. vellum/workflows/expressions/contains.py +7 -0
  64. vellum/workflows/expressions/tests/test_contains.py +175 -0
  65. vellum/workflows/graph/graph.py +52 -8
  66. vellum/workflows/graph/tests/test_graph.py +17 -0
  67. vellum/workflows/integrations/mcp_service.py +35 -5
  68. vellum/workflows/integrations/tests/test_mcp_service.py +120 -0
  69. vellum/workflows/nodes/core/error_node/node.py +4 -0
  70. vellum/workflows/nodes/core/map_node/node.py +7 -0
  71. vellum/workflows/nodes/core/map_node/tests/test_node.py +19 -0
  72. vellum/workflows/nodes/core/templating_node/node.py +3 -2
  73. vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py +129 -0
  74. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +12 -0
  75. vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py +41 -0
  76. vellum/workflows/nodes/displayable/bases/utils.py +38 -1
  77. vellum/workflows/nodes/displayable/code_execution_node/utils.py +3 -20
  78. vellum/workflows/nodes/displayable/final_output_node/node.py +4 -0
  79. vellum/workflows/nodes/displayable/inline_prompt_node/node.py +3 -26
  80. vellum/workflows/nodes/displayable/prompt_deployment_node/node.py +3 -25
  81. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +1 -1
  82. vellum/workflows/nodes/utils.py +26 -1
  83. vellum/workflows/ports/node_ports.py +3 -0
  84. vellum/workflows/ports/port.py +7 -0
  85. vellum/workflows/state/context.py +35 -4
  86. vellum/workflows/types/definition.py +1 -0
  87. vellum/workflows/utils/functions.py +4 -0
  88. vellum/workflows/utils/tests/test_functions.py +6 -3
  89. vellum/workflows/utils/uuids.py +15 -0
  90. {vellum_ai-1.2.2.dist-info → vellum_ai-1.2.4.dist-info}/METADATA +1 -1
  91. {vellum_ai-1.2.2.dist-info → vellum_ai-1.2.4.dist-info}/RECORD +106 -60
  92. vellum_cli/__init__.py +6 -0
  93. vellum_cli/config.py +2 -0
  94. vellum_cli/push.py +3 -0
  95. vellum_cli/tests/test_pull.py +2 -0
  96. vellum_cli/tests/test_push.py +39 -0
  97. vellum_ee/workflows/display/nodes/vellum/error_node.py +1 -5
  98. vellum_ee/workflows/display/nodes/vellum/final_output_node.py +1 -5
  99. vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +2 -0
  100. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_mcp_serialization.py +1 -0
  101. vellum_ee/workflows/display/utils/events.py +24 -0
  102. vellum_ee/workflows/display/utils/tests/test_events.py +69 -0
  103. vellum_ee/workflows/tests/test_server.py +95 -0
  104. {vellum_ai-1.2.2.dist-info → vellum_ai-1.2.4.dist-info}/LICENSE +0 -0
  105. {vellum_ai-1.2.2.dist-info → vellum_ai-1.2.4.dist-info}/WHEEL +0 -0
  106. {vellum_ai-1.2.2.dist-info → vellum_ai-1.2.4.dist-info}/entry_points.txt +0 -0
vellum_cli/__init__.py CHANGED
@@ -68,6 +68,7 @@ with the provided module or be available for use. The Workflow Sandbox must also
68
68
  @click.option("--deployment-name", type=str, help="Unique name for the Deployment")
69
69
  @click.option("--deployment-description", type=str, help="Description for the Deployment")
70
70
  @click.option("--release-tag", help="Release Tag for the Deployment", multiple=True)
71
+ @click.option("--release-description", type=str, help="Description for the Release")
71
72
  @click.option(
72
73
  "--dry-run",
73
74
  is_flag=True,
@@ -87,6 +88,7 @@ def workflows_push(
87
88
  deployment_name: Optional[str],
88
89
  deployment_description: Optional[str],
89
90
  release_tag: Optional[List[str]],
91
+ release_description: Optional[str],
90
92
  dry_run: Optional[bool],
91
93
  strict: Optional[bool],
92
94
  workspace: Optional[str],
@@ -104,6 +106,7 @@ def workflows_push(
104
106
  deployment_name=deployment_name,
105
107
  deployment_description=deployment_description,
106
108
  release_tags=release_tag,
109
+ release_description=release_description,
107
110
  dry_run=dry_run,
108
111
  strict=strict,
109
112
  workspace=workspace,
@@ -118,6 +121,7 @@ def workflows_push(
118
121
  @click.option("--deployment-name", type=str, help="Unique name for the Deployment")
119
122
  @click.option("--deployment-description", type=str, help="Description for the Deployment")
120
123
  @click.option("--release-tag", help="Release Tag for the Deployment", multiple=True)
124
+ @click.option("--release-description", type=str, help="Description for the Release")
121
125
  @click.option(
122
126
  "--dry-run",
123
127
  is_flag=True,
@@ -137,6 +141,7 @@ def push_module(
137
141
  deployment_name: Optional[str],
138
142
  deployment_description: Optional[str],
139
143
  release_tag: Optional[List[str]],
144
+ release_description: Optional[str],
140
145
  dry_run: Optional[bool],
141
146
  strict: Optional[bool],
142
147
  workspace: Optional[str],
@@ -152,6 +157,7 @@ def push_module(
152
157
  deployment_name=deployment_name,
153
158
  deployment_description=deployment_description,
154
159
  release_tags=release_tag,
160
+ release_description=release_description,
155
161
  dry_run=dry_run,
156
162
  strict=strict,
157
163
  workspace=workspace,
vellum_cli/config.py CHANGED
@@ -36,6 +36,7 @@ class WorkflowDeploymentConfig(UniversalBaseModel):
36
36
  name: Optional[str] = None
37
37
  description: Optional[str] = None
38
38
  release_tags: Optional[List[str]] = None
39
+ release_description: Optional[str] = None
39
40
 
40
41
  def merge(self, other: "WorkflowDeploymentConfig") -> "WorkflowDeploymentConfig":
41
42
  return WorkflowDeploymentConfig(
@@ -44,6 +45,7 @@ class WorkflowDeploymentConfig(UniversalBaseModel):
44
45
  name=self.name or other.name,
45
46
  description=self.description or other.description,
46
47
  release_tags=self.release_tags or other.release_tags,
48
+ release_description=self.release_description or other.release_description,
47
49
  )
48
50
 
49
51
 
vellum_cli/push.py CHANGED
@@ -28,6 +28,7 @@ def push_command(
28
28
  deployment_name: Optional[str] = None,
29
29
  deployment_description: Optional[str] = None,
30
30
  release_tags: Optional[List[str]] = None,
31
+ release_description: Optional[str] = None,
31
32
  dry_run: Optional[bool] = None,
32
33
  strict: Optional[bool] = None,
33
34
  workspace: Optional[str] = None,
@@ -142,6 +143,7 @@ def push_command(
142
143
  name=deployment_name or cli_deployment_config.name or to_kebab_case(module_name),
143
144
  description=deployment_description or cli_deployment_config.description,
144
145
  release_tags=release_tags or cli_deployment_config.release_tags,
146
+ release_description=release_description or cli_deployment_config.release_description,
145
147
  )
146
148
  except ValidationError as e:
147
149
  for error in e.errors():
@@ -286,6 +288,7 @@ Visit at: {base_url}/workflow-sandboxes/{response.workflow_sandbox_id}"""
286
288
  name=deployment_config.name if deploy else None,
287
289
  description=deployment_config.description if deploy else None,
288
290
  release_tags=deployment_config.release_tags if deploy else None,
291
+ release_description=getattr(deployment_config, "release_description", None) if deploy else None,
289
292
  )
290
293
  workflow_config.deployments.append(stored_deployment_config)
291
294
 
@@ -393,6 +393,7 @@ def test_pull__workflow_deployment_with_no_config(vellum_client):
393
393
  "name": "my-deployment",
394
394
  "description": None,
395
395
  "release_tags": None,
396
+ "release_description": None,
396
397
  }
397
398
  ],
398
399
  "container_image_tag": None,
@@ -641,6 +642,7 @@ def test_pull__sandbox_id_with_other_workflow_deployment_in_lock(vellum_client,
641
642
  "name": None,
642
643
  "description": None,
643
644
  "release_tags": None,
645
+ "release_description": None,
644
646
  },
645
647
  ],
646
648
  "container_image_name": None,
@@ -1042,6 +1042,45 @@ def test_push__deploy_with_release_tags_success(mock_module, vellum_client):
1042
1042
  assert "Updated vellum.lock.json file." in result.output
1043
1043
 
1044
1044
 
1045
+ @pytest.mark.usefixtures("info_log_level")
1046
+ def test_push__deploy_with_release_description_success(mock_module, vellum_client):
1047
+ # GIVEN a single workflow configured
1048
+ temp_dir = mock_module.temp_dir
1049
+ module = mock_module.module
1050
+
1051
+ # AND a workflow exists in the module successfully
1052
+ _ensure_workflow_py(temp_dir, module)
1053
+
1054
+ # AND the push API call returns successfully
1055
+ workflow_deployment_id = str(uuid4())
1056
+ vellum_client.workflows.push.return_value = WorkflowPushResponse(
1057
+ workflow_sandbox_id=str(uuid4()),
1058
+ workflow_deployment_id=workflow_deployment_id,
1059
+ )
1060
+
1061
+ # WHEN calling `vellum workflows push` with --deploy and --release-description
1062
+ runner = CliRunner()
1063
+ result = runner.invoke(
1064
+ cli_main, ["workflows", "push", module, "--deploy", "--release-description", "This is a test release"]
1065
+ )
1066
+
1067
+ # THEN it should succeed
1068
+ assert result.exit_code == 0, result.output
1069
+
1070
+ # AND we should have called the push API with the correct deployment config
1071
+ vellum_client.workflows.push.assert_called_once()
1072
+ call_args = vellum_client.workflows.push.call_args.kwargs
1073
+
1074
+ # AND the deployment_config should contain the release description
1075
+ deployment_config_str = call_args["deployment_config"]
1076
+ deployment_config = json.loads(deployment_config_str)
1077
+ assert deployment_config["release_description"] == "This is a test release"
1078
+
1079
+ # AND should show success message
1080
+ assert "Successfully pushed" in result.output
1081
+ assert "Updated vellum.lock.json file." in result.output
1082
+
1083
+
1045
1084
  def test_push__deploy_stores_deployment_config_in_lock_file(mock_module, vellum_client):
1046
1085
  # GIVEN a single workflow
1047
1086
  temp_dir = mock_module.temp_dir
@@ -2,7 +2,7 @@ from uuid import UUID
2
2
  from typing import Any, ClassVar, Generic, Optional, TypeVar
3
3
 
4
4
  from vellum.workflows.nodes import ErrorNode
5
- from vellum.workflows.types.core import JsonArray, JsonObject
5
+ from vellum.workflows.types.core import JsonObject
6
6
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
7
7
  from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
8
8
  from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input
@@ -20,10 +20,6 @@ class BaseErrorNodeDisplay(BaseNodeDisplay[_ErrorNodeType], Generic[_ErrorNodeTy
20
20
 
21
21
  __serializable_inputs__ = {ErrorNode.error}
22
22
 
23
- def serialize_ports(self, display_context: "WorkflowDisplayContext") -> JsonArray:
24
- """Error nodes have no ports."""
25
- return []
26
-
27
23
  def serialize(self, display_context: WorkflowDisplayContext, **_kwargs) -> JsonObject:
28
24
  node_id = self.node_id
29
25
  error_source_input_id = self.node_input_ids_by_name.get(
@@ -2,7 +2,7 @@ from uuid import UUID
2
2
  from typing import ClassVar, Generic, Optional, TypeVar
3
3
 
4
4
  from vellum.workflows.nodes.displayable.final_output_node import FinalOutputNode
5
- from vellum.workflows.types.core import JsonArray, JsonObject
5
+ from vellum.workflows.types.core import JsonObject
6
6
  from vellum.workflows.utils.uuids import uuid4_from_hash
7
7
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
8
8
  from vellum_ee.workflows.display.nodes.utils import to_kebab_case
@@ -19,10 +19,6 @@ NODE_INPUT_KEY = "node_input"
19
19
  class BaseFinalOutputNodeDisplay(BaseNodeDisplay[_FinalOutputNodeType], Generic[_FinalOutputNodeType]):
20
20
  output_name: ClassVar[Optional[str]] = None
21
21
 
22
- def serialize_ports(self, display_context: "WorkflowDisplayContext") -> JsonArray:
23
- """Final output nodes have no ports."""
24
- return []
25
-
26
22
  def serialize(self, display_context: WorkflowDisplayContext, **_kwargs) -> JsonObject:
27
23
  node = self._node
28
24
  node_id = self.node_id
@@ -191,6 +191,7 @@ def test_serialize_node__tool_calling_node__mcp_server_api_key():
191
191
  {
192
192
  "type": "MCP_SERVER",
193
193
  "name": "my-mcp-server",
194
+ "description": "",
194
195
  "url": "https://my-mcp-server.com",
195
196
  "authorization_type": "API_KEY",
196
197
  "bearer_token_value": None,
@@ -243,6 +244,7 @@ def test_serialize_node__tool_calling_node__mcp_server_no_authorization():
243
244
  {
244
245
  "type": "MCP_SERVER",
245
246
  "name": "my-mcp-server",
247
+ "description": "",
246
248
  "url": "https://my-mcp-server.com",
247
249
  "authorization_type": None,
248
250
  "bearer_token_value": None,
@@ -52,6 +52,7 @@ def test_serialize_workflow():
52
52
  {
53
53
  "type": "MCP_SERVER",
54
54
  "name": "github",
55
+ "description": "",
55
56
  "url": "https://api.githubcopilot.com/mcp/",
56
57
  "authorization_type": "BEARER_TOKEN",
57
58
  "bearer_token_value": "GITHUB_PERSONAL_ACCESS_TOKEN",
@@ -0,0 +1,24 @@
1
+ from vellum.workflows.events.workflow import WorkflowExecutionInitiatedEvent
2
+ from vellum_ee.workflows.display.utils.registry import (
3
+ get_parent_display_context_from_event,
4
+ register_workflow_display_class,
5
+ register_workflow_display_context,
6
+ )
7
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
8
+
9
+
10
+ def event_enricher(event: WorkflowExecutionInitiatedEvent) -> WorkflowExecutionInitiatedEvent:
11
+ workflow_definition = event.body.workflow_definition
12
+ workflow_display = get_workflow_display(
13
+ workflow_class=workflow_definition,
14
+ parent_display_context=get_parent_display_context_from_event(event),
15
+ dry_run=True,
16
+ )
17
+ register_workflow_display_context(event.span_id, workflow_display.display_context)
18
+
19
+ if event.body.workflow_definition.is_dynamic:
20
+ register_workflow_display_class(workflow_definition, workflow_display.__class__)
21
+ workflow_version_exec_config = workflow_display.serialize()
22
+ setattr(event.body, "workflow_version_exec_config", workflow_version_exec_config)
23
+
24
+ return event
@@ -0,0 +1,69 @@
1
+ import pytest
2
+ from uuid import uuid4
3
+ from typing import Optional
4
+
5
+ from vellum.workflows.events.workflow import WorkflowExecutionInitiatedBody, WorkflowExecutionInitiatedEvent
6
+ from vellum.workflows.inputs.base import BaseInputs
7
+ from vellum.workflows.workflows.base import BaseWorkflow
8
+ from vellum_ee.workflows.display.utils.events import event_enricher
9
+
10
+
11
+ @pytest.mark.parametrize(
12
+ "is_dynamic,expected_config",
13
+ [
14
+ (False, None),
15
+ (
16
+ True,
17
+ {
18
+ "workflow_raw_data": {
19
+ "nodes": [
20
+ {
21
+ "id": "86607b18-7872-49f3-a592-fda1428f70aa",
22
+ "type": "ENTRYPOINT",
23
+ "inputs": [],
24
+ "data": {
25
+ "label": "Entrypoint Node",
26
+ "source_handle_id": "d1fe8f4c-53d7-43a0-b210-73ebdc60bf57",
27
+ },
28
+ "display_data": {"position": {"x": 0.0, "y": -50.0}},
29
+ "base": None,
30
+ "definition": None,
31
+ }
32
+ ],
33
+ "edges": [],
34
+ "display_data": {"viewport": {"x": 0.0, "y": 0.0, "zoom": 1.0}},
35
+ "definition": {
36
+ "name": "TestWorkflow",
37
+ "module": ["vellum_ee", "workflows", "display", "utils", "tests", "test_events"],
38
+ },
39
+ "output_values": [],
40
+ },
41
+ "input_variables": [],
42
+ "state_variables": [],
43
+ "output_variables": [],
44
+ },
45
+ ),
46
+ ],
47
+ )
48
+ def test_event_enricher_static_workflow(is_dynamic: bool, expected_config: Optional[dict]):
49
+ """Test event_enricher with a static workflow (is_dynamic=False)."""
50
+ # GIVEN a workflow class with the specified is_dynamic value
51
+ _is_dynamic = is_dynamic
52
+
53
+ class TestWorkflow(BaseWorkflow):
54
+ is_dynamic = _is_dynamic
55
+
56
+ event: WorkflowExecutionInitiatedEvent = WorkflowExecutionInitiatedEvent(
57
+ trace_id=uuid4(),
58
+ span_id=uuid4(),
59
+ body=WorkflowExecutionInitiatedBody(
60
+ workflow_definition=TestWorkflow,
61
+ inputs=BaseInputs(),
62
+ ),
63
+ )
64
+
65
+ # WHEN the event_enricher is called with mocked dependencies
66
+ event_enricher(event)
67
+
68
+ # AND workflow_version_exec_config is set to the expected config
69
+ assert event.body.workflow_version_exec_config == expected_config
@@ -8,6 +8,7 @@ from vellum.client.types.number_vellum_value import NumberVellumValue
8
8
  from vellum.workflows import BaseWorkflow
9
9
  from vellum.workflows.nodes import BaseNode
10
10
  from vellum.workflows.state.context import WorkflowContext
11
+ from vellum.workflows.utils.uuids import generate_workflow_deployment_prefix
11
12
  from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
12
13
  from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
13
14
 
@@ -566,3 +567,97 @@ class Workflow(BaseWorkflow[Inputs, BaseState]):
566
567
 
567
568
  # THEN the serialization should complete successfully
568
569
  assert result is not None
570
+
571
+
572
+ def test_resolve_workflow_deployment__returns_workflow_with_generated_files():
573
+ """
574
+ Test that resolve_workflow_deployment returns a workflow with artifacts
575
+ in generated_files using the correct prefix format.
576
+ """
577
+ # GIVEN a deployment name and release tag
578
+ deployment_name = "test_deployment"
579
+ release_tag = "LATEST"
580
+
581
+ expected_prefix = generate_workflow_deployment_prefix(deployment_name, release_tag)
582
+
583
+ # Create a simple test node for the resolved workflow
584
+ test_node_code = """
585
+ from vellum.workflows.nodes.bases.base import BaseNode
586
+ from vellum.workflows.outputs import BaseOutputs
587
+
588
+ class TestNode(BaseNode):
589
+ template = "Hello"
590
+
591
+ class Outputs(BaseOutputs):
592
+ result: str
593
+
594
+ def run(self):
595
+ return self.Outputs(result="Hello, {template}")
596
+ """
597
+
598
+ mock_workflow_code = """
599
+ from vellum.workflows import BaseWorkflow
600
+ from .nodes.test_node import TestNode
601
+
602
+ class ResolvedWorkflow(BaseWorkflow):
603
+ graph = TestNode
604
+ """
605
+
606
+ # Create parent workflow files that reference the subworkflow deployment
607
+ parent_workflow_code = """
608
+ from vellum.workflows import BaseWorkflow
609
+ from .nodes.subworkflow_deployment_node import TestSubworkflowDeploymentNode
610
+
611
+ class ParentWorkflow(BaseWorkflow):
612
+ graph = TestSubworkflowDeploymentNode
613
+ """
614
+
615
+ parent_node_code = """
616
+ from vellum.workflows.nodes import SubworkflowDeploymentNode
617
+ from vellum.workflows.outputs import BaseOutputs
618
+
619
+ class TestSubworkflowDeploymentNode(SubworkflowDeploymentNode):
620
+ deployment = "test_deployment"
621
+
622
+ class Outputs(BaseOutputs):
623
+ result: str
624
+
625
+ subworkflow_inputs = {"message": "test"}
626
+ """
627
+
628
+ files = {
629
+ "__init__.py": "",
630
+ "workflow.py": parent_workflow_code,
631
+ "nodes/__init__.py": """
632
+ from .subworkflow_deployment_node import TestSubworkflowDeploymentNode
633
+
634
+ __all__ = ["TestSubworkflowDeploymentNode"]
635
+ """,
636
+ "nodes/subworkflow_deployment_node.py": parent_node_code,
637
+ f"{expected_prefix}/__init__.py": "",
638
+ f"{expected_prefix}/workflow.py": mock_workflow_code,
639
+ f"{expected_prefix}/nodes/__init__.py": """
640
+ from .test_node import TestNode
641
+
642
+ __all__ = ["TestNode"]
643
+ """,
644
+ f"{expected_prefix}/nodes/test_node.py": test_node_code,
645
+ }
646
+
647
+ namespace = str(uuid4())
648
+
649
+ # AND the virtual file loader is registered
650
+ finder = VirtualFileFinder(files, namespace)
651
+ sys.meta_path.append(finder)
652
+
653
+ # WHEN we execute the root workflow
654
+ Workflow = BaseWorkflow.load_from_module(namespace)
655
+ workflow = Workflow(context=WorkflowContext(generated_files=files, namespace=namespace))
656
+
657
+ # THEN the workflow should be successfully initialized
658
+ assert workflow
659
+
660
+ event = workflow.run()
661
+
662
+ # AND the method should return a workflow (not None) - this will pass once implemented
663
+ assert event.name == "workflow.execution.fulfilled"