vellum-ai 0.14.71__py3-none-any.whl → 0.14.72__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.
@@ -18,7 +18,7 @@ class BaseClientWrapper:
18
18
  headers: typing.Dict[str, str] = {
19
19
  "X-Fern-Language": "Python",
20
20
  "X-Fern-SDK-Name": "vellum-ai",
21
- "X-Fern-SDK-Version": "0.14.71",
21
+ "X-Fern-SDK-Version": "0.14.72",
22
22
  }
23
23
  headers["X-API-KEY"] = self.api_key
24
24
  return headers
@@ -573,7 +573,7 @@ class VellumMypyPlugin(Plugin):
573
573
  alias_target = alias.target
574
574
  if (
575
575
  not isinstance(alias_target, Instance)
576
- or not _is_subclass(alias_target.type, "typing.Iterator")
576
+ or not _is_subclass(alias_target.type, "vellum.workflows.events.stream.WorkflowEventGenerator")
577
577
  or not alias_target.args
578
578
  ):
579
579
  return ctx.default_return_type
@@ -5,6 +5,7 @@ from .node import (
5
5
  NodeExecutionRejectedEvent,
6
6
  NodeExecutionStreamingEvent,
7
7
  )
8
+ from .stream import WorkflowEventGenerator
8
9
  from .workflow import (
9
10
  WorkflowEvent,
10
11
  WorkflowEventStream,
@@ -26,4 +27,5 @@ __all__ = [
26
27
  "WorkflowExecutionStreamingEvent",
27
28
  "WorkflowEvent",
28
29
  "WorkflowEventStream",
30
+ "WorkflowEventGenerator",
29
31
  ]
@@ -0,0 +1,28 @@
1
+ from uuid import UUID
2
+ from typing import Generator, Generic, Iterator, TypeVar
3
+
4
+ EventType = TypeVar("EventType")
5
+
6
+
7
+ class WorkflowEventGenerator(Generic[EventType]):
8
+ """
9
+ Generic wrapper for event streams that exposes span_id as a top-level property
10
+ while maintaining iterator compatibility.
11
+ """
12
+
13
+ def __init__(self, event_generator: Generator[EventType, None, None], span_id: UUID):
14
+ self._event_generator = event_generator
15
+ self._span_id = span_id
16
+
17
+ @property
18
+ def span_id(self) -> UUID:
19
+ """The span_id associated with this workflow stream."""
20
+ return self._span_id
21
+
22
+ def __iter__(self) -> Iterator[EventType]:
23
+ """Return self to make this object iterable."""
24
+ return self
25
+
26
+ def __next__(self) -> EventType:
27
+ """Get the next event from the underlying generator."""
28
+ return next(self._event_generator)
@@ -1,5 +1,5 @@
1
1
  from uuid import UUID
2
- from typing import TYPE_CHECKING, Any, Dict, Generator, Generic, Iterable, Literal, Optional, Type, Union
2
+ from typing import TYPE_CHECKING, Any, Dict, Generic, Iterable, Literal, Optional, Type, Union
3
3
  from typing_extensions import TypeGuard
4
4
 
5
5
  from pydantic import field_serializer
@@ -19,6 +19,7 @@ from .node import (
19
19
  NodeExecutionResumedEvent,
20
20
  NodeExecutionStreamingEvent,
21
21
  )
22
+ from .stream import WorkflowEventGenerator
22
23
  from .types import BaseEvent, default_serializer
23
24
 
24
25
  if TYPE_CHECKING:
@@ -193,7 +194,7 @@ WorkflowEvent = Union[
193
194
  WorkflowExecutionSnapshottedEvent,
194
195
  ]
195
196
 
196
- WorkflowEventStream = Generator[WorkflowEvent, None, None]
197
+ WorkflowEventStream = WorkflowEventGenerator[WorkflowEvent]
197
198
 
198
199
  WorkflowExecutionEvent = Union[
199
200
  WorkflowExecutionInitiatedEvent,
@@ -110,7 +110,7 @@ class ToolCallingNode(BaseNode):
110
110
 
111
111
  # Get configuration for this function
112
112
  config = {}
113
- if self.function_configs and function.__name__ in self.function_configs:
113
+ if callable(function) and self.function_configs and function.__name__ in self.function_configs:
114
114
  config = self.function_configs[function.__name__]
115
115
 
116
116
  packages = config.get("packages", None)
@@ -145,7 +145,7 @@ def create_function_node(
145
145
  """
146
146
  if is_workflow_class(function):
147
147
  # Create a class-level wrapper that calls the original function
148
- def execute_function(self) -> BaseNode.Outputs:
148
+ def execute_inline_workflow_function(self) -> BaseNode.Outputs:
149
149
  outputs = self.state.meta.node_outputs.get(tool_router_node.Outputs.text)
150
150
 
151
151
  outputs = json.loads(outputs)
@@ -181,7 +181,7 @@ def create_function_node(
181
181
  f"InlineWorkflowNode_{function.__name__}",
182
182
  (FunctionNode,),
183
183
  {
184
- "run": execute_function,
184
+ "run": execute_inline_workflow_function,
185
185
  "__module__": __name__,
186
186
  },
187
187
  )
@@ -5,7 +5,21 @@ import logging
5
5
  from queue import Empty, Queue
6
6
  from threading import Event as ThreadingEvent, Thread
7
7
  from uuid import UUID, uuid4
8
- from typing import TYPE_CHECKING, Any, Dict, Generic, Iterable, Iterator, Optional, Sequence, Set, Tuple, Type, Union
8
+ from typing import (
9
+ TYPE_CHECKING,
10
+ Any,
11
+ Dict,
12
+ Generator,
13
+ Generic,
14
+ Iterable,
15
+ Iterator,
16
+ Optional,
17
+ Sequence,
18
+ Set,
19
+ Tuple,
20
+ Type,
21
+ Union,
22
+ )
9
23
 
10
24
  from vellum.workflows.constants import undefined
11
25
  from vellum.workflows.context import ExecutionContext, execution_context, get_execution_context
@@ -17,7 +31,7 @@ from vellum.workflows.events import (
17
31
  NodeExecutionRejectedEvent,
18
32
  NodeExecutionStreamingEvent,
19
33
  WorkflowEvent,
20
- WorkflowEventStream,
34
+ WorkflowEventGenerator,
21
35
  WorkflowExecutionFulfilledEvent,
22
36
  WorkflowExecutionInitiatedEvent,
23
37
  WorkflowExecutionRejectedEvent,
@@ -31,6 +45,7 @@ from vellum.workflows.events.node import (
31
45
  )
32
46
  from vellum.workflows.events.types import BaseEvent, NodeParentContext, ParentContext, WorkflowParentContext
33
47
  from vellum.workflows.events.workflow import (
48
+ WorkflowEventStream,
34
49
  WorkflowExecutionFulfilledBody,
35
50
  WorkflowExecutionInitiatedBody,
36
51
  WorkflowExecutionPausedBody,
@@ -730,7 +745,7 @@ class WorkflowRunner(Generic[StateType]):
730
745
  return event.workflow_definition == self.workflow.__class__
731
746
  return False
732
747
 
733
- def stream(self) -> WorkflowEventStream:
748
+ def _generate_events(self) -> Generator[WorkflowEvent, None, None]:
734
749
  background_thread = Thread(
735
750
  target=self._run_background_thread,
736
751
  name=f"{self.workflow.__class__.__name__}.background_thread",
@@ -788,3 +803,6 @@ class WorkflowRunner(Generic[StateType]):
788
803
 
789
804
  self._background_thread_queue.put(None)
790
805
  cancel_thread_kill_switch.set()
806
+
807
+ def stream(self) -> WorkflowEventStream:
808
+ return WorkflowEventGenerator(self._generate_events(), self._initial_state.meta.span_id)
@@ -1,12 +1,14 @@
1
1
  import dataclasses
2
2
  import inspect
3
- from typing import TYPE_CHECKING, Any, Callable, Optional, Type, Union, get_args, get_origin
3
+ from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Type, Union, get_args, get_origin
4
4
 
5
5
  from pydantic import BaseModel
6
6
  from pydantic_core import PydanticUndefined
7
7
  from pydash import snake_case
8
8
 
9
+ from vellum import Vellum
9
10
  from vellum.client.types.function_definition import FunctionDefinition
11
+ from vellum.workflows.utils.vellum_variables import vellum_variable_type_to_openapi_type
10
12
 
11
13
  if TYPE_CHECKING:
12
14
  from vellum.workflows.workflows.base import BaseWorkflow
@@ -86,6 +88,19 @@ def _compile_default_value(default: Any) -> Any:
86
88
  return default
87
89
 
88
90
 
91
+ def _compile_deployment_workflow_input(input_var: Any) -> dict[str, Any]:
92
+ """
93
+ Converts a deployment workflow input variable to a JSON schema type definition.
94
+ """
95
+ primitive_type = vellum_variable_type_to_openapi_type(input_var.type)
96
+ input_schema = {"type": primitive_type}
97
+
98
+ if input_var.default is not None:
99
+ input_schema["default"] = input_var.default.value
100
+
101
+ return input_schema
102
+
103
+
89
104
  def compile_function_definition(function: Callable) -> FunctionDefinition:
90
105
  """
91
106
  Converts a Python function into our Vellum-native FunctionDefinition type.
@@ -151,3 +166,42 @@ def compile_workflow_function_definition(workflow_class: Type["BaseWorkflow"]) -
151
166
  description=workflow_class.__doc__,
152
167
  parameters=parameters,
153
168
  )
169
+
170
+
171
+ def compile_deployment_workflow_function_definition(
172
+ deployment_config: Dict[str, str],
173
+ vellum_client: Vellum,
174
+ ) -> FunctionDefinition:
175
+ """
176
+ Converts a deployment workflow config into our Vellum-native FunctionDefinition type.
177
+
178
+ Args:
179
+ deployment_config: Dict with 'deployment' and 'release_tag' keys
180
+ vellum_client: Vellum client instance
181
+ """
182
+ deployment = deployment_config["deployment"]
183
+ release_tag = deployment_config["release_tag"]
184
+
185
+ workflow_deployment_release = vellum_client.release_reviews.retrieve_workflow_deployment_release(
186
+ deployment, release_tag
187
+ )
188
+
189
+ input_variables = workflow_deployment_release.workflow_version.input_variables
190
+ description = workflow_deployment_release.description
191
+
192
+ properties = {}
193
+ required = []
194
+
195
+ for input_var in input_variables:
196
+ properties[input_var.key] = _compile_deployment_workflow_input(input_var)
197
+
198
+ if input_var.required and input_var.default is None:
199
+ required.append(input_var.key)
200
+
201
+ parameters = {"type": "object", "properties": properties, "required": required}
202
+
203
+ return FunctionDefinition(
204
+ name=deployment,
205
+ description=description,
206
+ parameters=parameters,
207
+ )
@@ -1,14 +1,21 @@
1
1
  from dataclasses import dataclass
2
+ from unittest.mock import Mock
2
3
  from typing import Dict, List, Optional, Union
3
4
 
4
5
  from pydantic import BaseModel
5
6
 
6
7
  from vellum.client.types.function_definition import FunctionDefinition
8
+ from vellum.client.types.string_vellum_value import StringVellumValue
9
+ from vellum.client.types.vellum_variable import VellumVariable
7
10
  from vellum.workflows import BaseWorkflow
8
11
  from vellum.workflows.inputs.base import BaseInputs
9
12
  from vellum.workflows.nodes.bases.base import BaseNode
10
13
  from vellum.workflows.state.base import BaseState
11
- from vellum.workflows.utils.functions import compile_function_definition, compile_workflow_function_definition
14
+ from vellum.workflows.utils.functions import (
15
+ compile_deployment_workflow_function_definition,
16
+ compile_function_definition,
17
+ compile_workflow_function_definition,
18
+ )
12
19
 
13
20
 
14
21
  def test_compile_function_definition__just_name():
@@ -431,3 +438,146 @@ def test_compile_workflow_function_definition__optionals():
431
438
  "required": ["a", "b", "c"],
432
439
  },
433
440
  )
