vellum-ai 0.14.7__py3-none-any.whl → 0.14.9__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 +2 -0
- vellum/client/core/client_wrapper.py +1 -1
- vellum/client/types/__init__.py +2 -0
- vellum/client/types/document_prompt_block.py +29 -0
- vellum/client/types/prompt_block.py +2 -0
- vellum/types/document_prompt_block.py +3 -0
- vellum/workflows/descriptors/base.py +6 -0
- vellum/workflows/descriptors/tests/test_utils.py +14 -0
- vellum/workflows/events/tests/test_event.py +40 -0
- vellum/workflows/events/workflow.py +20 -1
- vellum/workflows/expressions/greater_than.py +15 -8
- vellum/workflows/expressions/greater_than_or_equal_to.py +14 -8
- vellum/workflows/expressions/less_than.py +14 -8
- vellum/workflows/expressions/less_than_or_equal_to.py +14 -8
- vellum/workflows/expressions/parse_json.py +30 -0
- vellum/workflows/expressions/tests/__init__.py +0 -0
- vellum/workflows/expressions/tests/test_expressions.py +310 -0
- vellum/workflows/expressions/tests/test_parse_json.py +31 -0
- vellum/workflows/nodes/bases/base.py +5 -2
- vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +34 -2
- vellum/workflows/nodes/displayable/bases/api_node/node.py +1 -1
- vellum/workflows/nodes/displayable/code_execution_node/node.py +18 -8
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +53 -0
- vellum/workflows/runner/runner.py +33 -4
- vellum/workflows/state/encoder.py +2 -1
- {vellum_ai-0.14.7.dist-info → vellum_ai-0.14.9.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.7.dist-info → vellum_ai-0.14.9.dist-info}/RECORD +44 -38
- vellum_cli/__init__.py +9 -2
- vellum_cli/config.py +1 -0
- vellum_cli/init.py +6 -2
- vellum_cli/pull.py +1 -0
- vellum_cli/tests/test_init.py +194 -76
- vellum_cli/tests/test_pull.py +8 -0
- vellum_cli/tests/test_push.py +1 -0
- vellum_ee/workflows/display/nodes/base_node_display.py +4 -0
- vellum_ee/workflows/display/tests/test_vellum_workflow_display.py +114 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +118 -3
- vellum_ee/workflows/display/types.py +1 -14
- vellum_ee/workflows/display/workflows/base_workflow_display.py +48 -19
- vellum_ee/workflows/display/workflows/vellum_workflow_display.py +12 -0
- vellum_ee/workflows/tests/test_server.py +1 -0
- {vellum_ai-0.14.7.dist-info → vellum_ai-0.14.9.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.7.dist-info → vellum_ai-0.14.9.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.7.dist-info → vellum_ai-0.14.9.dist-info}/entry_points.txt +0 -0
vellum_cli/tests/test_init.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
import pytest
|
2
1
|
import io
|
3
2
|
import json
|
4
3
|
import os
|
@@ -33,14 +32,7 @@ class MockTemplate:
|
|
33
32
|
self.label = label
|
34
33
|
|
35
34
|
|
36
|
-
|
37
|
-
"base_command",
|
38
|
-
[
|
39
|
-
["workflows", "init"],
|
40
|
-
],
|
41
|
-
ids=["workflows_init"],
|
42
|
-
)
|
43
|
-
def test_init_command(vellum_client, mock_module, base_command):
|
35
|
+
def test_init_command(vellum_client, mock_module):
|
44
36
|
# GIVEN a module on the user's filesystem
|
45
37
|
temp_dir = mock_module.temp_dir
|
46
38
|
mock_module.set_pyproject_toml({"workflows": []})
|
@@ -52,18 +44,11 @@ def test_init_command(vellum_client, mock_module, base_command):
|
|
52
44
|
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
53
45
|
|
54
46
|
# AND the workflow pull API call returns a zip file
|
55
|
-
vellum_client.workflows.pull.return_value = iter(
|
56
|
-
|
57
|
-
_zip_file_map(
|
58
|
-
{
|
59
|
-
"workflow.py": "print('hello')",
|
60
|
-
}
|
61
|
-
)
|
62
|
-
]
|
63
|
-
)
|
47
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
48
|
+
|
64
49
|
# WHEN the user runs the `init` command and selects the first template
|
65
50
|
runner = CliRunner()
|
66
|
-
result = runner.invoke(cli_main,
|
51
|
+
result = runner.invoke(cli_main, ["workflows", "init"], input="1\n")
|
67
52
|
|
68
53
|
# THEN the command returns successfully
|
69
54
|
assert result.exit_code == 0
|
@@ -94,18 +79,12 @@ def test_init_command(vellum_client, mock_module, base_command):
|
|
94
79
|
"container_image_name": None,
|
95
80
|
"container_image_tag": None,
|
96
81
|
"workspace": "default",
|
82
|
+
"target_directory": None,
|
97
83
|
}
|
98
84
|
]
|
99
85
|
|
100
86
|
|
101
|
-
|
102
|
-
"base_command",
|
103
|
-
[
|
104
|
-
["workflows", "init"],
|
105
|
-
],
|
106
|
-
ids=["workflows_init"],
|
107
|
-
)
|
108
|
-
def test_init_command__invalid_template_id(vellum_client, mock_module, base_command):
|
87
|
+
def test_init_command__invalid_template_id(vellum_client, mock_module):
|
109
88
|
# GIVEN a module on the user's filesystem
|
110
89
|
temp_dir = mock_module.temp_dir
|
111
90
|
mock_module.set_pyproject_toml({"workflows": []})
|
@@ -121,7 +100,7 @@ def test_init_command__invalid_template_id(vellum_client, mock_module, base_comm
|
|
121
100
|
# Mock click.prompt to raise a KeyboardInterrupt (simulating Ctrl+C)
|
122
101
|
with patch("click.prompt", side_effect=KeyboardInterrupt):
|
123
102
|
runner = CliRunner()
|
124
|
-
result = runner.invoke(cli_main,
|
103
|
+
result = runner.invoke(cli_main, ["workflows", "init"])
|
125
104
|
|
126
105
|
# THEN the command is aborted
|
127
106
|
assert result.exit_code != 0
|
@@ -142,14 +121,7 @@ def test_init_command__invalid_template_id(vellum_client, mock_module, base_comm
|
|
142
121
|
assert lock_data["workflows"] == []
|
143
122
|
|
144
123
|
|
145
|
-
|
146
|
-
"base_command",
|
147
|
-
[
|
148
|
-
["workflows", "init"],
|
149
|
-
],
|
150
|
-
ids=["workflows_init"],
|
151
|
-
)
|
152
|
-
def test_init_command__no_templates(vellum_client, mock_module, base_command):
|
124
|
+
def test_init_command__no_templates(vellum_client, mock_module):
|
153
125
|
# GIVEN a module on the user's filesystem
|
154
126
|
temp_dir = mock_module.temp_dir
|
155
127
|
mock_module.set_pyproject_toml({"workflows": []})
|
@@ -158,7 +130,7 @@ def test_init_command__no_templates(vellum_client, mock_module, base_command):
|
|
158
130
|
|
159
131
|
# WHEN the user runs the `init` command
|
160
132
|
runner = CliRunner()
|
161
|
-
result = runner.invoke(cli_main,
|
133
|
+
result = runner.invoke(cli_main, ["workflows", "init"])
|
162
134
|
|
163
135
|
# THEN the command gracefully exits
|
164
136
|
assert result.exit_code == 0
|
@@ -179,14 +151,7 @@ def test_init_command__no_templates(vellum_client, mock_module, base_command):
|
|
179
151
|
assert lock_data["workflows"] == []
|
180
152
|
|
181
153
|
|
182
|
-
|
183
|
-
"base_command",
|
184
|
-
[
|
185
|
-
["workflows", "init"],
|
186
|
-
],
|
187
|
-
ids=["workflows_init"],
|
188
|
-
)
|
189
|
-
def test_init_command_target_directory_exists(vellum_client, mock_module, base_command):
|
154
|
+
def test_init_command_target_directory_exists(vellum_client, mock_module):
|
190
155
|
"""
|
191
156
|
GIVEN a target directory already exists
|
192
157
|
WHEN the user tries to run the `init` command
|
@@ -208,19 +173,11 @@ def test_init_command_target_directory_exists(vellum_client, mock_module, base_c
|
|
208
173
|
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
209
174
|
|
210
175
|
# AND the workflow pull API call returns a zip file
|
211
|
-
vellum_client.workflows.pull.return_value = iter(
|
212
|
-
[
|
213
|
-
_zip_file_map(
|
214
|
-
{
|
215
|
-
"workflow.py": "print('hello')",
|
216
|
-
}
|
217
|
-
)
|
218
|
-
]
|
219
|
-
)
|
176
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
220
177
|
|
221
178
|
# WHEN the user runs the `init` command and selects the template
|
222
179
|
runner = CliRunner()
|
223
|
-
result = runner.invoke(cli_main,
|
180
|
+
result = runner.invoke(cli_main, ["workflows", "init"], input="1\n")
|
224
181
|
|
225
182
|
# THEN the command should detect the existing directory and abort
|
226
183
|
assert result.exit_code == 0
|
@@ -244,14 +201,7 @@ def test_init_command_target_directory_exists(vellum_client, mock_module, base_c
|
|
244
201
|
assert lock_data["workflows"] == []
|
245
202
|
|
246
203
|
|
247
|
-
|
248
|
-
"base_command",
|
249
|
-
[
|
250
|
-
["workflows", "init"],
|
251
|
-
],
|
252
|
-
ids=["workflows_init"],
|
253
|
-
)
|
254
|
-
def test_init_command_with_template_name(vellum_client, mock_module, base_command):
|
204
|
+
def test_init_command_with_template_name(vellum_client, mock_module):
|
255
205
|
# GIVEN a module on the user's filesystem
|
256
206
|
temp_dir = mock_module.temp_dir
|
257
207
|
mock_module.set_pyproject_toml({"workflows": []})
|
@@ -264,14 +214,12 @@ def test_init_command_with_template_name(vellum_client, mock_module, base_comman
|
|
264
214
|
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
265
215
|
|
266
216
|
# AND the workflow pull API call returns a zip file
|
267
|
-
vellum_client.workflows.pull.return_value = iter(
|
268
|
-
[_zip_file_map({"workflow.py": "print('hello')", "README.md": "# Another Workflow\nThis is a test template."})]
|
269
|
-
)
|
217
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
270
218
|
|
271
219
|
# WHEN the user runs the `init` command with a specific template name
|
272
220
|
template_name = snake_case("Another Workflow")
|
273
221
|
runner = CliRunner()
|
274
|
-
result = runner.invoke(cli_main,
|
222
|
+
result = runner.invoke(cli_main, ["workflows", "init", template_name])
|
275
223
|
|
276
224
|
# THEN the command returns successfully
|
277
225
|
assert result.exit_code == 0
|
@@ -305,18 +253,12 @@ def test_init_command_with_template_name(vellum_client, mock_module, base_comman
|
|
305
253
|
"container_image_name": None,
|
306
254
|
"container_image_tag": None,
|
307
255
|
"workspace": "default",
|
256
|
+
"target_directory": None,
|
308
257
|
}
|
309
258
|
]
|
310
259
|
|
311
260
|
|
312
|
-
|
313
|
-
"base_command",
|
314
|
-
[
|
315
|
-
["workflows", "init"],
|
316
|
-
],
|
317
|
-
ids=["workflows_init"],
|
318
|
-
)
|
319
|
-
def test_init_command_with_nonexistent_template_name(vellum_client, mock_module, base_command):
|
261
|
+
def test_init_command_with_nonexistent_template_name(vellum_client, mock_module):
|
320
262
|
# GIVEN a module on the user's filesystem
|
321
263
|
temp_dir = mock_module.temp_dir
|
322
264
|
mock_module.set_pyproject_toml({"workflows": []})
|
@@ -331,7 +273,7 @@ def test_init_command_with_nonexistent_template_name(vellum_client, mock_module,
|
|
331
273
|
# WHEN the user runs the `init` command with a non-existent template name
|
332
274
|
nonexistent_template = "nonexistent_template"
|
333
275
|
runner = CliRunner()
|
334
|
-
result = runner.invoke(cli_main,
|
276
|
+
result = runner.invoke(cli_main, ["workflows", "init", nonexistent_template])
|
335
277
|
|
336
278
|
# THEN the command should indicate the template was not found
|
337
279
|
assert result.exit_code == 0
|
@@ -353,3 +295,179 @@ def test_init_command_with_nonexistent_template_name(vellum_client, mock_module,
|
|
353
295
|
with open(vellum_lock_json) as f:
|
354
296
|
lock_data = json.load(f)
|
355
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": [],
|
@@ -167,6 +168,7 @@ def test_pull__with_target_dir(vellum_client, mock_module, base_command):
|
|
167
168
|
"ignore": None,
|
168
169
|
"deployments": [],
|
169
170
|
"workspace": "default",
|
171
|
+
"target_directory": module_path,
|
170
172
|
}
|
171
173
|
],
|
172
174
|
"workspaces": [],
|
@@ -233,6 +235,7 @@ def test_pull__with_nested_target_dir(vellum_client, mock_module, base_command):
|
|
233
235
|
"ignore": None,
|
234
236
|
"deployments": [],
|
235
237
|
"workspace": "default",
|
238
|
+
"target_directory": module_path,
|
236
239
|
}
|
237
240
|
],
|
238
241
|
"workspaces": [],
|
@@ -289,6 +292,7 @@ def test_pull__sandbox_id_with_no_config(vellum_client):
|
|
289
292
|
"container_image_tag": None,
|
290
293
|
"container_image_name": None,
|
291
294
|
"workspace": "default",
|
295
|
+
"target_directory": None,
|
292
296
|
}
|
293
297
|
],
|
294
298
|
}
|
@@ -372,6 +376,7 @@ def test_pull__workflow_deployment_with_no_config(vellum_client):
|
|
372
376
|
"container_image_tag": None,
|
373
377
|
"container_image_name": None,
|
374
378
|
"workspace": "default",
|
379
|
+
"target_directory": None,
|
375
380
|
}
|
376
381
|
],
|
377
382
|
"workspaces": [],
|
@@ -619,6 +624,7 @@ def test_pull__sandbox_id_with_other_workflow_deployment_in_lock(vellum_client,
|
|
619
624
|
"container_image_name": None,
|
620
625
|
"container_image_tag": None,
|
621
626
|
"workspace": "default",
|
627
|
+
"target_directory": None,
|
622
628
|
},
|
623
629
|
{
|
624
630
|
"module": "super_cool_workflow",
|
@@ -628,6 +634,7 @@ def test_pull__sandbox_id_with_other_workflow_deployment_in_lock(vellum_client,
|
|
628
634
|
"container_image_name": "test",
|
629
635
|
"container_image_tag": "1.0",
|
630
636
|
"workspace": "default",
|
637
|
+
"target_directory": None,
|
631
638
|
},
|
632
639
|
]
|
633
640
|
|
@@ -771,6 +778,7 @@ def test_pull__module_not_in_config(vellum_client, mock_module):
|
|
771
778
|
"container_image_name": None,
|
772
779
|
"container_image_tag": None,
|
773
780
|
"workspace": "default",
|
781
|
+
"target_directory": None,
|
774
782
|
}
|
775
783
|
]
|
776
784
|
|
vellum_cli/tests/test_push.py
CHANGED
@@ -27,6 +27,7 @@ from vellum.workflows.expressions.is_not_undefined import IsNotUndefinedExpressi
|
|
27
27
|
from vellum.workflows.expressions.is_null import IsNullExpression
|
28
28
|
from vellum.workflows.expressions.is_undefined import IsUndefinedExpression
|
29
29
|
from vellum.workflows.expressions.not_between import NotBetweenExpression
|
30
|
+
from vellum.workflows.expressions.parse_json import ParseJsonExpression
|
30
31
|
from vellum.workflows.nodes.bases.base import BaseNode
|
31
32
|
from vellum.workflows.nodes.utils import get_wrapped_node
|
32
33
|
from vellum.workflows.ports import Port
|
@@ -386,6 +387,9 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
|
|
386
387
|
"node_id": str(node_class_display.node_id),
|
387
388
|
}
|
388
389
|
|
390
|
+
if isinstance(value, ParseJsonExpression):
|
391
|
+
raise ValueError("ParseJsonExpression is not supported in the UI")
|
392
|
+
|
389
393
|
if not isinstance(value, BaseDescriptor):
|
390
394
|
vellum_value = primitive_to_vellum_value(value)
|
391
395
|
return {
|
@@ -1,4 +1,6 @@
|
|
1
|
+
import pytest
|
1
2
|
from uuid import UUID
|
3
|
+
from typing import Dict
|
2
4
|
|
3
5
|
from vellum.workflows.inputs import BaseInputs
|
4
6
|
from vellum.workflows.nodes import BaseNode
|
@@ -145,3 +147,115 @@ def test_vellum_workflow_display_serialize_valid_handle_ids_for_base_nodes():
|
|
145
147
|
assert (
|
146
148
|
node["trigger"]["id"] in edge_target_handle_ids
|
147
149
|
), f"Trigger {node['trigger']['id']} from node {node['label']} not found in edge target handle ids"
|
150
|
+
|
151
|
+
|
152
|
+
def test_vellum_workflow_display__serialize_with_unused_nodes_and_edges():
|
153
|
+
# GIVEN a workflow with active and unused nodes
|
154
|
+
class NodeA(BaseNode):
|
155
|
+
class Outputs(BaseNode.Outputs):
|
156
|
+
result: str
|
157
|
+
|
158
|
+
class NodeB(BaseNode):
|
159
|
+
pass
|
160
|
+
|
161
|
+
class NodeC(BaseNode):
|
162
|
+
pass
|
163
|
+
|
164
|
+
# AND A workflow that uses them correctly
|
165
|
+
class Workflow(BaseWorkflow):
|
166
|
+
graph = NodeA
|
167
|
+
unused_graphs = {NodeB >> NodeC}
|
168
|
+
|
169
|
+
class Outputs(BaseWorkflow.Outputs):
|
170
|
+
final = NodeA.Outputs.result
|
171
|
+
|
172
|
+
# WHEN we serialize it
|
173
|
+
workflow_display = get_workflow_display(
|
174
|
+
base_display_class=VellumWorkflowDisplay,
|
175
|
+
workflow_class=Workflow,
|
176
|
+
)
|
177
|
+
|
178
|
+
# WHEN we serialize the workflow
|
179
|
+
exec_config = workflow_display.serialize()
|
180
|
+
|
181
|
+
# THEN the serialized workflow contains the expected nodes and edges
|
182
|
+
raw_data = exec_config["workflow_raw_data"]
|
183
|
+
assert isinstance(raw_data, dict)
|
184
|
+
|
185
|
+
nodes = raw_data["nodes"]
|
186
|
+
edges = raw_data["edges"]
|
187
|
+
|
188
|
+
assert isinstance(nodes, list)
|
189
|
+
assert isinstance(edges, list)
|
190
|
+
|
191
|
+
# Find nodes by their definition name
|
192
|
+
node_ids: Dict[str, str] = {}
|
193
|
+
|
194
|
+
for node in nodes:
|
195
|
+
assert isinstance(node, dict)
|
196
|
+
definition = node.get("definition")
|
197
|
+
if definition is None:
|
198
|
+
continue
|
199
|
+
|
200
|
+
assert isinstance(definition, dict)
|
201
|
+
name = definition.get("name")
|
202
|
+
if not isinstance(name, str):
|
203
|
+
continue
|
204
|
+
|
205
|
+
if name in ["NodeA", "NodeB", "NodeC"]:
|
206
|
+
node_id = node.get("id")
|
207
|
+
if isinstance(node_id, str):
|
208
|
+
node_ids[name] = node_id
|
209
|
+
|
210
|
+
# Verify all nodes are present
|
211
|
+
assert "NodeA" in node_ids, "Active node NodeA not found in serialized output"
|
212
|
+
assert "NodeB" in node_ids, "Unused node NodeB not found in serialized output"
|
213
|
+
assert "NodeC" in node_ids, "Unused node NodeC not found in serialized output"
|
214
|
+
|
215
|
+
# Verify the edge between NodeB and NodeC is present
|
216
|
+
edge_found = False
|
217
|
+
for edge in edges:
|
218
|
+
assert isinstance(edge, dict)
|
219
|
+
source_id = edge.get("source_node_id")
|
220
|
+
target_id = edge.get("target_node_id")
|
221
|
+
|
222
|
+
if (
|
223
|
+
isinstance(source_id, str)
|
224
|
+
and isinstance(target_id, str)
|
225
|
+
and source_id == node_ids["NodeB"]
|
226
|
+
and target_id == node_ids["NodeC"]
|
227
|
+
):
|
228
|
+
edge_found = True
|
229
|
+
break
|
230
|
+
|
231
|
+
assert edge_found, "Edge between unused nodes NodeB and NodeC not found in serialized output"
|
232
|
+
|
233
|
+
|
234
|
+
def test_parse_json_not_supported_in_ui():
|
235
|
+
"""
|
236
|
+
Test that verifies ParseJsonExpression is not yet supported in the UI.
|
237
|
+
This test should fail once UI support is added, at which point it should be updated.
|
238
|
+
"""
|
239
|
+
# GIVEN a workflow that uses the parse_json function
|
240
|
+
from vellum.workflows.references.constant import ConstantValueReference
|
241
|
+
|
242
|
+
class JsonNode(BaseNode):
|
243
|
+
class Outputs(BaseNode.Outputs):
|
244
|
+
json_result = ConstantValueReference('{"key": "value"}').parse_json()
|
245
|
+
|
246
|
+
class Workflow(BaseWorkflow):
|
247
|
+
graph = JsonNode
|
248
|
+
|
249
|
+
class Outputs(BaseWorkflow.Outputs):
|
250
|
+
final = JsonNode.Outputs.json_result
|
251
|
+
|
252
|
+
# WHEN we attempt to serialize it
|
253
|
+
workflow_display = get_workflow_display(
|
254
|
+
base_display_class=VellumWorkflowDisplay,
|
255
|
+
workflow_class=Workflow,
|
256
|
+
)
|
257
|
+
|
258
|
+
with pytest.raises(ValueError) as exc_info:
|
259
|
+
workflow_display.serialize()
|
260
|
+
|
261
|
+
assert "ParseJsonExpression is not supported in the UI" == str(exc_info.value)
|