vellum-ai 0.14.6__py3-none-any.whl → 0.14.8__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.
- vellum/__init__.py +14 -0
- vellum/client/core/client_wrapper.py +1 -1
- vellum/client/types/__init__.py +14 -0
- vellum/client/types/array_chat_message_content_item.py +6 -1
- vellum/client/types/array_chat_message_content_item_request.py +2 -0
- vellum/client/types/chat_message_content.py +2 -0
- vellum/client/types/chat_message_content_request.py +2 -0
- vellum/client/types/document_chat_message_content.py +25 -0
- vellum/client/types/document_chat_message_content_request.py +25 -0
- vellum/client/types/document_prompt_block.py +29 -0
- vellum/client/types/document_vellum_value.py +25 -0
- vellum/client/types/document_vellum_value_request.py +25 -0
- vellum/client/types/prompt_block.py +2 -0
- vellum/client/types/vellum_document.py +20 -0
- vellum/client/types/vellum_document_request.py +20 -0
- vellum/client/types/vellum_value.py +2 -0
- vellum/client/types/vellum_value_request.py +2 -0
- vellum/client/types/vellum_variable_type.py +1 -0
- vellum/types/document_chat_message_content.py +3 -0
- vellum/types/document_chat_message_content_request.py +3 -0
- vellum/types/document_prompt_block.py +3 -0
- vellum/types/document_vellum_value.py +3 -0
- vellum/types/document_vellum_value_request.py +3 -0
- vellum/types/vellum_document.py +3 -0
- vellum/types/vellum_document_request.py +3 -0
- vellum/workflows/exceptions.py +18 -0
- vellum/workflows/inputs/base.py +27 -1
- vellum/workflows/inputs/tests/__init__.py +0 -0
- vellum/workflows/inputs/tests/test_inputs.py +49 -0
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py +1 -1
- vellum/workflows/nodes/core/map_node/node.py +7 -7
- vellum/workflows/nodes/core/try_node/node.py +1 -1
- vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +33 -0
- vellum/workflows/nodes/displayable/bases/api_node/node.py +1 -1
- vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +2 -2
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +5 -3
- vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +5 -4
- vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py +4 -4
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +39 -15
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/tests/test_node.py +142 -0
- vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +3 -1
- vellum/workflows/outputs/base.py +1 -1
- vellum/workflows/runner/runner.py +16 -10
- vellum/workflows/state/context.py +7 -7
- vellum/workflows/workflows/base.py +16 -5
- vellum/workflows/workflows/tests/test_base_workflow.py +131 -40
- {vellum_ai-0.14.6.dist-info → vellum_ai-0.14.8.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.6.dist-info → vellum_ai-0.14.8.dist-info}/RECORD +65 -47
- vellum_cli/__init__.py +43 -0
- vellum_cli/config.py +1 -0
- vellum_cli/init.py +132 -0
- vellum_cli/pull.py +7 -3
- vellum_cli/tests/test_init.py +473 -0
- vellum_cli/tests/test_pull.py +135 -0
- vellum_cli/tests/test_push.py +1 -0
- vellum_ee/workflows/display/nodes/base_node_display.py +4 -4
- vellum_ee/workflows/display/tests/test_vellum_workflow_display.py +83 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +118 -3
- vellum_ee/workflows/display/vellum.py +0 -4
- vellum_ee/workflows/display/workflows/base_workflow_display.py +46 -13
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +29 -0
- vellum_ee/workflows/display/workflows/vellum_workflow_display.py +12 -0
- {vellum_ai-0.14.6.dist-info → vellum_ai-0.14.8.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.6.dist-info → vellum_ai-0.14.8.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.6.dist-info → vellum_ai-0.14.8.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,473 @@
|
|
1
|
+
import io
|
2
|
+
import json
|
3
|
+
import os
|
4
|
+
from unittest.mock import patch
|
5
|
+
import zipfile
|
6
|
+
|
7
|
+
from click.testing import CliRunner
|
8
|
+
from pydash import snake_case
|
9
|
+
|
10
|
+
from vellum_cli import main as cli_main
|
11
|
+
|
12
|
+
|
13
|
+
def _zip_file_map(file_map: dict[str, str]) -> bytes:
|
14
|
+
# Create an in-memory bytes buffer to store the zip
|
15
|
+
zip_buffer = io.BytesIO()
|
16
|
+
|
17
|
+
# Create zip file and add files from file_map
|
18
|
+
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
19
|
+
for filename, content in file_map.items():
|
20
|
+
zip_file.writestr(filename, content)
|
21
|
+
|
22
|
+
# Get the bytes from the buffer
|
23
|
+
zip_bytes = zip_buffer.getvalue()
|
24
|
+
zip_buffer.close()
|
25
|
+
|
26
|
+
return zip_bytes
|
27
|
+
|
28
|
+
|
29
|
+
class MockTemplate:
|
30
|
+
def __init__(self, id, label):
|
31
|
+
self.id = id
|
32
|
+
self.label = label
|
33
|
+
|
34
|
+
|
35
|
+
def test_init_command(vellum_client, mock_module):
|
36
|
+
# GIVEN a module on the user's filesystem
|
37
|
+
temp_dir = mock_module.temp_dir
|
38
|
+
mock_module.set_pyproject_toml({"workflows": []})
|
39
|
+
# GIVEN the vellum client returns a list of template workflows
|
40
|
+
fake_templates = [
|
41
|
+
MockTemplate(id="template-1", label="Example Workflow"),
|
42
|
+
MockTemplate(id="template-2", label="Another Workflow"),
|
43
|
+
]
|
44
|
+
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
45
|
+
|
46
|
+
# AND the workflow pull API call returns a zip file
|
47
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
48
|
+
|
49
|
+
# WHEN the user runs the `init` command and selects the first template
|
50
|
+
runner = CliRunner()
|
51
|
+
result = runner.invoke(cli_main, ["workflows", "init"], input="1\n")
|
52
|
+
|
53
|
+
# THEN the command returns successfully
|
54
|
+
assert result.exit_code == 0
|
55
|
+
|
56
|
+
# AND `vellum_client.workflows.pull` is called with the selected template ID
|
57
|
+
vellum_client.workflows.pull.assert_called_once_with(
|
58
|
+
"template-1",
|
59
|
+
request_options={"additional_query_parameters": {"include_sandbox": True}},
|
60
|
+
)
|
61
|
+
|
62
|
+
# AND the `workflow.py` file should be created in the correct module directory
|
63
|
+
workflow_py = os.path.join(temp_dir, "example_workflow", "workflow.py")
|
64
|
+
assert os.path.exists(workflow_py)
|
65
|
+
with open(workflow_py) as f:
|
66
|
+
assert f.read() == "print('hello')"
|
67
|
+
|
68
|
+
# AND the vellum.lock.json file should be created
|
69
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
70
|
+
assert os.path.exists(vellum_lock_json)
|
71
|
+
with open(vellum_lock_json) as f:
|
72
|
+
lock_data = json.load(f)
|
73
|
+
assert lock_data["workflows"] == [
|
74
|
+
{
|
75
|
+
"module": "example_workflow",
|
76
|
+
"workflow_sandbox_id": "template-1",
|
77
|
+
"ignore": None,
|
78
|
+
"deployments": [],
|
79
|
+
"container_image_name": None,
|
80
|
+
"container_image_tag": None,
|
81
|
+
"workspace": "default",
|
82
|
+
"target_directory": None,
|
83
|
+
}
|
84
|
+
]
|
85
|
+
|
86
|
+
|
87
|
+
def test_init_command__invalid_template_id(vellum_client, mock_module):
|
88
|
+
# GIVEN a module on the user's filesystem
|
89
|
+
temp_dir = mock_module.temp_dir
|
90
|
+
mock_module.set_pyproject_toml({"workflows": []})
|
91
|
+
# GIVEN the vellum client returns a list of template workflows
|
92
|
+
fake_templates = [
|
93
|
+
MockTemplate(id="template-1", label="Example Workflow"),
|
94
|
+
MockTemplate(id="template-2", label="Another Workflow"),
|
95
|
+
]
|
96
|
+
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
97
|
+
|
98
|
+
# WHEN the user runs the `init` command, enters invalid input and then cancels
|
99
|
+
runner = CliRunner()
|
100
|
+
# Mock click.prompt to raise a KeyboardInterrupt (simulating Ctrl+C)
|
101
|
+
with patch("click.prompt", side_effect=KeyboardInterrupt):
|
102
|
+
runner = CliRunner()
|
103
|
+
result = runner.invoke(cli_main, ["workflows", "init"])
|
104
|
+
|
105
|
+
# THEN the command is aborted
|
106
|
+
assert result.exit_code != 0
|
107
|
+
assert "Aborted!" in result.output # Click shows this message on Ctrl+C
|
108
|
+
|
109
|
+
# AND `vellum_client.workflows.pull` is not called
|
110
|
+
vellum_client.workflows.pull.assert_not_called()
|
111
|
+
|
112
|
+
# AND no workflow files are created
|
113
|
+
workflow_py = os.path.join(temp_dir, "example_workflow", "workflow.py")
|
114
|
+
assert not os.path.exists(workflow_py)
|
115
|
+
|
116
|
+
# AND the lock file remains empty
|
117
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
118
|
+
if os.path.exists(vellum_lock_json):
|
119
|
+
with open(vellum_lock_json) as f:
|
120
|
+
lock_data = json.load(f)
|
121
|
+
assert lock_data["workflows"] == []
|
122
|
+
|
123
|
+
|
124
|
+
def test_init_command__no_templates(vellum_client, mock_module):
|
125
|
+
# GIVEN a module on the user's filesystem
|
126
|
+
temp_dir = mock_module.temp_dir
|
127
|
+
mock_module.set_pyproject_toml({"workflows": []})
|
128
|
+
# GIVEN the vellum client returns no template workflows
|
129
|
+
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = []
|
130
|
+
|
131
|
+
# WHEN the user runs the `init` command
|
132
|
+
runner = CliRunner()
|
133
|
+
result = runner.invoke(cli_main, ["workflows", "init"])
|
134
|
+
|
135
|
+
# THEN the command gracefully exits
|
136
|
+
assert result.exit_code == 0
|
137
|
+
assert "No templates available" in result.output
|
138
|
+
|
139
|
+
# AND `vellum_client.workflows.pull` is not called
|
140
|
+
vellum_client.workflows.pull.assert_not_called()
|
141
|
+
|
142
|
+
# AND no workflow files are created
|
143
|
+
workflow_py = os.path.join(temp_dir, "example_workflow", "workflow.py")
|
144
|
+
assert not os.path.exists(workflow_py)
|
145
|
+
|
146
|
+
# AND the lock file remains empty
|
147
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
148
|
+
if os.path.exists(vellum_lock_json):
|
149
|
+
with open(vellum_lock_json) as f:
|
150
|
+
lock_data = json.load(f)
|
151
|
+
assert lock_data["workflows"] == []
|
152
|
+
|
153
|
+
|
154
|
+
def test_init_command_target_directory_exists(vellum_client, mock_module):
|
155
|
+
"""
|
156
|
+
GIVEN a target directory already exists
|
157
|
+
WHEN the user tries to run the `init` command
|
158
|
+
THEN the command should fail and exit without modifying existing files
|
159
|
+
"""
|
160
|
+
temp_dir = mock_module.temp_dir
|
161
|
+
existing_workflow_dir = os.path.join(temp_dir, "example_workflow")
|
162
|
+
|
163
|
+
# Create the target directory to simulate it already existing
|
164
|
+
os.makedirs(existing_workflow_dir, exist_ok=True)
|
165
|
+
|
166
|
+
# Ensure directory exists before command execution
|
167
|
+
assert os.path.exists(existing_workflow_dir)
|
168
|
+
|
169
|
+
# GIVEN the vellum client returns a list of template workflows
|
170
|
+
fake_templates = [
|
171
|
+
MockTemplate(id="template-1", label="Example Workflow"),
|
172
|
+
]
|
173
|
+
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
174
|
+
|
175
|
+
# AND the workflow pull API call returns a zip file
|
176
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
177
|
+
|
178
|
+
# WHEN the user runs the `init` command and selects the template
|
179
|
+
runner = CliRunner()
|
180
|
+
result = runner.invoke(cli_main, ["workflows", "init"], input="1\n")
|
181
|
+
|
182
|
+
# THEN the command should detect the existing directory and abort
|
183
|
+
assert result.exit_code == 0
|
184
|
+
assert f"{existing_workflow_dir} already exists." in result.output
|
185
|
+
|
186
|
+
# Ensure the directory still exists (wasn't deleted or modified)
|
187
|
+
assert os.path.exists(existing_workflow_dir)
|
188
|
+
|
189
|
+
# AND `vellum_client.workflows.pull` is not called
|
190
|
+
vellum_client.workflows.pull.assert_not_called()
|
191
|
+
|
192
|
+
# AND no workflow files are created
|
193
|
+
workflow_py = os.path.join(temp_dir, "example_workflow", "workflow.py")
|
194
|
+
assert not os.path.exists(workflow_py)
|
195
|
+
|
196
|
+
# AND the lock file remains empty
|
197
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
198
|
+
if os.path.exists(vellum_lock_json):
|
199
|
+
with open(vellum_lock_json) as f:
|
200
|
+
lock_data = json.load(f)
|
201
|
+
assert lock_data["workflows"] == []
|
202
|
+
|
203
|
+
|
204
|
+
def test_init_command_with_template_name(vellum_client, mock_module):
|
205
|
+
# GIVEN a module on the user's filesystem
|
206
|
+
temp_dir = mock_module.temp_dir
|
207
|
+
mock_module.set_pyproject_toml({"workflows": []})
|
208
|
+
|
209
|
+
# GIVEN the vellum client returns a list of template workflows
|
210
|
+
fake_templates = [
|
211
|
+
MockTemplate(id="template-1", label="Example Workflow"),
|
212
|
+
MockTemplate(id="template-2", label="Another Workflow"),
|
213
|
+
]
|
214
|
+
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
215
|
+
|
216
|
+
# AND the workflow pull API call returns a zip file
|
217
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
218
|
+
|
219
|
+
# WHEN the user runs the `init` command with a specific template name
|
220
|
+
template_name = snake_case("Another Workflow")
|
221
|
+
runner = CliRunner()
|
222
|
+
result = runner.invoke(cli_main, ["workflows", "init", template_name])
|
223
|
+
|
224
|
+
# THEN the command returns successfully
|
225
|
+
assert result.exit_code == 0
|
226
|
+
|
227
|
+
# AND `vellum_client.workflows.pull` is called with the correct template ID
|
228
|
+
vellum_client.workflows.pull.assert_called_once_with(
|
229
|
+
"template-2", # ID of "Another Workflow"
|
230
|
+
request_options={"additional_query_parameters": {"include_sandbox": True}},
|
231
|
+
)
|
232
|
+
|
233
|
+
# AND the workflow files should be created in the correct module directory
|
234
|
+
workflow_py = os.path.join(temp_dir, "another_workflow", "workflow.py")
|
235
|
+
|
236
|
+
assert os.path.exists(workflow_py)
|
237
|
+
|
238
|
+
with open(workflow_py) as f:
|
239
|
+
assert f.read() == "print('hello')"
|
240
|
+
|
241
|
+
# AND the vellum.lock.json file should be created with the correct data
|
242
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
243
|
+
assert os.path.exists(vellum_lock_json)
|
244
|
+
|
245
|
+
with open(vellum_lock_json) as f:
|
246
|
+
lock_data = json.load(f)
|
247
|
+
assert lock_data["workflows"] == [
|
248
|
+
{
|
249
|
+
"module": "another_workflow",
|
250
|
+
"workflow_sandbox_id": "template-2",
|
251
|
+
"ignore": None,
|
252
|
+
"deployments": [],
|
253
|
+
"container_image_name": None,
|
254
|
+
"container_image_tag": None,
|
255
|
+
"workspace": "default",
|
256
|
+
"target_directory": None,
|
257
|
+
}
|
258
|
+
]
|
259
|
+
|
260
|
+
|
261
|
+
def test_init_command_with_nonexistent_template_name(vellum_client, mock_module):
|
262
|
+
# GIVEN a module on the user's filesystem
|
263
|
+
temp_dir = mock_module.temp_dir
|
264
|
+
mock_module.set_pyproject_toml({"workflows": []})
|
265
|
+
|
266
|
+
# GIVEN the vellum client returns a list of template workflows
|
267
|
+
fake_templates = [
|
268
|
+
MockTemplate(id="template-1", label="Example Workflow"),
|
269
|
+
MockTemplate(id="template-2", label="Another Workflow"),
|
270
|
+
]
|
271
|
+
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
272
|
+
|
273
|
+
# WHEN the user runs the `init` command with a non-existent template name
|
274
|
+
nonexistent_template = "nonexistent_template"
|
275
|
+
runner = CliRunner()
|
276
|
+
result = runner.invoke(cli_main, ["workflows", "init", nonexistent_template])
|
277
|
+
|
278
|
+
# THEN the command should indicate the template was not found
|
279
|
+
assert result.exit_code == 0
|
280
|
+
assert f"Template {nonexistent_template} not found" in result.output
|
281
|
+
|
282
|
+
# AND `vellum_client.workflows.pull` is not called
|
283
|
+
vellum_client.workflows.pull.assert_not_called()
|
284
|
+
|
285
|
+
# AND no workflow files are created
|
286
|
+
example_workflow_dir = os.path.join(temp_dir, "example_workflow")
|
287
|
+
nonexistent_workflow_dir = os.path.join(temp_dir, nonexistent_template)
|
288
|
+
|
289
|
+
assert not os.path.exists(example_workflow_dir)
|
290
|
+
assert not os.path.exists(nonexistent_workflow_dir)
|
291
|
+
|
292
|
+
# AND the lock file remains empty
|
293
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
294
|
+
if os.path.exists(vellum_lock_json):
|
295
|
+
with open(vellum_lock_json) as f:
|
296
|
+
lock_data = json.load(f)
|
297
|
+
assert lock_data["workflows"] == []
|
298
|
+
|
299
|
+
|
300
|
+
def test_init__with_target_dir(vellum_client, mock_module):
|
301
|
+
# GIVEN a module on the user's filesystem
|
302
|
+
temp_dir = mock_module.temp_dir
|
303
|
+
mock_module.set_pyproject_toml({"workflows": []})
|
304
|
+
|
305
|
+
# GIVEN the vellum client returns a list of template workflows
|
306
|
+
fake_templates = [
|
307
|
+
MockTemplate(id="template-1", label="Example Workflow"),
|
308
|
+
]
|
309
|
+
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
310
|
+
|
311
|
+
# AND the workflow pull API call returns a zip file
|
312
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
313
|
+
|
314
|
+
# AND a target directory
|
315
|
+
target_dir = os.path.join(temp_dir, "dir")
|
316
|
+
os.makedirs(target_dir, exist_ok=True)
|
317
|
+
|
318
|
+
# WHEN the user runs the init command with target-dir
|
319
|
+
runner = CliRunner()
|
320
|
+
result = runner.invoke(cli_main, ["workflows", "init", "--target-dir", target_dir], input="1\n")
|
321
|
+
|
322
|
+
# THEN the command returns successfully
|
323
|
+
assert result.exit_code == 0
|
324
|
+
|
325
|
+
# AND the `workflow.py` file should be created in the target directory
|
326
|
+
module_path = os.path.join(target_dir, "example_workflow")
|
327
|
+
workflow_py = os.path.join(module_path, "workflow.py")
|
328
|
+
assert os.path.exists(workflow_py)
|
329
|
+
with open(workflow_py) as f:
|
330
|
+
assert f.read() == "print('hello')"
|
331
|
+
|
332
|
+
# AND the files are not in the default module directory
|
333
|
+
default_module_path = os.path.join(temp_dir, "example_workflow", "workflow.py")
|
334
|
+
assert not os.path.exists(default_module_path)
|
335
|
+
|
336
|
+
# AND the vellum.lock.json file should be created in the original directory
|
337
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
338
|
+
assert os.path.exists(vellum_lock_json)
|
339
|
+
with open(vellum_lock_json) as f:
|
340
|
+
lock_data = json.load(f)
|
341
|
+
assert lock_data["workflows"] == [
|
342
|
+
{
|
343
|
+
"module": "example_workflow",
|
344
|
+
"workflow_sandbox_id": "template-1",
|
345
|
+
"ignore": None,
|
346
|
+
"deployments": [],
|
347
|
+
"container_image_name": None,
|
348
|
+
"container_image_tag": None,
|
349
|
+
"workspace": "default",
|
350
|
+
"target_directory": module_path,
|
351
|
+
}
|
352
|
+
]
|
353
|
+
|
354
|
+
|
355
|
+
def test_init__with_nested_target_dir(vellum_client, mock_module):
|
356
|
+
# GIVEN a module on the user's filesystem
|
357
|
+
temp_dir = mock_module.temp_dir
|
358
|
+
mock_module.set_pyproject_toml({"workflows": []})
|
359
|
+
|
360
|
+
# GIVEN the vellum client returns a list of template workflows
|
361
|
+
fake_templates = [
|
362
|
+
MockTemplate(id="template-1", label="Example Workflow"),
|
363
|
+
]
|
364
|
+
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
365
|
+
|
366
|
+
# AND the workflow pull API call returns a zip file
|
367
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
368
|
+
|
369
|
+
# AND a nested target directory that doesn't exist yet
|
370
|
+
nested_target_dir = os.path.join(temp_dir, "dir-1", "dir-2")
|
371
|
+
|
372
|
+
# WHEN the user runs the init command with nested target-dir
|
373
|
+
runner = CliRunner()
|
374
|
+
result = runner.invoke(cli_main, ["workflows", "init", "--target-dir", nested_target_dir], input="1\n")
|
375
|
+
|
376
|
+
# THEN the command returns successfully
|
377
|
+
assert result.exit_code == 0
|
378
|
+
|
379
|
+
# AND the nested directory with module subdirectory should be created
|
380
|
+
module_path = os.path.join(nested_target_dir, "example_workflow")
|
381
|
+
assert os.path.exists(module_path)
|
382
|
+
|
383
|
+
# AND the workflow.py file is written to the nested target directory
|
384
|
+
workflow_py = os.path.join(module_path, "workflow.py")
|
385
|
+
assert os.path.exists(workflow_py)
|
386
|
+
with open(workflow_py) as f:
|
387
|
+
assert f.read() == "print('hello')"
|
388
|
+
|
389
|
+
# AND the files are not in the default module directory
|
390
|
+
default_module_path = os.path.join(temp_dir, "example_workflow", "workflow.py")
|
391
|
+
assert not os.path.exists(default_module_path)
|
392
|
+
|
393
|
+
# AND the vellum.lock.json file is still updated
|
394
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
395
|
+
assert os.path.exists(vellum_lock_json)
|
396
|
+
with open(vellum_lock_json) as f:
|
397
|
+
lock_data = json.load(f)
|
398
|
+
assert lock_data["workflows"] == [
|
399
|
+
{
|
400
|
+
"module": "example_workflow",
|
401
|
+
"workflow_sandbox_id": "template-1",
|
402
|
+
"ignore": None,
|
403
|
+
"deployments": [],
|
404
|
+
"container_image_name": None,
|
405
|
+
"container_image_tag": None,
|
406
|
+
"workspace": "default",
|
407
|
+
"target_directory": module_path,
|
408
|
+
}
|
409
|
+
]
|
410
|
+
|
411
|
+
|
412
|
+
def test_init__with_template_name_and_target_dir(vellum_client, mock_module):
|
413
|
+
# GIVEN a module on the user's filesystem
|
414
|
+
temp_dir = mock_module.temp_dir
|
415
|
+
mock_module.set_pyproject_toml({"workflows": []})
|
416
|
+
|
417
|
+
# GIVEN the vellum client returns a list of template workflows
|
418
|
+
fake_templates = [
|
419
|
+
MockTemplate(id="template-1", label="Example Workflow"),
|
420
|
+
MockTemplate(id="template-2", label="Another Workflow"),
|
421
|
+
]
|
422
|
+
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
423
|
+
|
424
|
+
# AND the workflow pull API call returns a zip file
|
425
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
426
|
+
|
427
|
+
# AND a target directory
|
428
|
+
target_dir = os.path.join(temp_dir, "dir")
|
429
|
+
os.makedirs(target_dir, exist_ok=True)
|
430
|
+
|
431
|
+
# WHEN the user runs the init command with a specific template name and target-dir
|
432
|
+
template_name = snake_case("Another Workflow")
|
433
|
+
runner = CliRunner()
|
434
|
+
result = runner.invoke(cli_main, ["workflows", "init", template_name, "--target-dir", target_dir])
|
435
|
+
|
436
|
+
# THEN the command returns successfully
|
437
|
+
assert result.exit_code == 0
|
438
|
+
|
439
|
+
# AND `vellum_client.workflows.pull` is called with the correct template ID
|
440
|
+
vellum_client.workflows.pull.assert_called_once_with(
|
441
|
+
"template-2", # ID of "Another Workflow"
|
442
|
+
request_options={"additional_query_parameters": {"include_sandbox": True}},
|
443
|
+
)
|
444
|
+
|
445
|
+
# AND the workflow files should be created in the target directory with the correct module subdirectory
|
446
|
+
module_path = os.path.join(target_dir, "another_workflow")
|
447
|
+
workflow_py = os.path.join(module_path, "workflow.py")
|
448
|
+
assert os.path.exists(workflow_py)
|
449
|
+
with open(workflow_py) as f:
|
450
|
+
assert f.read() == "print('hello')"
|
451
|
+
|
452
|
+
# AND the files are not in the default module directory
|
453
|
+
default_module_path = os.path.join(temp_dir, "another_workflow", "workflow.py")
|
454
|
+
assert not os.path.exists(default_module_path)
|
455
|
+
|
456
|
+
# AND the vellum.lock.json file should be created with the correct data
|
457
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
458
|
+
assert os.path.exists(vellum_lock_json)
|
459
|
+
|
460
|
+
with open(vellum_lock_json) as f:
|
461
|
+
lock_data = json.load(f)
|
462
|
+
assert lock_data["workflows"] == [
|
463
|
+
{
|
464
|
+
"module": "another_workflow",
|
465
|
+
"workflow_sandbox_id": "template-2",
|
466
|
+
"ignore": None,
|
467
|
+
"deployments": [],
|
468
|
+
"container_image_name": None,
|
469
|
+
"container_image_tag": None,
|
470
|
+
"workspace": "default",
|
471
|
+
"target_directory": module_path,
|
472
|
+
}
|
473
|
+
]
|
vellum_cli/tests/test_pull.py
CHANGED
@@ -73,6 +73,7 @@ def test_pull(vellum_client, mock_module, base_command):
|
|
73
73
|
"ignore": None,
|
74
74
|
"deployments": [],
|
75
75
|
"workspace": "default",
|
76
|
+
"target_directory": None,
|
76
77
|
}
|
77
78
|
],
|
78
79
|
"workspaces": [],
|
@@ -112,6 +113,135 @@ def test_pull__second_module(vellum_client, mock_module):
|
|
112
113
|
assert f.read() == "print('hello')"
|
113
114
|
|
114
115
|
|
116
|
+
@pytest.mark.parametrize(
|
117
|
+
"base_command",
|
118
|
+
[
|
119
|
+
["pull"],
|
120
|
+
["workflows", "pull"],
|
121
|
+
],
|
122
|
+
ids=["pull", "workflows_pull"],
|
123
|
+
)
|
124
|
+
def test_pull__with_target_dir(vellum_client, mock_module, base_command):
|
125
|
+
# GIVEN a module on the user's filesystem
|
126
|
+
temp_dir = mock_module.temp_dir
|
127
|
+
module = mock_module.module
|
128
|
+
workflow_sandbox_id = mock_module.workflow_sandbox_id
|
129
|
+
|
130
|
+
# AND a target directory
|
131
|
+
target_dir = os.path.join(temp_dir, "dir")
|
132
|
+
os.makedirs(target_dir, exist_ok=True)
|
133
|
+
|
134
|
+
# AND the workflow pull API call returns a zip file
|
135
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
136
|
+
|
137
|
+
# WHEN the user runs the pull command with target-dir
|
138
|
+
runner = CliRunner()
|
139
|
+
result = runner.invoke(cli_main, base_command + [module, "--target-dir", target_dir])
|
140
|
+
|
141
|
+
# THEN the command returns successfully
|
142
|
+
assert result.exit_code == 0
|
143
|
+
|
144
|
+
# AND the workflow.py file is written to the target directory
|
145
|
+
module_path = os.path.join(target_dir, *module.split("."))
|
146
|
+
workflow_py = os.path.join(module_path, "workflow.py")
|
147
|
+
assert os.path.exists(workflow_py)
|
148
|
+
with open(workflow_py) as f:
|
149
|
+
assert f.read() == "print('hello')"
|
150
|
+
|
151
|
+
# AND the files are not in the default module directory
|
152
|
+
default_module_path = os.path.join(temp_dir, *module.split("."), "workflow.py")
|
153
|
+
assert not os.path.exists(default_module_path)
|
154
|
+
|
155
|
+
# AND the vellum.lock.json file is still updated
|
156
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
157
|
+
assert os.path.exists(vellum_lock_json)
|
158
|
+
with open(vellum_lock_json) as f:
|
159
|
+
lock_data = json.load(f)
|
160
|
+
assert lock_data == {
|
161
|
+
"version": "1.0",
|
162
|
+
"workflows": [
|
163
|
+
{
|
164
|
+
"module": module,
|
165
|
+
"workflow_sandbox_id": workflow_sandbox_id,
|
166
|
+
"container_image_name": None,
|
167
|
+
"container_image_tag": None,
|
168
|
+
"ignore": None,
|
169
|
+
"deployments": [],
|
170
|
+
"workspace": "default",
|
171
|
+
"target_directory": module_path,
|
172
|
+
}
|
173
|
+
],
|
174
|
+
"workspaces": [],
|
175
|
+
}
|
176
|
+
|
177
|
+
|
178
|
+
@pytest.mark.parametrize(
|
179
|
+
"base_command",
|
180
|
+
[
|
181
|
+
["pull"],
|
182
|
+
["workflows", "pull"],
|
183
|
+
],
|
184
|
+
ids=["pull", "workflows_pull"],
|
185
|
+
)
|
186
|
+
def test_pull__with_nested_target_dir(vellum_client, mock_module, base_command):
|
187
|
+
# GIVEN a module on the user's filesystem
|
188
|
+
temp_dir = mock_module.temp_dir
|
189
|
+
module = mock_module.module
|
190
|
+
workflow_sandbox_id = mock_module.workflow_sandbox_id
|
191
|
+
|
192
|
+
# AND a nested target directory that doesn't exist yet
|
193
|
+
nested_target_dir = os.path.join(temp_dir, "dir-1", "dir-2")
|
194
|
+
|
195
|
+
# AND the workflow pull API call returns a zip file
|
196
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
197
|
+
|
198
|
+
# WHEN the user runs the pull command with nested target-dir
|
199
|
+
runner = CliRunner()
|
200
|
+
result = runner.invoke(cli_main, base_command + [module, "--target-dir", nested_target_dir])
|
201
|
+
|
202
|
+
# THEN the command returns successfully
|
203
|
+
assert result.exit_code == 0
|
204
|
+
|
205
|
+
# AND the nested directory with module subdirectory should be created
|
206
|
+
module_path = os.path.join(nested_target_dir, *module.split("."))
|
207
|
+
assert os.path.exists(module_path)
|
208
|
+
|
209
|
+
# AND the nested directory should be created
|
210
|
+
assert os.path.exists(module_path)
|
211
|
+
|
212
|
+
# AND the workflow.py file is written to the nested target directory
|
213
|
+
workflow_py = os.path.join(module_path, "workflow.py")
|
214
|
+
assert os.path.exists(workflow_py)
|
215
|
+
with open(workflow_py) as f:
|
216
|
+
assert f.read() == "print('hello')"
|
217
|
+
|
218
|
+
# AND the files are not in the default module directory
|
219
|
+
default_module_path = os.path.join(temp_dir, *module.split("."), "workflow.py")
|
220
|
+
assert not os.path.exists(default_module_path)
|
221
|
+
|
222
|
+
# AND the vellum.lock.json file is still updated
|
223
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
224
|
+
assert os.path.exists(vellum_lock_json)
|
225
|
+
with open(vellum_lock_json) as f:
|
226
|
+
lock_data = json.load(f)
|
227
|
+
assert lock_data == {
|
228
|
+
"version": "1.0",
|
229
|
+
"workflows": [
|
230
|
+
{
|
231
|
+
"module": module,
|
232
|
+
"workflow_sandbox_id": workflow_sandbox_id,
|
233
|
+
"container_image_name": None,
|
234
|
+
"container_image_tag": None,
|
235
|
+
"ignore": None,
|
236
|
+
"deployments": [],
|
237
|
+
"workspace": "default",
|
238
|
+
"target_directory": module_path,
|
239
|
+
}
|
240
|
+
],
|
241
|
+
"workspaces": [],
|
242
|
+
}
|
243
|
+
|
244
|
+
|
115
245
|
def test_pull__sandbox_id_with_no_config(vellum_client):
|
116
246
|
# GIVEN a workflow sandbox id
|
117
247
|
workflow_sandbox_id = "87654321-0000-0000-0000-000000000000"
|
@@ -162,6 +292,7 @@ def test_pull__sandbox_id_with_no_config(vellum_client):
|
|
162
292
|
"container_image_tag": None,
|
163
293
|
"container_image_name": None,
|
164
294
|
"workspace": "default",
|
295
|
+
"target_directory": None,
|
165
296
|
}
|
166
297
|
],
|
167
298
|
}
|
@@ -245,6 +376,7 @@ def test_pull__workflow_deployment_with_no_config(vellum_client):
|
|
245
376
|
"container_image_tag": None,
|
246
377
|
"container_image_name": None,
|
247
378
|
"workspace": "default",
|
379
|
+
"target_directory": None,
|
248
380
|
}
|
249
381
|
],
|
250
382
|
"workspaces": [],
|
@@ -492,6 +624,7 @@ def test_pull__sandbox_id_with_other_workflow_deployment_in_lock(vellum_client,
|
|
492
624
|
"container_image_name": None,
|
493
625
|
"container_image_tag": None,
|
494
626
|
"workspace": "default",
|
627
|
+
"target_directory": None,
|
495
628
|
},
|
496
629
|
{
|
497
630
|
"module": "super_cool_workflow",
|
@@ -501,6 +634,7 @@ def test_pull__sandbox_id_with_other_workflow_deployment_in_lock(vellum_client,
|
|
501
634
|
"container_image_name": "test",
|
502
635
|
"container_image_tag": "1.0",
|
503
636
|
"workspace": "default",
|
637
|
+
"target_directory": None,
|
504
638
|
},
|
505
639
|
]
|
506
640
|
|
@@ -644,6 +778,7 @@ def test_pull__module_not_in_config(vellum_client, mock_module):
|
|
644
778
|
"container_image_name": None,
|
645
779
|
"container_image_tag": None,
|
646
780
|
"workspace": "default",
|
781
|
+
"target_directory": None,
|
647
782
|
}
|
648
783
|
]
|
649
784
|
|
vellum_cli/tests/test_push.py
CHANGED
@@ -47,7 +47,7 @@ from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_di
|
|
47
47
|
from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay, PortDisplay, PortDisplayOverrides
|
48
48
|
from vellum_ee.workflows.display.utils.expressions import get_child_descriptor
|
49
49
|
from vellum_ee.workflows.display.utils.vellum import convert_descriptor_to_operator, primitive_to_vellum_value
|
50
|
-
from vellum_ee.workflows.display.vellum import CodeResourceDefinition,
|
50
|
+
from vellum_ee.workflows.display.vellum import CodeResourceDefinition, NodeDisplayData
|
51
51
|
|
52
52
|
if TYPE_CHECKING:
|
53
53
|
from vellum_ee.workflows.display.types import WorkflowDisplayContext
|
@@ -295,9 +295,9 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
|
|
295
295
|
|
296
296
|
cls._node_display_registry[node_class] = cls
|
297
297
|
|
298
|
-
def _get_generic_node_display_data(self) ->
|
299
|
-
explicit_value = self._get_explicit_node_display_attr("display_data",
|
300
|
-
return explicit_value if explicit_value else
|
298
|
+
def _get_generic_node_display_data(self) -> NodeDisplayData:
|
299
|
+
explicit_value = self._get_explicit_node_display_attr("display_data", NodeDisplayData)
|
300
|
+
return explicit_value if explicit_value else NodeDisplayData()
|
301
301
|
|
302
302
|
def serialize_condition(self, display_context: "WorkflowDisplayContext", condition: BaseDescriptor) -> JsonObject:
|
303
303
|
if isinstance(
|