441
+
442
+
443
+ def test_compile_deployment_workflow_function_definition__just_name():
444
+ # GIVEN a mock Vellum client and deployment
445
+ mock_client = Mock()
446
+ mock_release = Mock()
447
+ mock_release.workflow_version.input_variables = []
448
+ mock_release.description = "This is a test deployment"
449
+ mock_client.release_reviews.retrieve_workflow_deployment_release.return_value = mock_release
450
+
451
+ deployment_config = {"deployment": "my_deployment", "release_tag": "latest"}
452
+
453
+ # WHEN compiling the deployment workflow function
454
+ compiled_function = compile_deployment_workflow_function_definition(deployment_config, mock_client)
455
+
456
+ # THEN it should return the compiled function definition (same structure as function test)
457
+ assert compiled_function == FunctionDefinition(
458
+ name="my_deployment",
459
+ description="This is a test deployment",
460
+ parameters={"type": "object", "properties": {}, "required": []},
461
+ )
462
+
463
+
464
+ def test_compile_deployment_workflow_function_definition__all_args():
465
+ # GIVEN a mock Vellum client and deployment
466
+ mock_client = Mock()
467
+ mock_release = Mock()
468
+
469
+ mock_inputs = []
470
+ for key, vellum_type in [
471
+ ("a", "STRING"),
472
+ ("b", "NUMBER"),
473
+ ("c", "JSON"),
474
+ ("d", "CHAT_HISTORY"),
475
+ ("e", "SEARCH_RESULTS"),
476
+ ("f", "ERROR"),
477
+ ("g", "ARRAY"),
478
+ ("h", "FUNCTION_CALL"),
479
+ ("i", "IMAGE"),
480
+ ("j", "AUDIO"),
481
+ ("k", "DOCUMENT"),
482
+ ("l", "NULL"),
483
+ ]:
484
+ mock_input = VellumVariable(
485
+ id=f"input_{key}",
486
+ key=key,
487
+ type=vellum_type,
488
+ required=True,
489
+ default=None,
490
+ )
491
+ mock_inputs.append(mock_input)
492
+
493
+ mock_release.workflow_version.input_variables = mock_inputs
494
+ mock_release.description = "This is a test deployment"
495
+ mock_client.release_reviews.retrieve_workflow_deployment_release.return_value = mock_release
496
+
497
+ deployment_config = {"deployment": "my_deployment", "release_tag": "latest"}
498
+
499
+ # WHEN compiling the deployment workflow function
500
+ compiled_function = compile_deployment_workflow_function_definition(deployment_config, mock_client)
501
+
502
+ # THEN it should return the compiled function definition
503
+ assert compiled_function == FunctionDefinition(
504
+ name="my_deployment",
505
+ description="This is a test deployment",
506
+ parameters={
507
+ "type": "object",
508
+ "properties": {
509
+ "a": {"type": "string"},
510
+ "b": {"type": "number"},
511
+ "c": {"type": "object"},
512
+ "d": {"type": "array"},
513
+ "e": {"type": "array"},
514
+ "f": {"type": "object"},
515
+ "g": {"type": "array"},
516
+ "h": {"type": "object"},
517
+ "i": {"type": "object"},
518
+ "j": {"type": "object"},
519
+ "k": {"type": "object"},
520
+ "l": {"type": "null"},
521
+ },
522
+ "required": ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"],
523
+ },
524
+ )
525
+
526
+
527
+ def test_compile_deployment_workflow_function_definition__defaults():
528
+ # GIVEN a mock Vellum client and deployment
529
+ mock_client = Mock()
530
+ mock_release = Mock()
531
+
532
+ mock_inputs = []
533
+
534
+ mock_input_no_default = VellumVariable(
535
+ id="no_default",
536
+ key="no_default",
537
+ type="STRING",
538
+ required=True,
539
+ default=None,
540
+ )
541
+ mock_inputs.append(mock_input_no_default)
542
+
543
+ mock_input_null_default = VellumVariable(
544
+ id="null_default",
545
+ key="null_default",
546
+ type="STRING",
547
+ required=False,
548
+ default=StringVellumValue(value=None),
549
+ )
550
+ mock_inputs.append(mock_input_null_default)
551
+
552
+ mock_input_actual_default = VellumVariable(
553
+ id="actual_default",
554
+ key="actual_default",
555
+ type="STRING",
556
+ required=False,
557
+ default=StringVellumValue(value="hello world"),
558
+ )
559
+ mock_inputs.append(mock_input_actual_default)
560
+
561
+ mock_release.workflow_version.input_variables = mock_inputs
562
+ mock_release.description = "This is a test deployment"
563
+ mock_client.release_reviews.retrieve_workflow_deployment_release.return_value = mock_release
564
+
565
+ deployment_config = {"deployment": "my_deployment", "release_tag": "latest"}
566
+
567
+ # WHEN compiling the deployment workflow function
568
+ compiled_function = compile_deployment_workflow_function_definition(deployment_config, mock_client)
569
+
570
+ # THEN it should return the compiled function definition with proper default handling
571
+ assert compiled_function == FunctionDefinition(
572
+ name="my_deployment",
573
+ description="This is a test deployment",
574
+ parameters={
575
+ "type": "object",
576
+ "properties": {
577
+ "no_default": {"type": "string"},
578
+ "null_default": {"type": "string", "default": None},
579
+ "actual_default": {"type": "string", "default": "hello world"},
580
+ },
581
+ "required": ["no_default"],
582
+ },
583
+ )
@@ -3,7 +3,10 @@ from typing import List, Optional
3
3
 
