vellum-ai 1.8.5__py3-none-any.whl → 1.9.0__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 +6 -0
- vellum/client/core/client_wrapper.py +2 -2
- vellum/client/types/__init__.py +6 -0
- vellum/client/types/api_actor_type_enum.py +7 -0
- vellum/client/types/api_request_parent_context.py +6 -0
- vellum/client/types/external_parent_context.py +2 -0
- vellum/client/types/integration_name.py +1 -0
- vellum/client/types/integration_trigger_context.py +38 -0
- vellum/client/types/node_execution_fulfilled_event.py +2 -0
- vellum/client/types/node_execution_initiated_event.py +2 -0
- vellum/client/types/node_execution_paused_event.py +2 -0
- vellum/client/types/node_execution_rejected_event.py +2 -0
- vellum/client/types/node_execution_resumed_event.py +2 -0
- vellum/client/types/node_execution_span.py +2 -0
- vellum/client/types/node_execution_streaming_event.py +2 -0
- vellum/client/types/node_parent_context.py +2 -0
- vellum/client/types/parent_context.py +4 -0
- vellum/client/types/prompt_deployment_parent_context.py +2 -0
- vellum/client/types/scheduled_trigger_context.py +38 -0
- vellum/client/types/slim_workflow_execution_read.py +2 -0
- vellum/client/types/span_link.py +2 -0
- vellum/client/types/workflow_deployment_event_executions_response.py +2 -0
- vellum/client/types/workflow_deployment_parent_context.py +2 -0
- vellum/client/types/workflow_event_execution_read.py +2 -0
- vellum/client/types/workflow_execution_detail.py +2 -0
- vellum/client/types/workflow_execution_fulfilled_event.py +2 -0
- vellum/client/types/workflow_execution_initiated_event.py +2 -0
- vellum/client/types/workflow_execution_paused_event.py +2 -0
- vellum/client/types/workflow_execution_rejected_event.py +2 -0
- vellum/client/types/workflow_execution_resumed_event.py +2 -0
- vellum/client/types/workflow_execution_snapshotted_event.py +2 -0
- vellum/client/types/workflow_execution_span.py +2 -0
- vellum/client/types/workflow_execution_streaming_event.py +2 -0
- vellum/client/types/workflow_parent_context.py +2 -0
- vellum/client/types/workflow_sandbox_parent_context.py +2 -0
- vellum/types/api_actor_type_enum.py +3 -0
- vellum/types/integration_trigger_context.py +3 -0
- vellum/types/scheduled_trigger_context.py +3 -0
- vellum/workflows/inputs/dataset_row.py +9 -7
- vellum/workflows/nodes/displayable/final_output_node/node.py +4 -0
- vellum/workflows/nodes/displayable/final_output_node/tests/test_node.py +28 -0
- vellum/workflows/nodes/displayable/set_state_node/__init__.py +5 -0
- vellum/workflows/nodes/displayable/set_state_node/node.py +71 -0
- vellum/workflows/nodes/displayable/set_state_node/tests/__init__.py +0 -0
- vellum/workflows/nodes/displayable/set_state_node/tests/test_node.py +212 -0
- vellum/workflows/sandbox.py +13 -3
- vellum/workflows/tests/test_dataset_row.py +20 -0
- vellum/workflows/tests/test_sandbox.py +40 -0
- vellum/workflows/tests/triggers/{test_vellum_integration_trigger.py → test_integration_trigger.py} +22 -22
- vellum/workflows/triggers/__init__.py +2 -2
- vellum/workflows/triggers/base.py +22 -4
- vellum/workflows/triggers/integration.py +168 -49
- vellum/workflows/triggers/schedule.py +18 -0
- vellum/workflows/triggers/tests/test_integration.py +49 -20
- vellum/workflows/utils/uuids.py +1 -15
- vellum/workflows/workflows/base.py +44 -0
- {vellum_ai-1.8.5.dist-info → vellum_ai-1.9.0.dist-info}/METADATA +1 -1
- {vellum_ai-1.8.5.dist-info → vellum_ai-1.9.0.dist-info}/RECORD +67 -57
- vellum_ee/workflows/display/tests/workflow_serialization/{test_vellum_integration_trigger_serialization.py → test_integration_trigger_serialization.py} +8 -8
- vellum_ee/workflows/display/utils/expressions.py +2 -3
- vellum_ee/workflows/display/workflows/base_workflow_display.py +9 -9
- vellum_ee/workflows/server/virtual_file_loader.py +74 -2
- vellum_ee/workflows/tests/test_server.py +81 -66
- vellum_ee/workflows/tests/test_virtual_files.py +48 -0
- vellum/workflows/triggers/vellum_integration.py +0 -189
- {vellum_ai-1.8.5.dist-info → vellum_ai-1.9.0.dist-info}/LICENSE +0 -0
- {vellum_ai-1.8.5.dist-info → vellum_ai-1.9.0.dist-info}/WHEEL +0 -0
- {vellum_ai-1.8.5.dist-info → vellum_ai-1.9.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
import sys
|
|
3
|
-
from uuid import uuid4
|
|
3
|
+
from uuid import UUID, uuid4
|
|
4
4
|
from typing import Type, cast
|
|
5
5
|
|
|
6
6
|
from vellum.client.core.pydantic_utilities import UniversalBaseModel
|
|
7
7
|
from vellum.client.types.code_executor_response import CodeExecutorResponse
|
|
8
8
|
from vellum.client.types.number_vellum_value import NumberVellumValue
|
|
9
9
|
from vellum.workflows import BaseWorkflow
|
|
10
|
+
from vellum.workflows.events.workflow import WorkflowExecutionInitiatedBody
|
|
10
11
|
from vellum.workflows.exceptions import WorkflowInitializationException
|
|
11
12
|
from vellum.workflows.nodes import BaseNode
|
|
12
13
|
from vellum.workflows.state.context import WorkflowContext
|
|
13
14
|
from vellum.workflows.utils.uuids import generate_workflow_deployment_prefix
|
|
14
15
|
from vellum.workflows.utils.zip import zip_file_map
|
|
16
|
+
from vellum_ee.workflows.display.utils.events import event_enricher
|
|
15
17
|
from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
|
|
16
18
|
from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
|
|
17
19
|
|
|
@@ -27,20 +29,12 @@ def test_load_workflow_event_display_context():
|
|
|
27
29
|
def test_load_from_module__lazy_reference_in_file_loader():
|
|
28
30
|
# GIVEN a workflow module with a node containing a lazy reference
|
|
29
31
|
files = {
|
|
30
|
-
"__init__.py": "",
|
|
31
32
|
"workflow.py": """\
|
|
32
33
|
from vellum.workflows import BaseWorkflow
|
|
33
34
|
from .nodes.start_node import StartNode
|
|
34
35
|
|
|
35
36
|
class Workflow(BaseWorkflow):
|
|
36
37
|
graph = StartNode
|
|
37
|
-
""",
|
|
38
|
-
"nodes/__init__.py": """\
|
|
39
|
-
from .start_node import StartNode
|
|
40
|
-
|
|
41
|
-
__all__ = [
|
|
42
|
-
"StartNode",
|
|
43
|
-
]
|
|
44
38
|
""",
|
|
45
39
|
"nodes/start_node.py": """\
|
|
46
40
|
from vellum.workflows.nodes import BaseNode
|
|
@@ -98,11 +92,6 @@ class Workflow(BaseWorkflow):
|
|
|
98
92
|
|
|
99
93
|
class Outputs(BaseWorkflow.Outputs):
|
|
100
94
|
final_output = CodeExecutionNode.Outputs.result
|
|
101
|
-
""",
|
|
102
|
-
"nodes/__init__.py": """
|
|
103
|
-
from .code_execution_node import CodeExecutionNode
|
|
104
|
-
|
|
105
|
-
__all__ = ["CodeExecutionNode"]
|
|
106
95
|
""",
|
|
107
96
|
"nodes/code_execution_node/__init__.py": """
|
|
108
97
|
from typing import Any
|
|
@@ -174,11 +163,6 @@ class Workflow(BaseWorkflow):
|
|
|
174
163
|
|
|
175
164
|
class Outputs(BaseWorkflow.Outputs):
|
|
176
165
|
final_output = CodeExecutionNode.Outputs.result
|
|
177
|
-
""",
|
|
178
|
-
"nodes/__init__.py": """
|
|
179
|
-
from .code_execution_node import CodeExecutionNode
|
|
180
|
-
|
|
181
|
-
__all__ = ["CodeExecutionNode"]
|
|
182
166
|
""",
|
|
183
167
|
"nodes/code_execution_node/__init__.py": """
|
|
184
168
|
from typing import Union
|
|
@@ -250,11 +234,6 @@ class Workflow(BaseWorkflow):
|
|
|
250
234
|
|
|
251
235
|
class Outputs(BaseWorkflow.Outputs):
|
|
252
236
|
final_output = CodeExecutionNode.Outputs.result
|
|
253
|
-
""",
|
|
254
|
-
"nodes/__init__.py": """
|
|
255
|
-
from .code_execution_node import CodeExecutionNode
|
|
256
|
-
|
|
257
|
-
__all__ = ["CodeExecutionNode"]
|
|
258
237
|
""",
|
|
259
238
|
"nodes/code_execution_node/__init__.py": """
|
|
260
239
|
from typing import Union
|
|
@@ -326,11 +305,6 @@ class Workflow(BaseWorkflow):
|
|
|
326
305
|
|
|
327
306
|
class Outputs(BaseWorkflow.Outputs):
|
|
328
307
|
final_output = Subworkflow.Outputs.result
|
|
329
|
-
""",
|
|
330
|
-
"nodes/__init__.py": """
|
|
331
|
-
from .subworkflow import Subworkflow
|
|
332
|
-
|
|
333
|
-
__all__ = ["Subworkflow"]
|
|
334
308
|
""",
|
|
335
309
|
"nodes/subworkflow/__init__.py": """
|
|
336
310
|
from vellum.workflows.nodes.displayable import InlineSubworkflowNode
|
|
@@ -339,11 +313,6 @@ from .workflow import SubworkflowWorkflow
|
|
|
339
313
|
|
|
340
314
|
class Subworkflow(InlineSubworkflowNode):
|
|
341
315
|
subworkflow = SubworkflowWorkflow
|
|
342
|
-
""",
|
|
343
|
-
"nodes/subworkflow/nodes/__init__.py": """
|
|
344
|
-
from .code_execution_node import CodeExecutionNode
|
|
345
|
-
|
|
346
|
-
__all__ = ["CodeExecutionNode"]
|
|
347
316
|
""",
|
|
348
317
|
"nodes/subworkflow/nodes/code_execution_node/__init__.py": """
|
|
349
318
|
from typing import Union
|
|
@@ -424,11 +393,6 @@ class Workflow(BaseWorkflow):
|
|
|
424
393
|
|
|
425
394
|
class Outputs(BaseWorkflow.Outputs): # noqa: W293
|
|
426
395
|
results = MapNode.Outputs.final_output
|
|
427
|
-
""",
|
|
428
|
-
"nodes/__init__.py": """
|
|
429
|
-
from .map_node import MapNode
|
|
430
|
-
|
|
431
|
-
__all__ = ["MapNode"]
|
|
432
396
|
""",
|
|
433
397
|
"nodes/map_node/__init__.py": """
|
|
434
398
|
from vellum.workflows.nodes.core.map_node import MapNode as BaseMapNode
|
|
@@ -439,11 +403,6 @@ class MapNode(BaseMapNode):
|
|
|
439
403
|
items = ["foo", "bar", "baz"]
|
|
440
404
|
subworkflow = MapNodeWorkflow
|
|
441
405
|
max_concurrency = 4
|
|
442
|
-
""",
|
|
443
|
-
"nodes/map_node/nodes/__init__.py": """
|
|
444
|
-
from .code_execution_node import CodeExecutionNode
|
|
445
|
-
|
|
446
|
-
__all__ = ["CodeExecutionNode"]
|
|
447
406
|
""",
|
|
448
407
|
"nodes/map_node/nodes/code_execution_node/__init__.py": """
|
|
449
408
|
from typing import Union
|
|
@@ -520,7 +479,6 @@ from .nodes.broken_node import BrokenNode
|
|
|
520
479
|
class Workflow(BaseWorkflow):
|
|
521
480
|
graph = BrokenNode
|
|
522
481
|
""",
|
|
523
|
-
"nodes/__init__.py": "",
|
|
524
482
|
"nodes/broken_node.py": """\
|
|
525
483
|
from vellum.workflows.nodes import BaseNode
|
|
526
484
|
|
|
@@ -559,7 +517,6 @@ from .nodes.broken_node import BrokenNode
|
|
|
559
517
|
class Workflow(BaseWorkflow):
|
|
560
518
|
graph = BrokenNode
|
|
561
519
|
""",
|
|
562
|
-
"nodes/__init__.py": "",
|
|
563
520
|
"nodes/broken_node.py": """\
|
|
564
521
|
from vellum.workflows.nodes import BaseNode
|
|
565
522
|
|
|
@@ -819,19 +776,9 @@ class TestSubworkflowDeploymentNode(SubworkflowDeploymentNode):
|
|
|
819
776
|
files = {
|
|
820
777
|
"__init__.py": "",
|
|
821
778
|
"workflow.py": parent_workflow_code,
|
|
822
|
-
"nodes/__init__.py": """
|
|
823
|
-
from .subworkflow_deployment_node import TestSubworkflowDeploymentNode
|
|
824
|
-
|
|
825
|
-
__all__ = ["TestSubworkflowDeploymentNode"]
|
|
826
|
-
""",
|
|
827
779
|
"nodes/subworkflow_deployment_node.py": parent_node_code,
|
|
828
780
|
f"{expected_prefix}/__init__.py": "",
|
|
829
781
|
f"{expected_prefix}/workflow.py": mock_workflow_code,
|
|
830
|
-
f"{expected_prefix}/nodes/__init__.py": """
|
|
831
|
-
from .test_node import TestNode
|
|
832
|
-
|
|
833
|
-
__all__ = ["TestNode"]
|
|
834
|
-
""",
|
|
835
782
|
f"{expected_prefix}/nodes/test_node.py": test_node_code,
|
|
836
783
|
}
|
|
837
784
|
|
|
@@ -919,11 +866,6 @@ class TestSubworkflowDeploymentNode(SubworkflowDeploymentNode):
|
|
|
919
866
|
subworkflow_files = {
|
|
920
867
|
"__init__.py": "",
|
|
921
868
|
"workflow.py": mock_workflow_code,
|
|
922
|
-
"nodes/__init__.py": """
|
|
923
|
-
from .test_node import TestNode
|
|
924
|
-
|
|
925
|
-
__all__ = ["TestNode"]
|
|
926
|
-
""",
|
|
927
869
|
"nodes/test_node.py": test_node_code,
|
|
928
870
|
}
|
|
929
871
|
|
|
@@ -931,11 +873,6 @@ __all__ = ["TestNode"]
|
|
|
931
873
|
"__init__.py": "",
|
|
932
874
|
"inputs.py": inputs_code,
|
|
933
875
|
"workflow.py": parent_workflow_code,
|
|
934
|
-
"nodes/__init__.py": """
|
|
935
|
-
from .subworkflow_deployment_node import TestSubworkflowDeploymentNode
|
|
936
|
-
|
|
937
|
-
__all__ = ["TestSubworkflowDeploymentNode"]
|
|
938
|
-
""",
|
|
939
876
|
"nodes/subworkflow_deployment_node.py": parent_node_code,
|
|
940
877
|
}
|
|
941
878
|
|
|
@@ -966,3 +903,81 @@ __all__ = ["TestSubworkflowDeploymentNode"]
|
|
|
966
903
|
|
|
967
904
|
# AND the X-Vellum-Always-Success header should be included for graceful error handling
|
|
968
905
|
assert kwargs["request_options"]["additional_headers"]["X-Vellum-Always-Success"] == "true"
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
def test_workflow_initiated_event_includes_display_context_with_output_display_name():
|
|
909
|
+
"""
|
|
910
|
+
Tests that workflow initiated events include display context with annotated node and output information.
|
|
911
|
+
"""
|
|
912
|
+
# GIVEN a workflow with a single node that has a single output
|
|
913
|
+
files = {
|
|
914
|
+
"workflow.py": """\
|
|
915
|
+
from vellum.workflows import BaseWorkflow
|
|
916
|
+
from .nodes.start_node import StartNode
|
|
917
|
+
|
|
918
|
+
class Workflow(BaseWorkflow):
|
|
919
|
+
graph = StartNode
|
|
920
|
+
|
|
921
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
922
|
+
final_result = StartNode.Outputs.result
|
|
923
|
+
""",
|
|
924
|
+
"nodes/start_node.py": """\
|
|
925
|
+
from vellum.workflows.nodes import BaseNode
|
|
926
|
+
|
|
927
|
+
class StartNode(BaseNode):
|
|
928
|
+
class Outputs(BaseNode.Outputs):
|
|
929
|
+
result: str = "test output"
|
|
930
|
+
""",
|
|
931
|
+
"display/nodes/__init__.py": """\
|
|
932
|
+
from .start_node import StartNodeDisplay
|
|
933
|
+
""",
|
|
934
|
+
"display/nodes/start_node.py": """\
|
|
935
|
+
from uuid import UUID
|
|
936
|
+
from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
|
|
937
|
+
from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay
|
|
938
|
+
from ...nodes.start_node import StartNode
|
|
939
|
+
|
|
940
|
+
class StartNodeDisplay(BaseNodeDisplay[StartNode]):
|
|
941
|
+
node_id = UUID("11111111-1111-1111-1111-111111111111")
|
|
942
|
+
output_display = {
|
|
943
|
+
StartNode.Outputs.result: NodeOutputDisplay(
|
|
944
|
+
id=UUID("22222222-2222-2222-2222-222222222222"),
|
|
945
|
+
name="Pretty Result"
|
|
946
|
+
)
|
|
947
|
+
}
|
|
948
|
+
""",
|
|
949
|
+
"display/workflow.py": """\
|
|
950
|
+
""",
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
namespace = str(uuid4())
|
|
954
|
+
finder = VirtualFileFinder(files, namespace)
|
|
955
|
+
sys.meta_path.append(finder)
|
|
956
|
+
|
|
957
|
+
try:
|
|
958
|
+
# WHEN we stream the workflow and enrich the initiated event with display context
|
|
959
|
+
Workflow = BaseWorkflow.load_from_module(namespace)
|
|
960
|
+
workflow = Workflow()
|
|
961
|
+
initiated_event = list(workflow.stream())[0]
|
|
962
|
+
enriched_event = event_enricher(initiated_event)
|
|
963
|
+
|
|
964
|
+
# THEN the initiated event should have display context
|
|
965
|
+
assert isinstance(enriched_event.body, WorkflowExecutionInitiatedBody)
|
|
966
|
+
body = enriched_event.body
|
|
967
|
+
assert body.display_context is not None
|
|
968
|
+
|
|
969
|
+
# AND the display context should contain the annotated node
|
|
970
|
+
node_displays = body.display_context.node_displays
|
|
971
|
+
annotated_node_id = UUID("11111111-1111-1111-1111-111111111111")
|
|
972
|
+
assert annotated_node_id in node_displays
|
|
973
|
+
|
|
974
|
+
# AND the node's output display should contain the output with the display name "Pretty Result"
|
|
975
|
+
node_display = node_displays[annotated_node_id]
|
|
976
|
+
assert "result" in node_display.output_display
|
|
977
|
+
|
|
978
|
+
# AND the output should map to the annotated output id
|
|
979
|
+
annotated_output_id = UUID("22222222-2222-2222-2222-222222222222")
|
|
980
|
+
assert node_display.output_display["result"] == annotated_output_id
|
|
981
|
+
finally:
|
|
982
|
+
if finder in sys.meta_path:
|
|
983
|
+
sys.meta_path.remove(finder)
|
|
@@ -80,3 +80,51 @@ def test_base_class_dynamic_import(files):
|
|
|
80
80
|
assert instance.id
|
|
81
81
|
except Exception as e:
|
|
82
82
|
pytest.fail(f"Failed to create an instance of BaseClass: {e}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_display_directory_not_auto_generated():
|
|
86
|
+
"""
|
|
87
|
+
Test that the top-level display directory is NOT auto-generated with empty __init__.py.
|
|
88
|
+
Display directories typically have specific __init__.py content (e.g., "from .workflow import *")
|
|
89
|
+
that should not be replaced with empty auto-generated files.
|
|
90
|
+
"""
|
|
91
|
+
# GIVEN a workflow with display/workflow.py but NO display/__init__.py
|
|
92
|
+
files = {
|
|
93
|
+
"__init__.py": "",
|
|
94
|
+
"workflow.py": """\
|
|
95
|
+
from vellum.workflows import BaseWorkflow
|
|
96
|
+
from vellum.workflows.nodes.bases import BaseNode
|
|
97
|
+
|
|
98
|
+
class StartNode(BaseNode):
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
class Workflow(BaseWorkflow):
|
|
102
|
+
graph = StartNode
|
|
103
|
+
""",
|
|
104
|
+
"display/workflow.py": """\
|
|
105
|
+
from vellum_ee.workflows.display.workflows import BaseWorkflowDisplay
|
|
106
|
+
|
|
107
|
+
class WorkflowDisplay(BaseWorkflowDisplay):
|
|
108
|
+
pass
|
|
109
|
+
""",
|
|
110
|
+
# Note: NO "display/__init__.py" in files dict
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
namespace = str(uuid4())
|
|
114
|
+
|
|
115
|
+
# AND the virtual file loader is registered
|
|
116
|
+
sys.meta_path.append(VirtualFileFinder(files, namespace))
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
# WHEN we try to resolve display/__init__.py
|
|
120
|
+
import importlib.util
|
|
121
|
+
|
|
122
|
+
spec = importlib.util.find_spec(f"{namespace}.display")
|
|
123
|
+
|
|
124
|
+
# THEN the spec should be found because we now support dynamic display module imports
|
|
125
|
+
assert spec
|
|
126
|
+
assert spec.origin == "display/__init__.py"
|
|
127
|
+
|
|
128
|
+
finally:
|
|
129
|
+
# Clean up
|
|
130
|
+
sys.meta_path = [finder for finder in sys.meta_path if not isinstance(finder, VirtualFileFinder)]
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
from typing import Any, ClassVar, Dict
|
|
2
|
-
|
|
3
|
-
from vellum.workflows.constants import VellumIntegrationProviderType
|
|
4
|
-
from vellum.workflows.references.trigger import TriggerAttributeReference
|
|
5
|
-
from vellum.workflows.triggers.base import BaseTriggerMeta
|
|
6
|
-
from vellum.workflows.triggers.integration import IntegrationTrigger
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class VellumIntegrationTriggerMeta(BaseTriggerMeta):
|
|
10
|
-
"""
|
|
11
|
-
Custom metaclass for VellumIntegrationTrigger.
|
|
12
|
-
|
|
13
|
-
This metaclass extends BaseTriggerMeta to automatically convert type annotations
|
|
14
|
-
into TriggerAttributeReference objects during class creation. This enables trigger
|
|
15
|
-
attributes to be referenced in workflow graphs while maintaining type safety.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
def __new__(mcs, name: str, bases: tuple, namespace: dict, **kwargs: Any) -> "VellumIntegrationTriggerMeta":
|
|
19
|
-
"""Create a new trigger class and set up attribute references."""
|
|
20
|
-
cls = super().__new__(mcs, name, bases, namespace, **kwargs)
|
|
21
|
-
|
|
22
|
-
# Process __annotations__ to create TriggerAttributeReference for each attribute
|
|
23
|
-
# Only process if class has Config and annotations
|
|
24
|
-
has_config = hasattr(cls, "Config") and "Config" in namespace
|
|
25
|
-
if has_config and hasattr(cls, "__annotations__"):
|
|
26
|
-
# Create TriggerAttributeReference for each annotated attribute
|
|
27
|
-
for attr_name, attr_type in cls.__annotations__.items():
|
|
28
|
-
# Skip special attributes and Config
|
|
29
|
-
if attr_name.startswith("_") or attr_name == "Config":
|
|
30
|
-
continue
|
|
31
|
-
|
|
32
|
-
# Create reference with proper type
|
|
33
|
-
reference = TriggerAttributeReference(
|
|
34
|
-
name=attr_name, types=(attr_type,), instance=None, trigger_class=cls
|
|
35
|
-
)
|
|
36
|
-
# Set as class attribute so it's directly accessible
|
|
37
|
-
setattr(cls, attr_name, reference)
|
|
38
|
-
|
|
39
|
-
return cls
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class VellumIntegrationTrigger(IntegrationTrigger, metaclass=VellumIntegrationTriggerMeta):
|
|
43
|
-
"""
|
|
44
|
-
Base class for Vellum-managed integration triggers.
|
|
45
|
-
|
|
46
|
-
Subclasses define two types of attributes:
|
|
47
|
-
1. **Config class**: Specifies how the trigger is configured (provider, integration_name, slug)
|
|
48
|
-
- These are configuration details users shouldn't need to interact with directly
|
|
49
|
-
2. **Top-level type annotations**: Define the webhook event payload structure (message, user, channel, etc.)
|
|
50
|
-
- These become TriggerAttributeReference that can be referenced in workflow nodes
|
|
51
|
-
|
|
52
|
-
Examples:
|
|
53
|
-
Create a Slack trigger:
|
|
54
|
-
>>> class SlackNewMessageTrigger(VellumIntegrationTrigger):
|
|
55
|
-
... # Event attributes (webhook payload structure)
|
|
56
|
-
... message: str
|
|
57
|
-
... user: str
|
|
58
|
-
... channel: str
|
|
59
|
-
... timestamp: float
|
|
60
|
-
...
|
|
61
|
-
... # Configuration (how trigger is set up)
|
|
62
|
-
... class Config(VellumIntegrationTrigger.Config):
|
|
63
|
-
... provider = VellumIntegrationProviderType.COMPOSIO
|
|
64
|
-
... integration_name = "SLACK"
|
|
65
|
-
... slug = "slack_new_message"
|
|
66
|
-
|
|
67
|
-
Use in workflow graph:
|
|
68
|
-
>>> class MyWorkflow(BaseWorkflow):
|
|
69
|
-
... graph = SlackNewMessageTrigger >> ProcessMessageNode
|
|
70
|
-
|
|
71
|
-
Reference trigger attributes in nodes:
|
|
72
|
-
>>> class ProcessNode(BaseNode):
|
|
73
|
-
... class Outputs(BaseNode.Outputs):
|
|
74
|
-
... text = SlackNewMessageTrigger.message
|
|
75
|
-
... channel = SlackNewMessageTrigger.channel
|
|
76
|
-
|
|
77
|
-
Instantiate for testing:
|
|
78
|
-
>>> trigger = SlackNewMessageTrigger(event_data={
|
|
79
|
-
... "message": "Hello world",
|
|
80
|
-
... "channel": "C123456",
|
|
81
|
-
... "user": "U123",
|
|
82
|
-
... "timestamp": 1234567890.0,
|
|
83
|
-
... })
|
|
84
|
-
>>> trigger.message
|
|
85
|
-
'Hello world'
|
|
86
|
-
"""
|
|
87
|
-
|
|
88
|
-
class Config:
|
|
89
|
-
"""
|
|
90
|
-
Configuration for VellumIntegrationTrigger subclasses.
|
|
91
|
-
|
|
92
|
-
Defines how the trigger connects to the integration provider. These settings
|
|
93
|
-
specify which integration and which specific trigger type to use.
|
|
94
|
-
"""
|
|
95
|
-
|
|
96
|
-
provider: ClassVar[VellumIntegrationProviderType]
|
|
97
|
-
integration_name: ClassVar[str]
|
|
98
|
-
slug: ClassVar[str]
|
|
99
|
-
|
|
100
|
-
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
101
|
-
"""Validate that subclasses define required Config class with all required fields."""
|
|
102
|
-
super().__init_subclass__(**kwargs)
|
|
103
|
-
|
|
104
|
-
# Skip validation for the base class itself
|
|
105
|
-
if cls.__name__ == "VellumIntegrationTrigger":
|
|
106
|
-
return
|
|
107
|
-
|
|
108
|
-
# Require Config class with required fields
|
|
109
|
-
if not hasattr(cls, "Config") or cls.Config is VellumIntegrationTrigger.Config:
|
|
110
|
-
raise TypeError(
|
|
111
|
-
f"{cls.__name__} must define a nested Config class. "
|
|
112
|
-
f"Example:\n"
|
|
113
|
-
f" class {cls.__name__}(VellumIntegrationTrigger):\n"
|
|
114
|
-
f" message: str\n"
|
|
115
|
-
f" class Config(VellumIntegrationTrigger.Config):\n"
|
|
116
|
-
f" provider = VellumIntegrationProviderType.COMPOSIO\n"
|
|
117
|
-
f" integration_name = 'SLACK'\n"
|
|
118
|
-
f" slug = 'slack_new_message'"
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
# Validate Config class has required fields
|
|
122
|
-
config_cls = cls.Config
|
|
123
|
-
required_fields = ["provider", "integration_name", "slug"]
|
|
124
|
-
for field in required_fields:
|
|
125
|
-
if not hasattr(config_cls, field):
|
|
126
|
-
raise TypeError(
|
|
127
|
-
f"{cls.__name__}.Config must define '{field}'. " f"Required fields: {', '.join(required_fields)}"
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
def __init__(self, event_data: dict):
|
|
131
|
-
"""
|
|
132
|
-
Initialize trigger with event data from the integration.
|
|
133
|
-
|
|
134
|
-
The trigger dynamically populates its attributes based on the event_data
|
|
135
|
-
dictionary keys. Any key in event_data becomes an accessible attribute.
|
|
136
|
-
|
|
137
|
-
Args:
|
|
138
|
-
event_data: Raw event data from the integration. Keys become trigger attributes.
|
|
139
|
-
|
|
140
|
-
Examples:
|
|
141
|
-
>>> class SlackTrigger(VellumIntegrationTrigger):
|
|
142
|
-
... message: str
|
|
143
|
-
... channel: str
|
|
144
|
-
... user: str
|
|
145
|
-
...
|
|
146
|
-
... class Config(VellumIntegrationTrigger.Config):
|
|
147
|
-
... provider = VellumIntegrationProviderType.COMPOSIO
|
|
148
|
-
... integration_name = "SLACK"
|
|
149
|
-
... slug = "slack_new_message"
|
|
150
|
-
>>> trigger = SlackTrigger(event_data={
|
|
151
|
-
... "message": "Hello",
|
|
152
|
-
... "channel": "C123",
|
|
153
|
-
... "user": "U456"
|
|
154
|
-
... })
|
|
155
|
-
>>> trigger.message
|
|
156
|
-
'Hello'
|
|
157
|
-
>>> trigger.channel
|
|
158
|
-
'C123'
|
|
159
|
-
"""
|
|
160
|
-
super().__init__(event_data)
|
|
161
|
-
|
|
162
|
-
# Dynamically populate instance attributes from event_data.
|
|
163
|
-
# This allows any key in event_data to become an accessible attribute:
|
|
164
|
-
# event_data={"message": "Hi"} → trigger.message == "Hi"
|
|
165
|
-
for key, value in event_data.items():
|
|
166
|
-
setattr(self, key, value)
|
|
167
|
-
|
|
168
|
-
def to_trigger_attribute_values(self) -> Dict["TriggerAttributeReference[Any]", Any]:
|
|
169
|
-
"""
|
|
170
|
-
Materialize attribute descriptor/value pairs for this trigger instance.
|
|
171
|
-
|
|
172
|
-
For VellumIntegrationTrigger, this includes all dynamic attributes from event_data.
|
|
173
|
-
"""
|
|
174
|
-
attribute_values: Dict["TriggerAttributeReference[Any]", Any] = {}
|
|
175
|
-
|
|
176
|
-
# Unlike the base class which iterates over type(self) (predefined annotations),
|
|
177
|
-
# we iterate over event_data keys since our attributes are discovered dynamically
|
|
178
|
-
# from the actual event data received during workflow execution.
|
|
179
|
-
# The base class approach: for reference in type(self)
|
|
180
|
-
# Our approach: for attr_name in self._event_data.keys()
|
|
181
|
-
for attr_name in self._event_data.keys():
|
|
182
|
-
# Get the class-level reference for this attribute (created by __new__ from annotations)
|
|
183
|
-
# Unknown keys can appear in webhook payloads, so gracefully skip them if the
|
|
184
|
-
# trigger class doesn't expose a corresponding reference.
|
|
185
|
-
reference = getattr(type(self), attr_name, None)
|
|
186
|
-
if isinstance(reference, TriggerAttributeReference):
|
|
187
|
-
attribute_values[reference] = getattr(self, attr_name)
|
|
188
|
-
|
|
189
|
-
return attribute_values
|
|
File without changes
|
|
File without changes
|
|
File without changes
|