vellum-ai 1.7.10__py3-none-any.whl → 1.7.12__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.

Potentially problematic release.


This version of vellum-ai might be problematic. Click here for more details.

Files changed (45) hide show
  1. vellum/__init__.py +2 -0
  2. vellum/client/core/client_wrapper.py +2 -2
  3. vellum/client/types/__init__.py +2 -0
  4. vellum/client/types/auth_type_enum.py +5 -0
  5. vellum/client/types/integration_name.py +4 -0
  6. vellum/client/types/slim_integration_auth_config_read.py +2 -0
  7. vellum/client/types/slim_workflow_execution_read.py +3 -3
  8. vellum/client/types/vellum_error_code_enum.py +1 -0
  9. vellum/client/types/vellum_sdk_error_code_enum.py +1 -0
  10. vellum/client/types/workflow_event_execution_read.py +3 -3
  11. vellum/client/types/workflow_execution_event_error_code.py +1 -0
  12. vellum/client/types/workflow_execution_snapshotted_body.py +1 -0
  13. vellum/types/auth_type_enum.py +3 -0
  14. vellum/workflows/events/tests/test_event.py +1 -0
  15. vellum/workflows/events/workflow.py +3 -0
  16. vellum/workflows/exceptions.py +3 -0
  17. vellum/workflows/integrations/mcp_service.py +7 -0
  18. vellum/workflows/integrations/tests/test_mcp_service.py +48 -0
  19. vellum/workflows/loaders/__init__.py +3 -0
  20. vellum/workflows/loaders/base.py +21 -0
  21. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +3 -0
  22. vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py +3 -0
  23. vellum/workflows/tests/triggers/test_vellum_integration_trigger.py +225 -0
  24. vellum/workflows/triggers/__init__.py +2 -1
  25. vellum/workflows/triggers/vellum_integration.py +383 -0
  26. vellum/workflows/types/__init__.py +3 -0
  27. vellum/workflows/types/tests/test_utils.py +11 -0
  28. vellum/workflows/types/trigger_exec_config.py +63 -0
  29. vellum/workflows/types/utils.py +22 -0
  30. vellum/workflows/utils/names.py +20 -0
  31. vellum/workflows/workflows/base.py +13 -1
  32. {vellum_ai-1.7.10.dist-info → vellum_ai-1.7.12.dist-info}/METADATA +1 -1
  33. {vellum_ai-1.7.10.dist-info → vellum_ai-1.7.12.dist-info}/RECORD +45 -37
  34. vellum_cli/pull.py +6 -5
  35. vellum_cli/push.py +35 -2
  36. vellum_cli/tests/test_push.py +122 -0
  37. vellum_ee/workflows/display/tests/workflow_serialization/test_list_vellum_document_serialization.py +65 -0
  38. vellum_ee/workflows/display/utils/events.py +6 -3
  39. vellum_ee/workflows/display/utils/tests/test_events.py +29 -0
  40. vellum_ee/workflows/server/virtual_file_loader.py +15 -4
  41. vellum_ee/workflows/tests/test_serialize_module.py +48 -0
  42. vellum_ee/workflows/tests/test_server.py +105 -0
  43. {vellum_ai-1.7.10.dist-info → vellum_ai-1.7.12.dist-info}/LICENSE +0 -0
  44. {vellum_ai-1.7.10.dist-info → vellum_ai-1.7.12.dist-info}/WHEEL +0 -0
  45. {vellum_ai-1.7.10.dist-info → vellum_ai-1.7.12.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,383 @@
1
+ import json
2
+ from typing import Any, ClassVar, Dict, Optional, Type, cast
3
+
4
+ from vellum.workflows.constants import VellumIntegrationProviderType
5
+ from vellum.workflows.references.trigger import TriggerAttributeReference
6
+ from vellum.workflows.triggers.base import BaseTriggerMeta
7
+ from vellum.workflows.triggers.integration import IntegrationTrigger
8
+ from vellum.workflows.types import ComposioIntegrationTriggerExecConfig
9
+ from vellum.workflows.utils.uuids import uuid4_from_hash
10
+
11
+
12
+ class VellumIntegrationTriggerMeta(BaseTriggerMeta):
13
+ """
14
+ Custom metaclass for VellumIntegrationTrigger that supports dynamic attribute discovery.
15
+
16
+ This metaclass extends BaseTriggerMeta to enable class-level access to attributes
17
+ that aren't statically defined. When accessing an undefined attribute on a
18
+ VellumIntegrationTrigger class, this metaclass will create a TriggerAttributeReference
19
+ dynamically, allowing triggers to work with attributes discovered from integration
20
+ APIs or event payloads.
21
+
22
+ Note: This metaclass intentionally deviates from the standard metaclass pattern used
23
+ in BaseNodeMeta and BaseWorkflowMeta, which generate nested classes (Outputs, Inputs)
24
+ in __new__. Since VellumIntegrationTrigger uses a factory pattern with dynamic
25
+ attributes rather than predefined nested classes, this metaclass focuses on
26
+ attribute discovery via __getattribute__ during workflow definition (when the
27
+ developer references trigger attributes in their workflow code) instead of class
28
+ generation in __new__. This architectural difference is necessary to support the
29
+ dynamic nature of integration triggers where attributes are not known until the
30
+ integration is queried or event data is received.
31
+ """
32
+
33
+ def __getattribute__(cls, name: str) -> Any:
34
+ """
35
+ Override attribute access to support dynamic attribute discovery.
36
+
37
+ For VellumIntegrationTrigger classes generated by the factory, this method
38
+ allows access to any attribute name, creating TriggerAttributeReference objects
39
+ on demand. This enables usage like:
40
+
41
+ SlackMessage = VellumIntegrationTrigger.for_trigger(
42
+ integration_name="SLACK",
43
+ slug="slack_new_message",
44
+ trigger_nano_id="abc123"
45
+ )
46
+ text = SlackMessage.message # Creates reference even though 'message' isn't pre-defined
47
+
48
+ Args:
49
+ name: The attribute name being accessed
50
+
51
+ Returns:
52
+ The attribute value or a dynamically created TriggerAttributeReference
53
+ """
54
+ # Let BaseTriggerMeta handle internal attributes and known attributes
55
+ try:
56
+ return super().__getattribute__(name)
57
+ except AttributeError:
58
+ # For VellumIntegrationTrigger factory-generated classes, create dynamic references
59
+ # Only enable dynamic attribute creation for factory-generated classes, not the base
60
+ # VellumIntegrationTrigger class itself. We check the internal __name__ attribute
61
+ # (e.g., "VellumIntegrationTrigger_COMPOSIO_SLACK_slack_new_message"), not the user-facing
62
+ # variable name (e.g., "SlackNewMessage").
63
+ try:
64
+ is_factory_class = super().__getattribute__("__name__").startswith(
65
+ "VellumIntegrationTrigger_"
66
+ ) and not name.startswith("_")
67
+ except AttributeError:
68
+ is_factory_class = False
69
+
70
+ if is_factory_class:
71
+ trigger_cls = cast(Type["VellumIntegrationTrigger"], cls)
72
+
73
+ # Check cache first
74
+ cache = super().__getattribute__("__trigger_attribute_cache__")
75
+ if name in cache:
76
+ return cache[name]
77
+
78
+ # Generate and store deterministic UUID for this attribute if not already present.
79
+ # This ensures consistent IDs across multiple accesses to the same attribute,
80
+ # which is critical for serialization and state resolution.
81
+ # Use semantic identity (provider|integration|slug) instead of __qualname__ for stability.
82
+ attribute_ids = super().__getattribute__("__trigger_attribute_ids__")
83
+ if name not in attribute_ids:
84
+ # Generate stable ID from trigger semantics, not class naming
85
+ provider = super().__getattribute__("provider")
86
+ integration_name = super().__getattribute__("integration_name")
87
+ slug = super().__getattribute__("slug")
88
+ trigger_identity = f"{provider.value}|{integration_name}|{slug}"
89
+ attribute_ids[name] = uuid4_from_hash(f"{trigger_identity}|{name}")
90
+
91
+ # Create a new dynamic reference for this attribute
92
+ types = (object,)
93
+ reference = TriggerAttributeReference(name=name, types=types, instance=None, trigger_class=trigger_cls)
94
+ cache[name] = reference
95
+ return reference
96
+
97
+ # Not a factory class or starts with _, re-raise the AttributeError
98
+ raise
99
+
100
+
101
+ class VellumIntegrationTrigger(IntegrationTrigger, metaclass=VellumIntegrationTriggerMeta):
102
+ """
103
+ Factory-based trigger for Vellum-managed integration events.
104
+
105
+ VellumIntegrationTrigger provides a pure factory pattern for creating trigger
106
+ classes dynamically based on integration provider, integration name, slug, and
107
+ trigger nano ID. Unlike predefined trigger classes, these triggers are created
108
+ on-demand and support dynamic attribute discovery from the integration API.
109
+
110
+ This design ensures parity with VellumIntegrationToolDefinition and allows users to
111
+ work with any integration trigger without requiring SDK updates for new integrations.
112
+
113
+ Examples:
114
+ Create triggers dynamically for different integrations:
115
+ >>> SlackNewMessage = VellumIntegrationTrigger.for_trigger(
116
+ ... integration_name="SLACK",
117
+ ... slug="slack_new_message",
118
+ ... trigger_nano_id="abc123def456"
119
+ ... )
120
+ >>>
121
+ >>> GithubPush = VellumIntegrationTrigger.for_trigger(
122
+ ... integration_name="GITHUB",
123
+ ... slug="github_push_event",
124
+ ... trigger_nano_id="xyz789ghi012"
125
+ ... )
126
+
127
+ Use in workflow graph:
128
+ >>> class MyWorkflow(BaseWorkflow):
129
+ ... graph = SlackNewMessage >> ProcessMessageNode
130
+
131
+ Reference trigger attributes in nodes:
132
+ >>> class ProcessNode(BaseNode):
133
+ ... class Outputs(BaseNode.Outputs):
134
+ ... text = SlackNewMessage.message
135
+ ... channel = SlackNewMessage.channel
136
+
137
+ Instantiate for testing:
138
+ >>> trigger = SlackNewMessage(event_data={
139
+ ... "message": "Hello world",
140
+ ... "channel": "C123456"
141
+ ... })
142
+ >>> trigger.message
143
+ 'Hello world'
144
+
145
+ Note:
146
+ The factory method generates unique classes with proper __name__ and __module__
147
+ for correct attribute ID generation and serialization. Each factory call with
148
+ the same parameters returns the same class instance (cached).
149
+ """
150
+
151
+ # Class variables that identify this trigger
152
+ provider: ClassVar[VellumIntegrationProviderType]
153
+ integration_name: ClassVar[str]
154
+ slug: ClassVar[str]
155
+ trigger_nano_id: ClassVar[str]
156
+ attributes: ClassVar[Dict[str, Any]]
157
+
158
+ # Cache for generated trigger classes to ensure consistency
159
+ _trigger_class_cache: ClassVar[Dict[tuple, Type["VellumIntegrationTrigger"]]] = {}
160
+
161
+ @classmethod
162
+ def _freeze_attributes(cls, attributes: Dict[str, Any]) -> str:
163
+ """
164
+ Convert attributes dict to hashable string for caching.
165
+
166
+ Attributes must be JSON-serializable since they're sent to the backend
167
+ via ComposioIntegrationTriggerExecConfig. This method fails fast if
168
+ attributes contain non-JSON-serializable types.
169
+
170
+ Args:
171
+ attributes: Dictionary of trigger attributes
172
+
173
+ Returns:
174
+ JSON string representation with sorted keys for deterministic hashing
175
+
176
+ Raises:
177
+ ValueError: If attributes are not JSON-serializable
178
+ """
179
+ if not attributes:
180
+ return ""
181
+
182
+ try:
183
+ # Use json.dumps with sort_keys for deterministic output
184
+ return json.dumps(attributes, sort_keys=True)
185
+ except (TypeError, ValueError) as e:
186
+ raise ValueError(
187
+ f"Trigger attributes must be JSON-serializable (str, int, float, bool, None, list, dict). "
188
+ f"Got non-serializable value: {e}"
189
+ ) from e
190
+
191
+ def __init__(self, event_data: dict):
192
+ """
193
+ Initialize trigger with event data from the integration.
194
+
195
+ The trigger dynamically populates its attributes based on the event_data
196
+ dictionary keys. Any key in event_data becomes an accessible attribute.
197
+
198
+ Args:
199
+ event_data: Raw event data from the integration. Keys become trigger attributes.
200
+
201
+ Examples:
202
+ >>> SlackMessage = VellumIntegrationTrigger.for_trigger(
203
+ ... integration_name="SLACK",
204
+ ... slug="slack_new_message",
205
+ ... trigger_nano_id="abc123"
206
+ ... )
207
+ >>> trigger = SlackMessage(event_data={
208
+ ... "message": "Hello",
209
+ ... "channel": "C123",
210
+ ... "user": "U456"
211
+ ... })
212
+ >>> trigger.message
213
+ 'Hello'
214
+ >>> trigger.channel
215
+ 'C123'
216
+ """
217
+ super().__init__(event_data)
218
+
219
+ # Dynamically populate instance attributes from event_data.
220
+ # This allows any key in event_data to become an accessible attribute:
221
+ # event_data={"message": "Hi"} → trigger.message == "Hi"
222
+ for key, value in event_data.items():
223
+ setattr(self, key, value)
224
+
225
+ def to_trigger_attribute_values(self) -> Dict["TriggerAttributeReference[Any]", Any]:
226
+ """
227
+ Materialize attribute descriptor/value pairs for this trigger instance.
228
+
229
+ For VellumIntegrationTrigger, this includes all dynamic attributes from event_data.
230
+ """
231
+ attribute_values: Dict["TriggerAttributeReference[Any]", Any] = {}
232
+
233
+ # Unlike the base class which iterates over type(self) (predefined annotations),
234
+ # we iterate over event_data keys since our attributes are discovered dynamically
235
+ # from the actual event data received during workflow execution.
236
+ # The base class approach: for reference in type(self)
237
+ # Our approach: for attr_name in self._event_data.keys()
238
+ for attr_name in self._event_data.keys():
239
+ # Get the class-level reference for this attribute
240
+ # This will create it via our custom metaclass if it doesn't exist
241
+ reference = getattr(type(self), attr_name)
242
+ if isinstance(reference, TriggerAttributeReference):
243
+ attribute_values[reference] = getattr(self, attr_name)
244
+
245
+ return attribute_values
246
+
247
+ @classmethod
248
+ def to_exec_config(cls) -> ComposioIntegrationTriggerExecConfig:
249
+ """
250
+ Generate execution configuration for serialization.
251
+
252
+ This method creates a ComposioIntegrationTriggerExecConfig from the trigger
253
+ class's configuration, which is used during serialization to the backend.
254
+
255
+ Returns:
256
+ ComposioIntegrationTriggerExecConfig with all required fields
257
+
258
+ Raises:
259
+ AttributeError: If called on base VellumIntegrationTrigger (not factory class)
260
+
261
+ Examples:
262
+ >>> SlackMessage = VellumIntegrationTrigger.for_trigger(
263
+ ... integration_name="SLACK",
264
+ ... slug="slack_new_message",
265
+ ... trigger_nano_id="abc123",
266
+ ... attributes={"channel": "C123456"}
267
+ ... )
268
+ >>> exec_config = SlackMessage.to_exec_config()
269
+ >>> exec_config.slug
270
+ 'slack_new_message'
271
+ >>> exec_config.attributes
272
+ {'channel': 'C123456'}
273
+ """
274
+ if not hasattr(cls, "slug"):
275
+ raise AttributeError(
276
+ "to_exec_config() can only be called on factory-generated trigger classes. "
277
+ "Use VellumIntegrationTrigger.for_trigger() to create a trigger class first."
278
+ )
279
+
280
+ return ComposioIntegrationTriggerExecConfig(
281
+ provider=cls.provider,
282
+ integration_name=cls.integration_name,
283
+ slug=cls.slug,
284
+ trigger_nano_id=cls.trigger_nano_id,
285
+ attributes=cls.attributes,
286
+ )
287
+
288
+ @classmethod
289
+ def for_trigger(
290
+ cls,
291
+ integration_name: str,
292
+ slug: str,
293
+ trigger_nano_id: str,
294
+ provider: str = "COMPOSIO",
295
+ attributes: Optional[Dict[str, Any]] = None,
296
+ ) -> Type["VellumIntegrationTrigger"]:
297
+ """
298
+ Factory method to create a new trigger class for a specific integration trigger.
299
+
300
+ This method generates a unique trigger class that can be used in workflow graphs
301
+ and node definitions. Each unique combination of provider, integration_name,
302
+ slug, and trigger_nano_id produces the same class instance (cached).
303
+
304
+ Args:
305
+ integration_name: The integration identifier (e.g., "SLACK", "GITHUB")
306
+ slug: The slug of the integration trigger in Composio (e.g., "slack_new_message")
307
+ trigger_nano_id: Composio's unique trigger identifier used for event matching
308
+ provider: The integration provider (default: "COMPOSIO")
309
+ attributes: Optional dict of trigger-specific configuration attributes
310
+ used for filtering events. For example, {"channel": "C123456"} to only
311
+ match events from a specific Slack channel.
312
+
313
+ Returns:
314
+ A new trigger class configured for the specified integration trigger
315
+
316
+ Examples:
317
+ >>> SlackNewMessage = VellumIntegrationTrigger.for_trigger(
318
+ ... integration_name="SLACK",
319
+ ... slug="slack_new_message",
320
+ ... trigger_nano_id="abc123def456",
321
+ ... attributes={"channel": "C123456"}
322
+ ... )
323
+ >>> type(SlackNewMessage).__name__
324
+ 'VellumIntegrationTrigger_COMPOSIO_SLACK_slack_new_message'
325
+ >>>
326
+ >>> # Use in workflow
327
+ >>> class MyWorkflow(BaseWorkflow):
328
+ ... graph = SlackNewMessage >> ProcessNode
329
+
330
+ Note:
331
+ The generated class has proper __name__, __module__, and __qualname__
332
+ for correct serialization and attribute ID generation.
333
+ """
334
+ # Validate and normalize provider
335
+ provider_enum = VellumIntegrationProviderType(provider)
336
+
337
+ # Normalize attributes
338
+ attrs = attributes or {}
339
+
340
+ # Create cache key - include all identifying parameters including attributes
341
+ # Convert attributes dict to a hashable representation for caching
342
+ frozen_attrs = cls._freeze_attributes(attrs)
343
+ cache_key = (
344
+ provider_enum.value,
345
+ integration_name,
346
+ slug,
347
+ trigger_nano_id,
348
+ frozen_attrs,
349
+ )
350
+
351
+ # Return cached class if it exists
352
+ if cache_key in cls._trigger_class_cache:
353
+ return cls._trigger_class_cache[cache_key]
354
+
355
+ # Generate unique class name including provider to avoid collisions across providers
356
+ class_name = f"VellumIntegrationTrigger_{provider_enum.value}_{integration_name}_{slug}"
357
+
358
+ # Create the new trigger class
359
+ trigger_class = type(
360
+ class_name,
361
+ (cls,),
362
+ {
363
+ "provider": provider_enum,
364
+ "integration_name": integration_name,
365
+ "slug": slug,
366
+ "trigger_nano_id": trigger_nano_id,
367
+ "attributes": attrs,
368
+ "__module__": cls.__module__,
369
+ # Explicitly set __qualname__ to match __name__ for deterministic UUID generation.
370
+ # UUIDs are generated from __qualname__, so this must be consistent and unique
371
+ # across different trigger configurations to prevent ID collisions.
372
+ "__qualname__": class_name,
373
+ # Initialize cache attributes that would normally be set by BaseTriggerMeta.__new__
374
+ # Since we're using type() directly, we need to set these ourselves
375
+ "__trigger_attribute_ids__": {},
376
+ "__trigger_attribute_cache__": {},
377
+ },
378
+ )
379
+
380
+ # Cache the generated class
381
+ cls._trigger_class_cache[cache_key] = trigger_class
382
+
383
+ return trigger_class
@@ -1,6 +1,9 @@
1
1
  from .core import CancelSignal, MergeBehavior
2
+ from .trigger_exec_config import BaseIntegrationTriggerExecConfig, ComposioIntegrationTriggerExecConfig
2
3
 
3
4
  __all__ = [
5
+ "BaseIntegrationTriggerExecConfig",
4
6
  "CancelSignal",
7
+ "ComposioIntegrationTriggerExecConfig",
5
8
  "MergeBehavior",
6
9
  ]
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import pytest
2
4
  from typing import Any, ClassVar, Generic, List, TypeVar, Union
3
5
 
@@ -24,6 +26,11 @@ class ExampleClass:
24
26
  mu: list[str]
25
27
 
26
28
 
29
+ class ExamplePEP604Class:
30
+ pep604_union: str | int
31
+ pep604_optional: str | None
32
+
33
+
27
34
  T = TypeVar("T")
28
35
 
29
36
 
@@ -58,6 +65,8 @@ class ExampleNode(BaseNode):
58
65
  (ExampleNode.Outputs, "iota", (str,)),
59
66
  (ExampleClass, "kappa", (Any,)),
60
67
  (ExampleClass, "mu", (list[str],)),
68
+ (ExamplePEP604Class, "pep604_union", (str, int)),
69
+ (ExamplePEP604Class, "pep604_optional", (str, type(None))),
61
70
  ],