4
4
  from vellum import ChatMessage, SearchResult, VellumAudio, VellumDocument, VellumImage
5
5
  from vellum.workflows.types.core import Json
6
- from vellum.workflows.utils.vellum_variables import primitive_type_to_vellum_variable_type
6
+ from vellum.workflows.utils.vellum_variables import (
7
+ primitive_type_to_vellum_variable_type,
8
+ vellum_variable_type_to_openapi_type,
9
+ )
7
10
 
8
11
 
9
12
  @pytest.mark.parametrize(
@@ -31,3 +34,24 @@ from vellum.workflows.utils.vellum_variables import primitive_type_to_vellum_var
31
34
  )
32
35
  def test_primitive_type_to_vellum_variable_type(type_, expected):
33
36
  assert primitive_type_to_vellum_variable_type(type_) == expected
37
+
38
+
39
+ @pytest.mark.parametrize(
40
+ "vellum_type, expected",
41
+ [
42
+ ("STRING", "string"),
43
+ ("NUMBER", "number"),
44
+ ("JSON", "object"),
45
+ ("CHAT_HISTORY", "array"),
46
+ ("SEARCH_RESULTS", "array"),
47
+ ("ERROR", "object"),
48
+ ("ARRAY", "array"),
49
+ ("FUNCTION_CALL", "object"),
50
+ ("IMAGE", "object"),
51
+ ("AUDIO", "object"),
52
+ ("DOCUMENT", "object"),
53
+ ("NULL", "null"),
54
+ ],
55
+ )
56
+ def test_vellum_variable_type_to_openapi_type(vellum_type, expected):
57
+ assert vellum_variable_type_to_openapi_type(vellum_type) == expected
@@ -78,6 +78,36 @@ def primitive_type_to_vellum_variable_type(type_: Union[Type, BaseDescriptor]) -
78
78
  return "JSON"
