vellum-workflow-server 1.7.2__tar.gz → 1.7.2.post2__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.
Potentially problematic release.
This version of vellum-workflow-server might be problematic. Click here for more details.
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/PKG-INFO +1 -1
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/pyproject.toml +1 -1
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/api/tests/test_workflow_view_stream_workflow_route.py +59 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/api/workflow_view.py +3 -4
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/code_exec_runner.py +1 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/core/cancel_workflow.py +7 -5
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/core/executor.py +10 -15
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/core/utils.py +5 -1
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/README.md +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/__init__.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/api/__init__.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/api/auth_middleware.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/api/healthz_view.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/api/tests/__init__.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/api/tests/test_input_display_mapping.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/api/tests/test_workflow_view.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/config.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/core/__init__.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/core/events.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/core/workflow_executor_context.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/server.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/start.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/utils/__init__.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/utils/exit_handler.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/utils/log_proxy.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/utils/oom_killer.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/utils/sentry.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/utils/system_utils.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/utils/tests/__init__.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/utils/tests/test_sentry_integration.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/utils/tests/test_system_utils.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/utils/tests/test_utils.py +0 -0
- {vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/utils/utils.py +0 -0
|
@@ -571,6 +571,65 @@ class BasicCancellableWorkflow(BaseWorkflow):
|
|
|
571
571
|
)
|
|
572
572
|
|
|
573
573
|
|
|
574
|
+
def test_stream_workflow_route__timeout_emits_rejection_events():
|
|
575
|
+
"""
|
|
576
|
+
Tests that when a workflow times out, we emit node and workflow rejection events.
|
|
577
|
+
"""
|
|
578
|
+
|
|
579
|
+
span_id = uuid4()
|
|
580
|
+
request_body = {
|
|
581
|
+
"timeout": 1,
|
|
582
|
+
"execution_id": str(span_id),
|
|
583
|
+
"inputs": [],
|
|
584
|
+
"environment_api_key": "test",
|
|
585
|
+
"module": "workflow",
|
|
586
|
+
"files": {
|
|
587
|
+
"__init__.py": "",
|
|
588
|
+
"workflow.py": """\
|
|
589
|
+
import time
|
|
590
|
+
|
|
591
|
+
from vellum.workflows.nodes.bases.base import BaseNode
|
|
592
|
+
from vellum.workflows.workflows.base import BaseWorkflow
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
class LongRunningNode(BaseNode):
|
|
596
|
+
class Outputs(BaseNode.Outputs):
|
|
597
|
+
value: str
|
|
598
|
+
|
|
599
|
+
def run(self) -> Outputs:
|
|
600
|
+
time.sleep(30)
|
|
601
|
+
return self.Outputs(value="hello world")
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
class TimeoutWorkflow(BaseWorkflow):
|
|
605
|
+
graph = LongRunningNode
|
|
606
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
607
|
+
final_value = LongRunningNode.Outputs.value
|
|
608
|
+
|
|
609
|
+
""",
|
|
610
|
+
},
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
status_code, events = flask_stream(request_body)
|
|
614
|
+
|
|
615
|
+
assert status_code == 200
|
|
616
|
+
|
|
617
|
+
event_names = [e["name"] for e in events]
|
|
618
|
+
|
|
619
|
+
assert "vembda.execution.initiated" in event_names
|
|
620
|
+
assert "workflow.execution.initiated" in event_names
|
|
621
|
+
assert "node.execution.initiated" in event_names
|
|
622
|
+
|
|
623
|
+
# TODO: Re-enable once solved SDK-side
|
|
624
|
+
# assert "node.execution.rejected" in event_names, "Should emit node.execution.rejected on timeout"
|
|
625
|
+
|
|
626
|
+
assert "workflow.execution.rejected" in event_names, "Should emit workflow.execution.rejected on timeout"
|
|
627
|
+
|
|
628
|
+
assert "vembda.execution.fulfilled" in event_names
|
|
629
|
+
vembda_fulfilled = next(e for e in events if e["name"] == "vembda.execution.fulfilled")
|
|
630
|
+
assert vembda_fulfilled["body"]["timed_out"] is True
|
|
631
|
+
|
|
632
|
+
|
|
574
633
|
def test_stream_workflow_route__very_large_events(both_stream_types):
|
|
575
634
|
# GIVEN a valid request body
|
|
576
635
|
span_id = uuid4()
|
|
@@ -3,12 +3,11 @@ import importlib
|
|
|
3
3
|
import inspect
|
|
4
4
|
import json
|
|
5
5
|
import logging
|
|
6
|
-
from multiprocessing import Process, Queue, set_start_method
|
|
6
|
+
from multiprocessing import Event as MultiprocessingEvent, Process, Queue, set_start_method
|
|
7
7
|
import os
|
|
8
8
|
import pkgutil
|
|
9
9
|
from queue import Empty
|
|
10
10
|
import sys
|
|
11
|
-
from threading import Event as ThreadingEvent
|
|
12
11
|
import time
|
|
13
12
|
import traceback
|
|
14
13
|
from uuid import uuid4
|
|
@@ -123,8 +122,8 @@ def stream_workflow_route() -> Response:
|
|
|
123
122
|
headers=headers,
|
|
124
123
|
)
|
|
125
124
|
|
|
126
|
-
cancel_signal =
|
|
127
|
-
timeout_signal =
|
|
125
|
+
cancel_signal = MultiprocessingEvent()
|
|
126
|
+
timeout_signal = MultiprocessingEvent()
|
|
128
127
|
|
|
129
128
|
process: Optional[Process] = None
|
|
130
129
|
if ENABLE_PROCESS_WRAPPER:
|
|
@@ -50,6 +50,7 @@ def run_code_exec_stream() -> None:
|
|
|
50
50
|
disable_redirect=True,
|
|
51
51
|
# Timeouts are handled at the code exec level right now so just passing in an unused threading event
|
|
52
52
|
timeout_signal=ThreadingEvent(),
|
|
53
|
+
cancel_signal=ThreadingEvent(),
|
|
53
54
|
)
|
|
54
55
|
for line in stream_iterator:
|
|
55
56
|
print(f"{_EVENT_LINE}{json.dumps(line)}") # noqa: T201
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from threading import
|
|
2
|
+
from threading import Thread
|
|
3
3
|
import time
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
from typing import Optional
|
|
6
6
|
|
|
7
7
|
import requests
|
|
8
8
|
|
|
9
|
+
from workflow_server.core.utils import CancelSignal
|
|
10
|
+
|
|
9
11
|
_TIMER_INTERVAL = 5
|
|
10
12
|
|
|
11
13
|
logger = logging.getLogger(__name__)
|
|
@@ -23,18 +25,18 @@ def get_is_workflow_cancelled(execution_id: UUID, vembda_public_url: Optional[st
|
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
class CancelWorkflowWatcherThread(Thread):
|
|
26
|
-
_kill_switch:
|
|
28
|
+
_kill_switch: CancelSignal
|
|
27
29
|
_execution_id: UUID
|
|
28
|
-
_cancel_signal:
|
|
30
|
+
_cancel_signal: CancelSignal
|
|
29
31
|
_vembda_public_url: Optional[str]
|
|
30
32
|
_start = time.time()
|
|
31
33
|
|
|
32
34
|
def __init__(
|
|
33
35
|
self,
|
|
34
|
-
kill_switch:
|
|
36
|
+
kill_switch: CancelSignal,
|
|
35
37
|
execution_id: UUID,
|
|
36
38
|
vembda_public_url: Optional[str],
|
|
37
|
-
cancel_signal:
|
|
39
|
+
cancel_signal: CancelSignal,
|
|
38
40
|
timeout_seconds: int,
|
|
39
41
|
) -> None:
|
|
40
42
|
Thread.__init__(self)
|
|
@@ -8,7 +8,6 @@ import os
|
|
|
8
8
|
import random
|
|
9
9
|
import string
|
|
10
10
|
import sys
|
|
11
|
-
import threading
|
|
12
11
|
from threading import Event as ThreadingEvent
|
|
13
12
|
import time
|
|
14
13
|
from traceback import format_exc
|
|
@@ -43,6 +42,7 @@ from workflow_server.core.events import (
|
|
|
43
42
|
VembdaExecutionFulfilledEvent,
|
|
44
43
|
)
|
|
45
44
|
from workflow_server.core.utils import (
|
|
45
|
+
CancelSignal,
|
|
46
46
|
create_vembda_rejected_event,
|
|
47
47
|
is_events_emitting_enabled,
|
|
48
48
|
serialize_vembda_rejected_event,
|
|
@@ -87,8 +87,8 @@ def _stream_node_wrapper(executor_context: NodeExecutorContext, queue: Queue) ->
|
|
|
87
87
|
def _stream_workflow_wrapper(
|
|
88
88
|
executor_context: WorkflowExecutorContext,
|
|
89
89
|
queue: Queue,
|
|
90
|
-
cancel_signal:
|
|
91
|
-
timeout_signal:
|
|
90
|
+
cancel_signal: CancelSignal,
|
|
91
|
+
timeout_signal: CancelSignal,
|
|
92
92
|
) -> None:
|
|
93
93
|
span_id_emitted = False
|
|
94
94
|
try:
|
|
@@ -119,8 +119,8 @@ def _stream_workflow_wrapper(
|
|
|
119
119
|
def stream_workflow_process_timeout(
|
|
120
120
|
executor_context: WorkflowExecutorContext,
|
|
121
121
|
queue: Queue,
|
|
122
|
-
cancel_signal:
|
|
123
|
-
timeout_signal:
|
|
122
|
+
cancel_signal: CancelSignal,
|
|
123
|
+
timeout_signal: CancelSignal,
|
|
124
124
|
) -> Process:
|
|
125
125
|
workflow_process = Process(
|
|
126
126
|
target=_stream_workflow_wrapper,
|
|
@@ -141,9 +141,9 @@ def stream_workflow_process_timeout(
|
|
|
141
141
|
|
|
142
142
|
def stream_workflow(
|
|
143
143
|
executor_context: WorkflowExecutorContext,
|
|
144
|
-
timeout_signal:
|
|
144
|
+
timeout_signal: CancelSignal,
|
|
145
|
+
cancel_signal: CancelSignal,
|
|
145
146
|
disable_redirect: bool = True,
|
|
146
|
-
cancel_signal: Optional[ThreadingEvent] = None,
|
|
147
147
|
) -> tuple[Iterator[dict], UUID]:
|
|
148
148
|
cancel_watcher_kill_switch = ThreadingEvent()
|
|
149
149
|
try:
|
|
@@ -172,7 +172,8 @@ def stream_workflow(
|
|
|
172
172
|
state=workflow_state,
|
|
173
173
|
node_output_mocks=node_output_mocks,
|
|
174
174
|
event_filter=all_workflow_event_filter,
|
|
175
|
-
|
|
175
|
+
# TODO: Fix type in SDK
|
|
176
|
+
cancel_signal=cancel_signal, # type: ignore[arg-type]
|
|
176
177
|
entrypoint_nodes=[run_from_node] if run_from_node else None,
|
|
177
178
|
previous_execution_id=executor_context.previous_execution_id,
|
|
178
179
|
)
|
|
@@ -235,12 +236,6 @@ def stream_workflow(
|
|
|
235
236
|
finally:
|
|
236
237
|
cancel_watcher_kill_switch.set()
|
|
237
238
|
|
|
238
|
-
emitter_thread = next(
|
|
239
|
-
(t for t in threading.enumerate() if t.name.endswith(".background_thread") and t.is_alive()), None
|
|
240
|
-
)
|
|
241
|
-
if emitter_thread:
|
|
242
|
-
emitter_thread.join()
|
|
243
|
-
|
|
244
239
|
workflow.join()
|
|
245
240
|
|
|
246
241
|
return (
|
|
@@ -298,7 +293,7 @@ def stream_node(
|
|
|
298
293
|
def _call_stream(
|
|
299
294
|
executor_context: BaseExecutorContext,
|
|
300
295
|
stream_generator: Callable[[], Generator[dict[str, Any], Any, None]],
|
|
301
|
-
timeout_signal:
|
|
296
|
+
timeout_signal: CancelSignal,
|
|
302
297
|
disable_redirect: bool = True,
|
|
303
298
|
) -> Iterator[dict]:
|
|
304
299
|
log_redirect: Optional[StringIO] = None
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
+
from multiprocessing.synchronize import Event as MultiprocessingEvent
|
|
3
|
+
from threading import Event as ThreadingEvent
|
|
2
4
|
from uuid import uuid4
|
|
3
|
-
from typing import Optional
|
|
5
|
+
from typing import Optional, Union
|
|
4
6
|
|
|
5
7
|
from workflow_server.core.events import VembdaExecutionFulfilledBody, VembdaExecutionFulfilledEvent
|
|
6
8
|
from workflow_server.core.workflow_executor_context import BaseExecutorContext
|
|
7
9
|
|
|
10
|
+
CancelSignal = Union[MultiprocessingEvent, ThreadingEvent]
|
|
11
|
+
|
|
8
12
|
|
|
9
13
|
def _create_vembda_rejected_event_base(
|
|
10
14
|
executor_context: Optional[BaseExecutorContext], error_message: str, timed_out: bool
|
|
File without changes
|
{vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/server.py
RENAMED
|
File without changes
|
{vellum_workflow_server-1.7.2 → vellum_workflow_server-1.7.2.post2}/src/workflow_server/start.py
RENAMED
|
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
|