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.
Files changed (68) hide show
  1. vellum/__init__.py +6 -0
  2. vellum/client/core/client_wrapper.py +2 -2
  3. vellum/client/types/__init__.py +6 -0
  4. vellum/client/types/api_actor_type_enum.py +7 -0
  5. vellum/client/types/api_request_parent_context.py +6 -0
  6. vellum/client/types/external_parent_context.py +2 -0
  7. vellum/client/types/integration_name.py +1 -0
  8. vellum/client/types/integration_trigger_context.py +38 -0
  9. vellum/client/types/node_execution_fulfilled_event.py +2 -0
  10. vellum/client/types/node_execution_initiated_event.py +2 -0
  11. vellum/client/types/node_execution_paused_event.py +2 -0
  12. vellum/client/types/node_execution_rejected_event.py +2 -0
  13. vellum/client/types/node_execution_resumed_event.py +2 -0
  14. vellum/client/types/node_execution_span.py +2 -0
  15. vellum/client/types/node_execution_streaming_event.py +2 -0
  16. vellum/client/types/node_parent_context.py +2 -0
  17. vellum/client/types/parent_context.py +4 -0
  18. vellum/client/types/prompt_deployment_parent_context.py +2 -0
  19. vellum/client/types/scheduled_trigger_context.py +38 -0
  20. vellum/client/types/slim_workflow_execution_read.py +2 -0
  21. vellum/client/types/span_link.py +2 -0
  22. vellum/client/types/workflow_deployment_event_executions_response.py +2 -0
  23. vellum/client/types/workflow_deployment_parent_context.py +2 -0
  24. vellum/client/types/workflow_event_execution_read.py +2 -0
  25. vellum/client/types/workflow_execution_detail.py +2 -0
  26. vellum/client/types/workflow_execution_fulfilled_event.py +2 -0
  27. vellum/client/types/workflow_execution_initiated_event.py +2 -0
  28. vellum/client/types/workflow_execution_paused_event.py +2 -0
  29. vellum/client/types/workflow_execution_rejected_event.py +2 -0
  30. vellum/client/types/workflow_execution_resumed_event.py +2 -0
  31. vellum/client/types/workflow_execution_snapshotted_event.py +2 -0
  32. vellum/client/types/workflow_execution_span.py +2 -0
  33. vellum/client/types/workflow_execution_streaming_event.py +2 -0
  34. vellum/client/types/workflow_parent_context.py +2 -0
  35. vellum/client/types/workflow_sandbox_parent_context.py +2 -0
  36. vellum/types/api_actor_type_enum.py +3 -0
  37. vellum/types/integration_trigger_context.py +3 -0
  38. vellum/types/scheduled_trigger_context.py +3 -0
  39. vellum/workflows/inputs/dataset_row.py +9 -7
  40. vellum/workflows/nodes/displayable/final_output_node/node.py +4 -0
  41. vellum/workflows/nodes/displayable/final_output_node/tests/test_node.py +28 -0
  42. vellum/workflows/nodes/displayable/set_state_node/__init__.py +5 -0
  43. vellum/workflows/nodes/displayable/set_state_node/node.py +71 -0
  44. vellum/workflows/nodes/displayable/set_state_node/tests/__init__.py +0 -0
  45. vellum/workflows/nodes/displayable/set_state_node/tests/test_node.py +212 -0
  46. vellum/workflows/sandbox.py +13 -3
  47. vellum/workflows/tests/test_dataset_row.py +20 -0
  48. vellum/workflows/tests/test_sandbox.py +40 -0
  49. vellum/workflows/tests/triggers/{test_vellum_integration_trigger.py → test_integration_trigger.py} +22 -22
  50. vellum/workflows/triggers/__init__.py +2 -2
  51. vellum/workflows/triggers/base.py +22 -4
  52. vellum/workflows/triggers/integration.py +168 -49
  53. vellum/workflows/triggers/schedule.py +18 -0
  54. vellum/workflows/triggers/tests/test_integration.py +49 -20
  55. vellum/workflows/utils/uuids.py +1 -15
  56. vellum/workflows/workflows/base.py +44 -0
  57. {vellum_ai-1.8.5.dist-info → vellum_ai-1.9.0.dist-info}/METADATA +1 -1
  58. {vellum_ai-1.8.5.dist-info → vellum_ai-1.9.0.dist-info}/RECORD +67 -57
  59. vellum_ee/workflows/display/tests/workflow_serialization/{test_vellum_integration_trigger_serialization.py → test_integration_trigger_serialization.py} +8 -8
  60. vellum_ee/workflows/display/utils/expressions.py +2 -3
  61. vellum_ee/workflows/display/workflows/base_workflow_display.py +9 -9
  62. vellum_ee/workflows/server/virtual_file_loader.py +74 -2
  63. vellum_ee/workflows/tests/test_server.py +81 -66
  64. vellum_ee/workflows/tests/test_virtual_files.py +48 -0
  65. vellum/workflows/triggers/vellum_integration.py +0 -189
  66. {vellum_ai-1.8.5.dist-info → vellum_ai-1.9.0.dist-info}/LICENSE +0 -0
  67. {vellum_ai-1.8.5.dist-info → vellum_ai-1.9.0.dist-info}/WHEEL +0 -0
  68. {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