79
79
 
80
80
 
81
+ def vellum_variable_type_to_openapi_type(vellum_type: VellumVariableType) -> str:
82
+ """Converts a VellumVariableType to a JSON schema primitive type string"""
83
+ if vellum_type == "STRING":
84
+ return "string"
85
+ elif vellum_type == "NUMBER":
86
+ return "number"
87
+ elif vellum_type == "JSON":
88
+ return "object"
89
+ elif vellum_type == "CHAT_HISTORY":
90
+ return "array"
91
+ elif vellum_type == "SEARCH_RESULTS":
92
+ return "array"
93
+ elif vellum_type == "ERROR":
94
+ return "object"
95
+ elif vellum_type == "ARRAY":
96
+ return "array"
97
+ elif vellum_type == "FUNCTION_CALL":
98
+ return "object"
99
+ elif vellum_type == "IMAGE":
100
+ return "object"
101
+ elif vellum_type == "AUDIO":
102
+ return "object"
103
+ elif vellum_type == "DOCUMENT":
104
+ return "object"
105
+ elif vellum_type == "NULL":
106
+ return "null"
107
+ else:
108
+ return "object"
109
+
110
+
81
111
  def _is_type_optionally_equal(type_: Type, target_type: Type) -> bool:
82
112
  if type_ == target_type:
83
113
  return True
@@ -42,6 +42,7 @@ from vellum.workflows.events.node import (
42
42
  NodeExecutionStreamingBody,
43
43
  NodeExecutionStreamingEvent,
44
44
  )
45
+ from vellum.workflows.events.stream import WorkflowEventGenerator
45
46
  from vellum.workflows.events.workflow import (
46
47
  GenericWorkflowEvent,
47
48
  WorkflowExecutionFulfilledBody,
@@ -167,7 +168,7 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
167
168
  WorkflowExecutionPausedEvent,
168
169
  ]
169
170
 
170
- WorkflowEventStream = Generator[WorkflowEvent, None, None]
171
+ WorkflowEventStream = WorkflowEventGenerator[WorkflowEvent]
171
172
 