62
71
  ids=[
63
72
  "str",
@@ -74,6 +83,8 @@ class ExampleNode(BaseNode):
74
83
  "try_node_output",
75
84
  "any",
76
85
  "list_str_generic",
86
+ "pep604_union",
87
+ "pep604_optional",
77
88
  ],
78
89
  )
79
90
  def test_infer_types(cls, attr_name, expected_type):
@@ -0,0 +1,63 @@
1
+ """
2
+ Execution configuration dataclasses for integration triggers.
3
+
4
+ These classes define the structure of execution configuration data that is
5
+ sent to/from the backend for integration triggers. They are used during
6
+ serialization and deserialization of trigger configurations.
7
+ """
8
+
9
+ from typing import Any, Dict, Literal, Optional
10
+
11
+ from pydantic import Field
12
+
13
+ from vellum.client.core.pydantic_utilities import UniversalBaseModel
14
+ from vellum.workflows.constants import VellumIntegrationProviderType
15
+
16
+
17
+ class BaseIntegrationTriggerExecConfig(UniversalBaseModel):
18
+ """
19
+ Base class for integration trigger execution configurations.
20
+
21
+ This class defines the common structure for all integration trigger exec configs,
22
+ regardless of the provider. Specific providers should extend this class.
23
+ """
24
+
25
+ type: str = Field(..., description="The type of integration trigger exec config")
26
+
27
+
28
+ class ComposioIntegrationTriggerExecConfig(BaseIntegrationTriggerExecConfig):
29
+ """
30
+ Execution configuration for Composio-based integration triggers.
31
+
32
+ This configuration is used to identify and execute triggers through the Composio
33
+ integration provider. It includes the provider type, integration name, slug,
34
+ trigger nano ID, and optional attributes for filtering.
35
+
36
+ Examples:
37
+ >>> config = ComposioIntegrationTriggerExecConfig(
38
+ ... provider="COMPOSIO",
39
+ ... integration_name="SLACK",
40
+ ... slug="slack_new_message",
41
+ ... trigger_nano_id="abc123def456",
42
+ ... attributes={"channel": "C123456"}
43
+ ... )
44
+ >>> config.provider
45
+ <VellumIntegrationProviderType.COMPOSIO: 'COMPOSIO'>
46
+
47
+ Attributes:
48
+ type: Always "COMPOSIO_INTEGRATION_TRIGGER" for this config type
49
+ provider: The integration provider (e.g., COMPOSIO)
50
+ integration_name: The integration identifier (e.g., "SLACK", "GITHUB")
51
+ slug: The slug of the integration trigger in Composio
52
+ trigger_nano_id: Composio's unique trigger identifier used for event matching
53
+ attributes: Optional dictionary of trigger-specific configuration attributes for filtering
54
+ """
55
+
56
+ type: Literal["COMPOSIO_INTEGRATION_TRIGGER"] = "COMPOSIO_INTEGRATION_TRIGGER"
57
+ provider: VellumIntegrationProviderType = Field(..., description="The integration provider (e.g., COMPOSIO)")
58
+ integration_name: str = Field(..., description="The integration name (e.g., 'SLACK', 'GITHUB')")
59
+ slug: str = Field(..., description="The slug of the integration trigger in Composio")
60
+ trigger_nano_id: str = Field(..., description="Composio's unique trigger identifier used for event matching")
61
+ attributes: Optional[Dict[str, Any]] = Field(
62
+ default=None, description="Optional trigger-specific configuration attributes for filtering"
63
+ )
@@ -1,3 +1,4 @@
1
+ import builtins
1
2
  from copy import deepcopy
