vellum-workflow-server 1.0.5.post4__tar.gz → 1.0.6.post1__tar.gz
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_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/PKG-INFO +2 -2
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/pyproject.toml +2 -2
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/api/tests/test_workflow_view.py +79 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/api/workflow_view.py +45 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/README.md +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/__init__.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/api/__init__.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/api/auth_middleware.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/api/healthz_view.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/api/tests/__init__.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/api/tests/test_input_display_mapping.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/api/tests/test_workflow_view_stream_workflow_route.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/code_exec_runner.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/config.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/core/__init__.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/core/cancel_workflow.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/core/events.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/core/executor.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/core/utils.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/core/workflow_executor_context.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/server.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/start.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/utils/__init__.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/utils/exit_handler.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/utils/log_proxy.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/utils/oom_killer.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/utils/sentry.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/utils/system_utils.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/utils/tests/__init__.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/utils/tests/test_sentry_integration.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/utils/tests/test_system_utils.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/utils/tests/test_utils.py +0 -0
- {vellum_workflow_server-1.0.5.post4 → vellum_workflow_server-1.0.6.post1}/src/workflow_server/utils/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: vellum-workflow-server
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.6.post1
|
|
4
4
|
Summary:
|
|
5
5
|
License: AGPL
|
|
6
6
|
Requires-Python: >=3.9.0,<4
|
|
@@ -29,7 +29,7 @@ Requires-Dist: pyjwt (==2.10.0)
|
|
|
29
29
|
Requires-Dist: python-dotenv (==1.0.1)
|
|
30
30
|
Requires-Dist: retrying (==1.3.4)
|
|
31
31
|
Requires-Dist: sentry-sdk[flask] (==2.20.0)
|
|
32
|
-
Requires-Dist: vellum-ai (==1.0.
|
|
32
|
+
Requires-Dist: vellum-ai (==1.0.6)
|
|
33
33
|
Description-Content-Type: text/markdown
|
|
34
34
|
|
|
35
35
|
# Vellum Workflow Runner Server
|
|
@@ -3,7 +3,7 @@ name = "vellum-workflow-server"
|
|
|
3
3
|
|
|
4
4
|
[tool.poetry]
|
|
5
5
|
name = "vellum-workflow-server"
|
|
6
|
-
version = "1.0.
|
|
6
|
+
version = "1.0.6.post1"
|
|
7
7
|
description = ""
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
authors = []
|
|
@@ -45,7 +45,7 @@ flask = "2.3.3"
|
|
|
45
45
|
orderly-set = "5.2.2"
|
|
46
46
|
pebble = "5.0.7"
|
|
47
47
|
gunicorn = "23.0.0"
|
|
48
|
-
vellum-ai = "1.0.
|
|
48
|
+
vellum-ai = "1.0.6"
|
|
49
49
|
python-dotenv = "1.0.1"
|
|
50
50
|
retrying = "1.3.4"
|
|
51
51
|
sentry-sdk = {extras = ["flask"], version = "2.20.0"}
|
|
@@ -3,6 +3,8 @@ import re
|
|
|
3
3
|
from unittest.mock import patch
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
|
|
6
|
+
from deepdiff import DeepDiff
|
|
7
|
+
|
|
6
8
|
from workflow_server.server import create_app
|
|
7
9
|
|
|
8
10
|
|
|
@@ -376,3 +378,80 @@ class MyAdditionNode(BaseNode):
|
|
|
376
378
|
"trigger": {"id": "a5298668-d808-4a45-a62e-790943948e8a", "merge_behavior": "AWAIT_ATTRIBUTES"},
|
|
377
379
|
"type": "GENERIC",
|
|
378
380
|
}
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def test_serialize_route__with_no_files():
|
|
384
|
+
# GIVEN a Flask application
|
|
385
|
+
flask_app = create_app()
|
|
386
|
+
|
|
387
|
+
# WHEN we make a request with no files
|
|
388
|
+
with flask_app.test_client() as test_client:
|
|
389
|
+
response = test_client.post("/workflow/serialize", json={"files": {}})
|
|
390
|
+
|
|
391
|
+
# THEN we should get a bad request response
|
|
392
|
+
assert response.status_code == 400
|
|
393
|
+
|
|
394
|
+
# AND the response should contain an error message
|
|
395
|
+
assert "detail" in response.json
|
|
396
|
+
assert "No files received" in response.json["detail"]
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def test_serialize_route__with_invalid_python_syntax():
|
|
400
|
+
# GIVEN a Flask application
|
|
401
|
+
flask_app = create_app()
|
|
402
|
+
|
|
403
|
+
# AND a file with invalid Python syntax
|
|
404
|
+
invalid_content = """
|
|
405
|
+
from vellum.workflows.nodes import BaseNode
|
|
406
|
+
|
|
407
|
+
class BrokenNode(BaseNode) # Missing colon
|
|
408
|
+
\"\"\"This node has a syntax error.\"\"\"
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
# WHEN we make a request to the serialize route
|
|
412
|
+
with flask_app.test_client() as test_client:
|
|
413
|
+
response = test_client.post("/workflow/serialize", json={"files": {"broken_node.py": invalid_content}})
|
|
414
|
+
|
|
415
|
+
# THEN we should get a server error response
|
|
416
|
+
assert response.status_code == 500
|
|
417
|
+
|
|
418
|
+
# AND the response should contain an error message
|
|
419
|
+
assert "detail" in response.json
|
|
420
|
+
assert "Serialization failed" in response.json["detail"]
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def test_serialize_route__with__workflow():
|
|
424
|
+
# GIVEN a Flask application
|
|
425
|
+
flask_app = create_app()
|
|
426
|
+
|
|
427
|
+
# AND a complete workflow with multiple files
|
|
428
|
+
workflow_files = {
|
|
429
|
+
"__init__.py": "# flake8: noqa: F401, F403\n\n",
|
|
430
|
+
"inputs.py": "from typing import Any, Optional\n\nfrom vellum.workflows.inputs import BaseInputs\n\n\nclass Inputs(BaseInputs):\n text: str\n var_1: Optional[Any]\n", # noqa: E501
|
|
431
|
+
"workflow.py": "from vellum.workflows import BaseWorkflow\nfrom vellum.workflows.state import BaseState\n\nfrom .inputs import Inputs\nfrom .nodes.final_output import FinalOutput\nfrom .nodes.templating_node import TemplatingNode\n\n\nclass Workflow(BaseWorkflow[Inputs, BaseState]):\n graph = TemplatingNode >> FinalOutput\n\n class Outputs(BaseWorkflow.Outputs):\n final_output = FinalOutput.Outputs.value\n", # noqa: E501
|
|
432
|
+
"nodes/__init__.py": 'from .final_output import FinalOutput\nfrom .templating_node import TemplatingNode\n\n__all__ = [\n "FinalOutput",\n "TemplatingNode",\n]\n', # noqa: E501
|
|
433
|
+
"nodes/templating_node.py": 'from vellum.workflows.nodes.displayable import TemplatingNode as BaseTemplatingNode\nfrom vellum.workflows.state import BaseState\n\nfrom ..inputs import Inputs\n\n\nclass TemplatingNode(BaseTemplatingNode[BaseState, str]):\n template = """{{ text }}"""\n inputs = {\n "text": Inputs.text,\n }\n', # noqa: E501
|
|
434
|
+
"nodes/final_output.py": "from vellum.workflows.nodes.displayable import FinalOutputNode\nfrom vellum.workflows.state import BaseState\n\nfrom .templating_node import TemplatingNode\n\n\nclass FinalOutput(FinalOutputNode[BaseState, str]):\n class Outputs(FinalOutputNode.Outputs):\n value = TemplatingNode.Outputs.result\n", # noqa: E501
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
# WHEN we make a request to the serialize route
|
|
438
|
+
with flask_app.test_client() as test_client:
|
|
439
|
+
response = test_client.post("/workflow/serialize", json={"files": workflow_files})
|
|
440
|
+
|
|
441
|
+
# THEN we should get a successful response
|
|
442
|
+
assert response.status_code == 200
|
|
443
|
+
|
|
444
|
+
# AND the response should contain exec_config
|
|
445
|
+
assert "exec_config" in response.json
|
|
446
|
+
exec_config = response.json["exec_config"]
|
|
447
|
+
|
|
448
|
+
# AND the exec_config should have workflow_raw_data
|
|
449
|
+
assert "workflow_raw_data" in exec_config
|
|
450
|
+
nodes = exec_config["workflow_raw_data"]["nodes"]
|
|
451
|
+
|
|
452
|
+
# AND we should find the workflow nodes
|
|
453
|
+
node_labels = {node["data"]["label"] for node in nodes}
|
|
454
|
+
expected_nodes = {"Templating Node", "Final Output", "Entrypoint Node"}
|
|
455
|
+
|
|
456
|
+
# AND at least some of the expected nodes should be present
|
|
457
|
+
assert not DeepDiff(node_labels, expected_nodes, ignore_order=True)
|
|
@@ -7,6 +7,8 @@ from multiprocessing import Process, Queue, set_start_method
|
|
|
7
7
|
import os
|
|
8
8
|
import pkgutil
|
|
9
9
|
from queue import Empty
|
|
10
|
+
import random
|
|
11
|
+
import string
|
|
10
12
|
import sys
|
|
11
13
|
from threading import Event as ThreadingEvent
|
|
12
14
|
import time
|
|
@@ -18,6 +20,8 @@ from flask import Blueprint, Response, current_app as app, has_request_context,
|
|
|
18
20
|
from pydantic import ValidationError
|
|
19
21
|
from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
|
|
20
22
|
from vellum_ee.workflows.display.types import WorkflowDisplayContext
|
|
23
|
+
from vellum_ee.workflows.display.workflows import BaseWorkflowDisplay
|
|
24
|
+
from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
|
|
21
25
|
|
|
22
26
|
from vellum.workflows.exceptions import WorkflowInitializationException
|
|
23
27
|
from vellum.workflows.nodes import BaseNode
|
|
@@ -410,6 +414,47 @@ def stream_node_route() -> Response:
|
|
|
410
414
|
return resp
|
|
411
415
|
|
|
412
416
|
|
|
417
|
+
@bp.route("/serialize", methods=["POST"])
|
|
418
|
+
def serialize_route() -> Response:
|
|
419
|
+
data = request.get_json()
|
|
420
|
+
|
|
421
|
+
files = data.get("files", {})
|
|
422
|
+
|
|
423
|
+
if not files:
|
|
424
|
+
return Response(
|
|
425
|
+
json.dumps({"detail": "No files received"}),
|
|
426
|
+
status=400,
|
|
427
|
+
content_type="application/json",
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
try:
|
|
431
|
+
# Generate a unique namespace for this serialization request
|
|
432
|
+
namespace = "".join(random.choice(string.ascii_letters + string.digits) for i in range(14))
|
|
433
|
+
virtual_finder = VirtualFileFinder(files, namespace)
|
|
434
|
+
|
|
435
|
+
try:
|
|
436
|
+
sys.meta_path.append(virtual_finder)
|
|
437
|
+
exec_config = BaseWorkflowDisplay.serialize_module(namespace).exec_config
|
|
438
|
+
|
|
439
|
+
return Response(
|
|
440
|
+
json.dumps({"exec_config": exec_config}),
|
|
441
|
+
status=200,
|
|
442
|
+
content_type="application/json",
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
finally:
|
|
446
|
+
if virtual_finder in sys.meta_path:
|
|
447
|
+
sys.meta_path.remove(virtual_finder)
|
|
448
|
+
|
|
449
|
+
except Exception as e:
|
|
450
|
+
logger.exception(f"Error during serialization: {str(e)}")
|
|
451
|
+
return Response(
|
|
452
|
+
json.dumps({"detail": f"Serialization failed: {str(e)}"}),
|
|
453
|
+
status=500,
|
|
454
|
+
content_type="application/json",
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
|
|
413
458
|
@bp.route("/version", methods=["GET"])
|
|
414
459
|
def get_version_route() -> tuple[dict, int]:
|
|
415
460
|
resp = get_version()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|