172
173
  def __init__(
173
174
  self,
@@ -444,7 +445,7 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
444
445
  """
445
446
 
446
447
  should_yield = event_filter or workflow_event_filter
447
- for event in WorkflowRunner(
448
+ runner_stream = WorkflowRunner(
448
449
  self,
449
450
  inputs=inputs,
450
451
  state=state,
@@ -454,9 +455,14 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
454
455
  node_output_mocks=node_output_mocks,
455
456
  max_concurrency=max_concurrency,
456
457
  init_execution_context=self._execution_context,
457
- ).stream():
458
- if should_yield(self.__class__, event):
459
- yield event
458
+ ).stream()
459
+
460
+ def _generate_filtered_events() -> Generator[BaseWorkflow.WorkflowEvent, None, None]:
461
+ for event in runner_stream:
462
+ if should_yield(self.__class__, event):
463
+ yield event
464
+
465
+ return WorkflowEventGenerator(_generate_filtered_events(), runner_stream.span_id)
460
466
 
461
467
  def validate(self) -> None:
462
468
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 0.14.71
3
+ Version: 0.14.72
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -3,22 +3,22 @@ vellum_cli/README.md,sha256=2NudRoLzWxNKqnuVy1JuQ7DerIaxWGYkrH8kMd-asIE,90
3
3
  vellum_cli/__init__.py,sha256=oi-vvNVepu23IBBjhA5uUIDVYBPqQ3EzxWZAPn2S64c,12700
4
4
  vellum_cli/aliased_group.py,sha256=ugW498j0yv4ALJ8vS9MsO7ctDW7Jlir9j6nE_uHAP8c,3363
5
5
  vellum_cli/config.py,sha256=v5BmZ-t_v4Jmqd7KVuQMZF2pRI-rbMspSkVYXIRoTmI,9448
6
- vellum_cli/image_push.py,sha256=Xw_IlItZ27OM5XrWcaqRxXCx4rroV3IaUByl8Ela6U8,10730
6
+ vellum_cli/image_push.py,sha256=rPeSfTlFpdTzwTdnXRL82OVd61qMoGFGLxnLjZLU98Q,10596
7
7
  vellum_cli/init.py,sha256=WpnMXPItPmh0f0bBGIer3p-e5gu8DUGwSArT_FuoMEw,5093
8
8
  vellum_cli/logger.py,sha256=dcM_OmgqXLo93vDYswO5ylyUQQcTfnA5GTd5tbIt3wM,1446
9
9
  vellum_cli/ping.py,sha256=p_BCCRjgPhng6JktuECtkDQLbhopt6JpmrtGoLnLJT8,1161
10
10
  vellum_cli/pull.py,sha256=udYyPlJ6VKDdh78rApNJOZgxHl82fcV6iGnRPSdX1LY,14750
11
- vellum_cli/push.py,sha256=wxRlFu2mYW9SvwODYxwajri1mDQ2be0n-9i0d9QAc30,10194
11
+ vellum_cli/push.py,sha256=TqzaJrI_ty_1GuB3HX3EKfAnbMUF14FWfMMTD06vn2I,10428
12
12
  vellum_cli/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  vellum_cli/tests/conftest.py,sha256=AFYZryKA2qnUuCPBxBKmHLFoPiE0WhBFFej9tNwSHdc,1526
14
14
  vellum_cli/tests/test_config.py,sha256=uvKGDc8BoVyT9_H0Z-g8469zVxomn6Oi3Zj-vK7O_wU,2631
15
- vellum_cli/tests/test_image_push.py,sha256=QGbhCB2DhkepoBUdfhv_ovks-HQ164jmjKFuYvPmJt8,9141
15
+ vellum_cli/tests/test_image_push.py,sha256=xXZjI-CDp166cmk5P1NTa-aogergeIbcdfK4_KBsoHc,9095
16
16
  vellum_cli/tests/test_image_push_error_handling.py,sha256=_Wjfkn1orI2K4Ahzqz4u8T13or7NOX01K4BtcTuTIOM,7107
17
17
  vellum_cli/tests/test_init.py,sha256=8UOc_ThfouR4ja5cCl_URuLk7ohr9JXfCnG4yka1OUQ,18754
18
18
  vellum_cli/tests/test_main.py,sha256=qDZG-aQauPwBwM6A2DIu1494n47v3pL28XakTbLGZ-k,272
19
19
  vellum_cli/tests/test_ping.py,sha256=3ucVRThEmTadlV9LrJdCCrr1Ofj3rOjG6ue0BNR2UC0,2523
20
20
  vellum_cli/tests/test_pull.py,sha256=hxMbW_j0weDDrkzVGpvLpFcwNQdn-fxTv4wBHeYizzc,49904
21
- vellum_cli/tests/test_push.py,sha256=I8XICg3pUb3yxAFLXziVHHf5CRm354LO-uUfwtca3bU,33897
21
+ vellum_cli/tests/test_push.py,sha256=uyWknZ1CBkxxfKVdwcyv7fkGSxvvSNRdJegp5cV-_V4,35167
22
22
  vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  vellum_ee/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -32,7 +32,7 @@ vellum_ee/workflows/display/nodes/get_node_display_class.py,sha256=jI_kUi9LnNLDp
32
32
  vellum_ee/workflows/display/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  vellum_ee/workflows/display/nodes/tests/test_base_node_display.py,sha256=Z4Mf7xLCNiblSbpKI0BrV5modQr-ZcFzhfir_OSyTTs,2997
34
34
  vellum_ee/workflows/display/nodes/types.py,sha256=St1BB6no528OyELGiyRabWao0GGw6mLhstQAvEACbGk,247
35
- vellum_ee/workflows/display/nodes/utils.py,sha256=gxiixuiJi6jrYTV2jmsPsyQfeuwKmrULRb-Lg9c0Aqw,975
35
+ vellum_ee/workflows/display/nodes/utils.py,sha256=sloya5TpXsnot1HURc9L51INwflRqUzHxRVnCS9Cd-4,973
36
36
  vellum_ee/workflows/display/nodes/vellum/__init__.py,sha256=nUIgH2s0-7IbQRNrBhLPyRNe8YIrx3Yo9HeeW-aXXFk,1668
37
37
  vellum_ee/workflows/display/nodes/vellum/api_node.py,sha256=5Uq8x08F64yrBcqbfsVeuoGnTa9eoOPumYzZZrDPmr0,8847
38
38
  vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py,sha256=rJbHZBg9A_v2bjk-R6MfWzShcrS2gcKIOyYGoqwTx8s,6353
@@ -142,7 +142,7 @@ vellum/client/README.md,sha256=CuGUYnaE0Imt0KqQ4sIPaUghCjLHkF3DdEvZWu14-8s,4807
142
142
  vellum/client/__init__.py,sha256=AYopGv2ZRVn3zsU8_km6KOvEHDbXiTPCVuYVI7bWvdA,120166
143
143
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
144
144
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
145
- vellum/client/core/client_wrapper.py,sha256=3ZqkR_DngkaI-fY5PAgWW2c7RZ88oONDwDkWfxAKLBc,1869
145
+ vellum/client/core/client_wrapper.py,sha256=TaQmB_GjRpi-KMiR3qBKQTy1uf4a2n9roQbenkHDwBQ,1869
146
146
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
147
147
  vellum/client/core/file.py,sha256=d4NNbX8XvXP32z8KpK2Xovv33nFfruIrpz0QWxlgpZk,2663
148
148
  vellum/client/core/http_client.py,sha256=Z77OIxIbL4OAB2IDqjRq_sYa5yNYAWfmdhdCSSvh6Y4,19552
@@ -830,7 +830,7 @@ vellum/evaluations/utils/paginator.py,sha256=rEED_BJAXAM6tM1yMwHePNzszjq_tTq4NbQ
830
830
  vellum/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
831
831
  vellum/plugins/pydantic.py,sha256=j4ApZ5WyaoCX6v54qNoShhMFyft_uKWTkyAFz5wbcgg,3619
832
832
  vellum/plugins/utils.py,sha256=cPmxE9R2CK1bki2jKE8rB-G9zMf2pzHjSPDHFPXwd3Q,878
833
- vellum/plugins/vellum_mypy.py,sha256=QTuMSq6PiZW1dyTUZ5Bf1d4XkgFj0TKAgZLP8f4UgL4,27914
833
+ vellum/plugins/vellum_mypy.py,sha256=hfjC2rIxNdQuJa6fIN4PDgODnQ3YaUszyaV2eNbLJlE,27952
834
834
  vellum/prompts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
835
835
  vellum/prompts/blocks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
836
836
  vellum/prompts/blocks/compilation.py,sha256=qeC_4La5auQkm4EyzCMpN34F5R8mjiGcLV7IxKgVf3k,9973
@@ -1509,12 +1509,13 @@ vellum/workflows/environment/__init__.py,sha256=TJz0m9dwIs6YOwCTeuN0HHsU-ecyjc1O
1509
1509
  vellum/workflows/environment/environment.py,sha256=Ck3RPKXJvtMGx_toqYQQQF-ZwXm5ijVwJpEPTeIJ4_Q,471
1510
1510
  vellum/workflows/errors/__init__.py,sha256=tWGPu5xyAU8gRb8_bl0fL7OfU3wxQ9UH6qVwy4X4P_Q,113
1511
1511
  vellum/workflows/errors/types.py,sha256=nUWuniEfrhdtb-_2GzoDGlYnSJ_yuNUGjVkaKLNr-rM,4049
1512
- vellum/workflows/events/__init__.py,sha256=6pxxceJo2dcaRkWtkDAYlUQZ-PHBQSZytIoyuUK48Qw,759
1512
+ vellum/workflows/events/__init__.py,sha256=V4mh766fyA70WvHelm9kfVZGrUgEKcJ9tJt8EepfQYU,832
1513
1513
  vellum/workflows/events/node.py,sha256=TaawUxKaZG8cv_GkiKnJmjOmC4Ic0wMlsxUduF2Rbpw,5446
1514
+ vellum/workflows/events/stream.py,sha256=xhXJTZirFi0xad5neAQNogrIQ4h47fpnKbVC3vCM5Js,889
1514
1515
  vellum/workflows/events/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1515
1516
  vellum/workflows/events/tests/test_event.py,sha256=elg5j3MOdWQe-JjshopqVwd716Jrvtr9P_V2JUGZC60,17866
1516
1517
  vellum/workflows/events/types.py,sha256=LwgFlMRbptJvdPtPO1POUtGtbhGw7BSuvgHxNSgS7N8,4652
1517
- vellum/workflows/events/workflow.py,sha256=i9JSCANjAhf5uc57gYspdII2V2OLItbc0BfT8yB9mF0,7728
1518
+ vellum/workflows/events/workflow.py,sha256=7rb1evKsTCRUdCnisAM5Anc19ZKGxnsmzwOOv6jwWEI,7761
1518
1519
  vellum/workflows/exceptions.py,sha256=NiBiR3ggfmPxBVqD-H1SqmjI-7mIn0EStSN1BqApvCM,1213
1519
1520
  vellum/workflows/expressions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1520
1521
  vellum/workflows/expressions/accessor.py,sha256=1kY0sYfednQ_u0kh1Df2WTsDREVIOzU7UuZc5jvj5xw,2861
@@ -1658,9 +1659,9 @@ vellum/workflows/nodes/experimental/__init__.py,sha256=_tpZGWAZLydcKxfrj1-plrZeT
1658
1659
  vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py,sha256=lsyD9laR9p7kx5-BXGH2gUTM242UhKy8SMV0SR6S2iE,90
1659
1660
  vellum/workflows/nodes/experimental/openai_chat_completion_node/node.py,sha256=cKI2Ls25L-JVt4z4a2ozQa-YBeVy21Z7BQ32Sj7iBPE,10460
1660
1661
  vellum/workflows/nodes/experimental/tool_calling_node/__init__.py,sha256=S7OzT3I4cyOU5Beoz87nPwCejCMP2FsHBFL8OcVmxJ4,118
1661
- vellum/workflows/nodes/experimental/tool_calling_node/node.py,sha256=FkhaJccpCbx2be_IZ5V2v6Lo-jPJ0WgSC5tveLvAW4A,5774
1662
+ vellum/workflows/nodes/experimental/tool_calling_node/node.py,sha256=1Sp-Jnziz8lK5t7snBIwhMk_wKzsXu_aqW7xAeHKc_w,5797
1662
1663
  vellum/workflows/nodes/experimental/tool_calling_node/tests/test_node.py,sha256=7x_o81vT7gWtVw3zDppcWnlJbakgxx_oI1esqhs2gpI,4551
1663
- vellum/workflows/nodes/experimental/tool_calling_node/utils.py,sha256=iiB_Tm3zvElispx_DhnW4eQH1MnK-Y9k44X_PSpp7p8,9923
1664
+ vellum/workflows/nodes/experimental/tool_calling_node/utils.py,sha256=UiQ1o2tDl7EYLHY9HRWLa-MeuYPYfXp8lo2qNFakh2Q,9955
1664
1665
  vellum/workflows/nodes/mocks.py,sha256=a1FjWEIocseMfjzM-i8DNozpUsaW0IONRpZmXBoWlyc,10455
1665
1666
  vellum/workflows/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1666
1667
  vellum/workflows/nodes/tests/test_mocks.py,sha256=mfPvrs75PKcsNsbJLQAN6PDFoVqs9TmQxpdyFKDdO60,7837
@@ -1688,7 +1689,7 @@ vellum/workflows/references/workflow_input.py,sha256=W3rOK1EPd2gYHb04WJwmNm1CUSd
1688
1689
  vellum/workflows/resolvers/__init__.py,sha256=eH6hTvZO4IciDaf_cf7aM2vs-DkBDyJPycOQevJxQnI,82
1689
1690
  vellum/workflows/resolvers/base.py,sha256=WHra9LRtlTuB1jmuNqkfVE2JUgB61Cyntn8f0b0WZg4,411
1690
1691
  vellum/workflows/runner/__init__.py,sha256=i1iG5sAhtpdsrlvwgH6B-m49JsINkiWyPWs8vyT-bqM,72
1691
- vellum/workflows/runner/runner.py,sha256=j2LGHb4fDWR1pB__C1efIMxptq_C49fcOpz893bQHVQ,33645
1692
+ vellum/workflows/runner/runner.py,sha256=itKhjnJBIXrKcI3nKbKsAwcmeP4pn5BowX-LaTPu_VU,33911
1692
1693
  vellum/workflows/sandbox.py,sha256=GVJzVjMuYzOBnSrboB0_6MMRZWBluAyQ2o7syeaeBd0,2235
1693
1694
  vellum/workflows/state/__init__.py,sha256=yUUdR-_Vl7UiixNDYQZ-GEM_kJI9dnOia75TtuNEsnE,60
1694
1695
  vellum/workflows/state/base.py,sha256=WIMJYyuHUrP4zt0Nudk66HAK1L6GgGmsU_GQp7BGE2U,22189
@@ -1710,24 +1711,24 @@ vellum/workflows/types/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
1710
1711
  vellum/workflows/types/tests/test_utils.py,sha256=UnZog59tR577mVwqZRqqWn2fScoOU1H6up0EzS8zYhw,2536
1711
1712
  vellum/workflows/types/utils.py,sha256=axxHbPLsnjhEOnMZrc5YarFd-P2bnsacBDQGNCvY8OY,6367
1712
1713
  vellum/workflows/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1713
- vellum/workflows/utils/functions.py,sha256=FmbOnwl8tLUzbssybZkWRHyUfuXarimYMMD3ZTiUcPE,5390
1714
+ vellum/workflows/utils/functions.py,sha256=Gw9akY-_xU_kJpbkYvQ-07hOOLJpqq0rtch_3FbgFQ0,7169
1714
1715
  vellum/workflows/utils/names.py,sha256=QLUqfJ1tmSEeUwBKTTiv_Qk3QGbInC2RSmlXfGXc8Wo,380
1715
1716
  vellum/workflows/utils/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1716
- vellum/workflows/utils/tests/test_functions.py,sha256=kw-HaYo9JAigFj6sfnAFAbBTLUzPMxB1DeEUY-o10AU,13143
1717
+ vellum/workflows/utils/tests/test_functions.py,sha256=4mhIhZdlZdiboq1NUhJlOvDeRrpG-Oa_HrstvSbjg64,18368
1717
1718
  vellum/workflows/utils/tests/test_names.py,sha256=aOqpyvMsOEK_9mg_-yaNxQDW7QQfwqsYs37PseyLhxw,402
1718
1719
  vellum/workflows/utils/tests/test_uuids.py,sha256=i77ABQ0M3S-aFLzDXHJq_yr5FPkJEWCMBn1HJ3DObrE,437
1719
- vellum/workflows/utils/tests/test_vellum_variables.py,sha256=maI5e7Od7UlpMwlrOrcdlXqnFhonkXGnWq8G2-YQLi8,1155
1720
+ vellum/workflows/utils/tests/test_vellum_variables.py,sha256=vbnKgm41aB5OXlO-ZIPbhQ6xDiZkT8KMxCLqz4zocWY,1791
1720
1721
  vellum/workflows/utils/uuids.py,sha256=DFzPv9RCvsKhvdTEIQyfSek2A31D6S_QcmeLPbgrgTY,739
1721
- vellum/workflows/utils/vellum_variables.py,sha256=UiGlUh0a8vel2FbW3w-xbHxSv_jNutkDdqMVtP_b42A,3385
1722
+ vellum/workflows/utils/vellum_variables.py,sha256=tDWGyE8CfxYaCQQfMwTHqQNgK5V5sJMuhjLsliT8Ar4,4286
1722
1723
  vellum/workflows/vellum_client.py,sha256=xkfoucodxNK5JR2-lbRqZx3xzDgExWkP6kySrpi_Ubc,1079
1723
1724
  vellum/workflows/workflows/__init__.py,sha256=KY45TqvavCCvXIkyCFMEc0dc6jTMOUci93U2DUrlZYc,66
1724
- vellum/workflows/workflows/base.py,sha256=V60RZat8mG0XmMuIjprkHnacD_MpUdxGcN9t4TaP_Pg,24044
1725
+ vellum/workflows/workflows/base.py,sha256=ehJSHDf1vuNjJtKryqKgb5HKGtx_D-wWUiJNBWZ50JU,24347
1725
1726
  vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnadGsrSZGa7t7LpJA,2008
1726
1727
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1727
1728
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=fROqff6AZpCIzaSwOKSdtYy4XR0UZQ6ejxL3RJOSJVs,20447
1728
1729
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1729
- vellum_ai-0.14.71.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1730
- vellum_ai-0.14.71.dist-info/METADATA,sha256=C5GX4tfVPU2-YXu_9tu4RToI1a6SafllfrsjW6SiM9c,5556
1731
- vellum_ai-0.14.71.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1732
- vellum_ai-0.14.71.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1733
- vellum_ai-0.14.71.dist-info/RECORD,,
1730
+ vellum_ai-0.14.72.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1731
+ vellum_ai-0.14.72.dist-info/METADATA,sha256=5rmPI1o0CHUiw7gPjZq2ZSjkiaWzLTbWTxG0pCyP3WA,5556
1732
+ vellum_ai-0.14.72.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1733
+ vellum_ai-0.14.72.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1734
+ vellum_ai-0.14.72.dist-info/RECORD,,
vellum_cli/image_push.py CHANGED
@@ -30,12 +30,8 @@ def image_push_command(
30
30
  if not os.path.exists(source):
31
31
  handle_cli_error(logger, "Dockerfile not found", f"Dockerfile does not exist: {source}")
32
32
 
33
- source_dir = os.path.dirname(source)
34
- dockerfile_name = os.path.basename(source)
35
-
36
33
  build_result = subprocess.run(
37
- ["docker", "buildx", "build", "-f", dockerfile_name, "--platform=linux/amd64", "-t", image, "."],
38
- cwd=source_dir,
34
+ ["docker", "buildx", "build", "-f", source, "--platform=linux/amd64", "-t", image, "."],
39
35
  stdout=subprocess.PIPE,
40
36
  stderr=subprocess.PIPE,
41
37
  )
vellum_cli/push.py CHANGED
@@ -242,6 +242,13 @@ def push_command(
242
242
  {json.dumps(response.proposed_diffs, indent=2)}
243
243
  """
244
244
  ) # type: ignore[attr-defined]