2
3
  from datetime import datetime
3
4
  import importlib
@@ -68,6 +69,27 @@ def infer_types(object_: Type, attr_name: str, localns: Optional[Dict[str, Any]]
68
69
  args = get_args(object_)
69
70
  type_var_mapping = {t: a for t, a in zip(origin.__parameters__, args)}
70
71
 
72
+ if hasattr(object_, "__annotations__") and attr_name in object_.__annotations__:
73
+ annotation_str = object_.__annotations__[attr_name]
74
+ if isinstance(annotation_str, str) and "|" in annotation_str:
75
+ parts = [part.strip() for part in annotation_str.split("|")]
76
+ types_list: List[Type] = []
77
+ for part in parts:
78
+ if part == "None":
79
+ types_list.append(type(None))
80
+ else:
81
+ try:
82
+ module = importlib.import_module(object_.__module__)
83
+ resolved_type = getattr(module, part, None)
84
+ if resolved_type is None:
85
+ resolved_type = getattr(builtins, part, None)
86
+ if resolved_type is not None and isinstance(resolved_type, type):
87
+ types_list.append(resolved_type)
88
+ except (ImportError, AttributeError):
89
+ pass
90
+ if len(types_list) == len(parts):
91
+ return tuple(types_list)
92
+
71
93
  type_hints = get_type_hints(class_, localns=LOCAL_NS if localns is None else {**LOCAL_NS, **localns})
72
94
  if attr_name in type_hints:
73
95
  type_hint = type_hints[attr_name]
@@ -1,4 +1,7 @@
1
1
  import re
2
+ from typing import Optional
3
+
4
+ from pydash import snake_case
2
5
 
3
6
 
4
7
  def pascal_to_title_case(pascal_str: str) -> str:
@@ -19,3 +22,20 @@ def pascal_to_title_case(pascal_str: str) -> str:
19
22
 
20
23
  def snake_to_title_case(snake_str: str) -> str:
21
24
  return pascal_to_title_case(snake_str.replace("_", " "))
25
+
26
+
27
+ def create_module_name(*, deployment_name: Optional[str] = None, label: Optional[str] = None) -> str:
28
+ """Create a module name from potential workflow metadata.
29
+
30
+ Args:
31
+ deployment_name: Optional deployment name to convert to snake_case
32
+ label: Optional label to convert to snake_case (fallback if deployment_name not provided)
33
+
34
+ Returns:
35
+ Module name in valid python syntax, or empty string if unable to resolve one based on the arguments
36
+ """
37
+ if deployment_name:
38
+ return snake_case(deployment_name)
39
+ elif label:
40
+ return snake_case(label)
41
+ return ""
@@ -4,6 +4,7 @@ from functools import lru_cache
4
4
  import importlib
5
5
  import inspect
6
6
  import logging
7
+ import sys
7
8
  from uuid import UUID, uuid4
8
9
  from typing import (
9
10
  Any,
@@ -68,6 +69,7 @@ from vellum.workflows.exceptions import WorkflowInitializationException
68
69
  from vellum.workflows.executable import BaseExecutable
69
70
  from vellum.workflows.graph import Graph
70
71
  from vellum.workflows.inputs.base import BaseInputs
72
+ from vellum.workflows.loaders.base import BaseWorkflowFinder
71
73
  from vellum.workflows.nodes.bases import BaseNode
72
74
  from vellum.workflows.nodes.mocks import MockNodeExecutionArg
73
75
  from vellum.workflows.outputs import BaseOutputs
@@ -701,7 +703,17 @@ class BaseWorkflow(Generic[InputsType, StateType], BaseExecutable, metaclass=_Ba
701
703
  except SyntaxError as e:
702
704
  raise WorkflowInitializationException(message=f"Syntax Error raised while loading Workflow: {e}") from e
703
705
  except ModuleNotFoundError as e:
704
- raise WorkflowInitializationException(message=f"Workflow module not found: {e}") from e
706
+ error_message = f"Workflow module not found: {e}"
707
+ raw_data = None
708
+ has_namespace_match = False
709
+ for finder in sys.meta_path:
710
+ if isinstance(finder, BaseWorkflowFinder):
711
+ error_message = finder.format_error_message(error_message)
712
+ if hasattr(finder, "namespace") and e.name and finder.namespace in e.name:
713
+ has_namespace_match = True
714
+ if not has_namespace_match:
715
+ raw_data = {"vellum_on_error_action": "CREATE_CUSTOM_IMAGE"}
716
+ raise WorkflowInitializationException(message=error_message, raw_data=raw_data) from e
705
717
  except ImportError as e:
706
718
  raise WorkflowInitializationException(message=f"Invalid import found while loading Workflow: {e}") from e
707
719
  except NameError as e:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 1.7.10
3
+ Version: 1.7.12
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0