245
+
246
+ error_list = list(workflow_display.errors)
247
+ has_errors = len(error_list) > 0
248
+ has_diffs = response.proposed_diffs is not None and response.proposed_diffs
249
+
250
+ if has_errors or has_diffs:
251
+ exit(1)
245
252
  else:
246
253
  default_api_url = client._client_wrapper._environment.default
247
254
  base_url = default_api_url.split("/v1")[0].replace("//api.", "//app.")
@@ -209,13 +209,12 @@ def test_image_push_with_source_success(
209
209
  "buildx",
210
210
  "build",
211
211
  "-f",
212
- "Dockerfile",
212
+ dockerfile_path,
213
213
  "--platform=linux/amd64",
214
214
  "-t",
215
215
  "myimage:latest",
216
216
  ".",
217
217
  ]
218
- assert build_call[1]["cwd"] == mock_temp_dir
219
218
 
220
219
  assert "Docker build completed successfully" in result.output
221
220
  assert "Image successfully pushed" in result.output
@@ -444,8 +444,8 @@ class ExampleWorkflow(BaseWorkflow):
444
444
  runner = CliRunner(mix_stderr=True)
445
445
  result = runner.invoke(cli_main, ["push", module, "--dry-run"])
446
446
 
447
- # THEN it should succeed
448
- assert result.exit_code == 0
447
+ # THEN it should fail with exit code 1 due to errors
448
+ assert result.exit_code == 1
449
449
 
450
450
  # AND we should have called the push API with the dry-run option
451
451
  vellum_client.workflows.push.assert_called_once()
@@ -459,6 +459,37 @@ class ExampleWorkflow(BaseWorkflow):
459
459
  assert "iterable_item_added" in result.output
460
460
 
461
461
 
462
+ def test_push__dry_run_option_no_errors_returns_success(mock_module, vellum_client):
463
+ """Test that dry-run returns exit code 0 when there are no errors or diffs"""
464
+ # GIVEN a workflow module with a valid workflow (using the same pattern as happy path test)
465
+ temp_dir = mock_module.temp_dir
466
+ module = mock_module.module
467
+ _ensure_workflow_py(temp_dir, module)
468
+
469
+ # AND the push API call returns successfully with no errors and no diffs
470
+ vellum_client.workflows.push.return_value = WorkflowPushResponse(
471
+ workflow_sandbox_id=str(uuid4()),
472
+ proposed_diffs=None,
473
+ )
474
+
475
+ # WHEN calling `vellum push` with dry-run
476
+ runner = CliRunner(mix_stderr=True)
477
+ result = runner.invoke(cli_main, ["push", module, "--dry-run"])
478
+
479
+ # THEN it should succeed with exit code 0
480
+ assert result.exit_code == 0
481
+
482
+ # AND we should have called the push API with the dry-run option
483
+ vellum_client.workflows.push.assert_called_once()
484
+ call_args = vellum_client.workflows.push.call_args.kwargs
485
+ assert call_args["dry_run"] is True
486
+
487
+ # AND the report should be in the output
488
+ assert "## Errors" in result.output
489
+ assert "No errors found." in result.output
490
+ assert "## Proposed Diffs" in result.output
491
+
492
+
462
493
  def test_push__strict_option_returns_diffs(mock_module, vellum_client):
463
494
  # GIVEN a single workflow configured
464
495
  temp_dir = mock_module.temp_dir
@@ -8,11 +8,11 @@ _T = TypeVar("_T")
8
8
 
9
9
 
10
10
  @overload
11
- def raise_if_descriptor(_node_attr: BaseDescriptor[_T]) -> _T: ...
11
+ def raise_if_descriptor(node_attr: BaseDescriptor[_T]) -> _T: ...
12
12
 
13
13
 
14
14
  @overload
15
- def raise_if_descriptor(_node_attr: _T) -> _T: ...
15
+ def raise_if_descriptor(node_attr: _T) -> _T: ...
16
16
 
17
17
 
18
18
  def raise_if_descriptor(node_attr: Union[NodeReference[_T], _T]) -> Optional[_T]: