vellum-ai 0.14.28__py3-none-any.whl → 0.14.30__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.28",
21
+ "X-Fern-SDK-Version": "0.14.30",
22
22
  }
23
23
  headers["X_API_KEY"] = self.api_key
24
24
  return headers
@@ -30,6 +30,7 @@ class SandboxesClient:
30
30
  prompt_deployment_name: typing.Optional[str] = OMIT,
31
31
  label: typing.Optional[str] = OMIT,
32
32
  release_tags: typing.Optional[typing.Sequence[str]] = OMIT,
33
+ release_description: typing.Optional[str] = OMIT,
33
34
  request_options: typing.Optional[RequestOptions] = None,
34
35
  ) -> DeploymentRead:
35
36
  """
@@ -53,6 +54,9 @@ class SandboxesClient:
53
54
  release_tags : typing.Optional[typing.Sequence[str]]
54
55
  Optionally provide the release tags that you'd like to be associated with the latest release of the created/updated Prompt Deployment.
55
56
 
57
+ release_description : typing.Optional[str]
58
+ Optionally provide a description that details what's new in this Release.
59
+
56
60
  request_options : typing.Optional[RequestOptions]
57
61
  Request-specific configuration.
58
62
 
@@ -82,6 +86,7 @@ class SandboxesClient:
82
86
  "prompt_deployment_name": prompt_deployment_name,
83
87
  "label": label,
84
88
  "release_tags": release_tags,
89
+ "release_description": release_description,
85
90
  },
86
91
  request_options=request_options,
87
92
  omit=OMIT,
@@ -250,6 +255,7 @@ class AsyncSandboxesClient:
250
255
  prompt_deployment_name: typing.Optional[str] = OMIT,
251
256
  label: typing.Optional[str] = OMIT,
252
257
  release_tags: typing.Optional[typing.Sequence[str]] = OMIT,
258
+ release_description: typing.Optional[str] = OMIT,
253
259
  request_options: typing.Optional[RequestOptions] = None,
254
260
  ) -> DeploymentRead:
255
261
  """
@@ -273,6 +279,9 @@ class AsyncSandboxesClient:
273
279
  release_tags : typing.Optional[typing.Sequence[str]]
274
280
  Optionally provide the release tags that you'd like to be associated with the latest release of the created/updated Prompt Deployment.
275
281
 
282
+ release_description : typing.Optional[str]
283
+ Optionally provide a description that details what's new in this Release.
284
+
276
285
  request_options : typing.Optional[RequestOptions]
277
286
  Request-specific configuration.
278
287
 
@@ -310,6 +319,7 @@ class AsyncSandboxesClient:
310
319
  "prompt_deployment_name": prompt_deployment_name,
311
320
  "label": label,
312
321
  "release_tags": release_tags,
322
+ "release_description": release_description,
313
323
  },
314
324
  request_options=request_options,
315
325
  omit=OMIT,
@@ -29,6 +29,7 @@ class WorkflowSandboxesClient:
29
29
  workflow_deployment_name: typing.Optional[str] = OMIT,
30
30
  label: typing.Optional[str] = OMIT,
31
31
  release_tags: typing.Optional[typing.Sequence[str]] = OMIT,
32
+ release_description: typing.Optional[str] = OMIT,
32
33
  request_options: typing.Optional[RequestOptions] = None,
33
34
  ) -> WorkflowDeploymentRead:
34
35
  """
@@ -52,6 +53,9 @@ class WorkflowSandboxesClient:
52
53
  release_tags : typing.Optional[typing.Sequence[str]]
53
54
  Optionally provide the release tags that you'd like to be associated with the latest release of the created/updated Prompt Deployment.
54
55
 
56
+ release_description : typing.Optional[str]
57
+ Optionally provide a description that details what's new in this Release.
58
+
55
59
  request_options : typing.Optional[RequestOptions]
56
60
  Request-specific configuration.
57
61
 
@@ -81,6 +85,7 @@ class WorkflowSandboxesClient:
81
85
  "workflow_deployment_name": workflow_deployment_name,
82
86
  "label": label,
83
87
  "release_tags": release_tags,
88
+ "release_description": release_description,
84
89
  },
85
90
  request_options=request_options,
86
91
  omit=OMIT,
@@ -181,6 +186,7 @@ class AsyncWorkflowSandboxesClient:
181
186
  workflow_deployment_name: typing.Optional[str] = OMIT,
182
187
  label: typing.Optional[str] = OMIT,
183
188
  release_tags: typing.Optional[typing.Sequence[str]] = OMIT,
189
+ release_description: typing.Optional[str] = OMIT,
184
190
  request_options: typing.Optional[RequestOptions] = None,
185
191
  ) -> WorkflowDeploymentRead:
186
192
  """
@@ -204,6 +210,9 @@ class AsyncWorkflowSandboxesClient:
204
210
  release_tags : typing.Optional[typing.Sequence[str]]
205
211
  Optionally provide the release tags that you'd like to be associated with the latest release of the created/updated Prompt Deployment.
206
212
 
213
+ release_description : typing.Optional[str]
214
+ Optionally provide a description that details what's new in this Release.
215
+
207
216
  request_options : typing.Optional[RequestOptions]
208
217
  Request-specific configuration.
209
218
 
@@ -241,6 +250,7 @@ class AsyncWorkflowSandboxesClient:
241
250
  "workflow_deployment_name": workflow_deployment_name,
242
251
  "label": label,
243
252
  "release_tags": release_tags,
253
+ "release_description": release_description,
244
254
  },
245
255
  request_options=request_options,
246
256
  omit=OMIT,
@@ -1,4 +1,5 @@
1
1
  from contextlib import contextmanager
2
+ from dataclasses import field
2
3
  import threading
3
4
  from uuid import UUID
4
5
  from typing import Iterator, Optional, cast
@@ -8,8 +9,8 @@ from vellum.workflows.events.types import ParentContext
8
9
 
9
10
 
10
11
  class ExecutionContext(UniversalBaseModel):
12
+ trace_id: UUID = field(default_factory=lambda: UUID("00000000-0000-0000-0000-000000000000"))
11
13
  parent_context: Optional[ParentContext] = None
12
- trace_id: Optional[UUID] = None
13
14
 
14
15
 
15
16
  _CONTEXT_KEY = "_execution_context"
@@ -37,7 +38,11 @@ def execution_context(
37
38
  ) -> Iterator[None]:
38
39
  """Context manager for handling execution context."""
39
40
  prev_context = get_execution_context()
40
- set_trace_id = prev_context.trace_id or trace_id
41
+ set_trace_id = (
42
+ prev_context.trace_id
43
+ if int(prev_context.trace_id)
44
+ else trace_id or UUID("00000000-0000-0000-0000-000000000000")
45
+ )
41
46
  set_parent_context = parent_context or prev_context.parent_context
42
47
  set_context = ExecutionContext(parent_context=set_parent_context, trace_id=set_trace_id)
43
48
  try:
@@ -1,9 +1,12 @@
1
+ import pytest
1
2
  from uuid import UUID
2
3
  from typing import Optional
3
4
 
4
5
  from vellum.client.types.string_vellum_value_request import StringVellumValueRequest
5
6
  from vellum.core.pydantic_utilities import UniversalBaseModel
7
+ from vellum.workflows.descriptors.tests.test_utils import FixtureState
6
8
  from vellum.workflows.inputs.base import BaseInputs
9
+ from vellum.workflows.nodes import FinalOutputNode
7
10
  from vellum.workflows.nodes.bases.base import BaseNode
8
11
  from vellum.workflows.outputs.base import BaseOutputs
9
12
  from vellum.workflows.state.base import BaseState, StateMeta
@@ -232,3 +235,27 @@ def test_outputs_preserves_non_object_bases():
232
235
  # THEN the output values should be correct
233
236
  assert outputs.foo == "bar"
234
237
  assert outputs.bar == "baz"
238
+
239
+
240
+ @pytest.mark.parametrize(
241
+ "falsy_value,expected_type",
242
+ [
243
+ ("", str),
244
+ (0, int),
245
+ ],
246
+ )
247
+ def test_resolve_value__for_falsy_values(falsy_value, expected_type):
248
+ """Test that falsy values in FinalOutputNode are handled correctly."""
249
+
250
+ class FalsyOutput(FinalOutputNode[FixtureState, expected_type]): # type: ignore
251
+ class Outputs(FinalOutputNode.Outputs):
252
+ value = falsy_value
253
+
254
+ # GIVEN a node with a falsy value
255
+ falsy_node = FalsyOutput(state=FixtureState())
256
+
257
+ # WHEN we run the node
258
+ falsy_output = falsy_node.run()
259
+
260
+ # THEN the output has the correct value
261
+ assert falsy_output.value == falsy_value
@@ -140,8 +140,10 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
140
140
  value=input_value,
141
141
  )
142
142
  )
143
- elif isinstance(input_value, list) and all(
144
- isinstance(message, (ChatMessage, ChatMessageRequest)) for message in input_value
143
+ elif (
144
+ input_value
145
+ and isinstance(input_value, list)
146
+ and all(isinstance(message, (ChatMessage, ChatMessageRequest)) for message in input_value)
145
147
  ):
146
148
  chat_history = [
147
149
  message if isinstance(message, ChatMessage) else ChatMessage.model_validate(message.model_dump())
@@ -91,8 +91,10 @@ class BasePromptDeploymentNode(BasePromptNode, Generic[StateType]):
91
91
  value=input_value,
92
92
  )
93
93
  )
94
- elif isinstance(input_value, list) and all(
95
- isinstance(message, (ChatMessage, ChatMessageRequest)) for message in input_value
94
+ elif (
95
+ input_value
96
+ and isinstance(input_value, list)
97
+ and all(isinstance(message, (ChatMessage, ChatMessageRequest)) for message in input_value)
96
98
  ):
97
99
  chat_history = [
98
100
  (
@@ -44,6 +44,7 @@ def test_inline_prompt_node__json_inputs(vellum_adhoc_prompt_client):
44
44
  "a_list": [1, 2, 3],
45
45
  "a_dataclass": MyDataClass(hello="world"),
46
46
  "a_pydantic": MyPydantic(example="example"),
47
+ "an_empty_list": [],
47
48
  }
48
49
 
49
50
  # AND a known response from invoking an inline prompt
@@ -75,8 +76,9 @@ def test_inline_prompt_node__json_inputs(vellum_adhoc_prompt_client):
75
76
  PromptRequestJsonInput(key="a_list", type="JSON", value=[1, 2, 3]),
76
77
  PromptRequestJsonInput(key="a_dataclass", type="JSON", value={"hello": "world"}),
77
78
  PromptRequestJsonInput(key="a_pydantic", type="JSON", value={"example": "example"}),
79
+ PromptRequestJsonInput(key="an_empty_list", type="JSON", value=[]),
78
80
  ]
79
- assert len(mock_api.call_args.kwargs["input_variables"]) == 4
81
+ assert len(mock_api.call_args.kwargs["input_variables"]) == 5
80
82
 
81
83
 
82
84
  def test_inline_prompt_node__function_definitions(vellum_adhoc_prompt_client):
@@ -61,14 +61,22 @@ def test_run_node__chat_history_input(vellum_client, ChatMessageClass):
61
61
  ]
62
62
 
63
63
 
64
- def test_run_node__any_array_input(vellum_client):
64
+ @pytest.mark.parametrize(
65
+ "input_value",
66
+ [
67
+ ["apple", "banana", "cherry"],
68
+ [],
69
+ ],
70
+ ids=["non_empty_array", "empty_array"],
71
+ )
72
+ def test_run_node__any_array_input(vellum_client, input_value):
65
73
  """Confirm that we can successfully invoke a Prompt Deployment Node that uses any array input"""
66
74
 
67
75
  # GIVEN a Prompt Deployment Node
68
76
  class ExamplePromptDeploymentNode(PromptDeploymentNode):
69
77
  deployment = "example_prompt_deployment"
70
78
  prompt_inputs = {
71
- "fruits": ["apple", "banana", "cherry"],
79
+ "fruits": input_value,
72
80
  }
73
81
 
74
82
  # AND we know what the Prompt Deployment will respond with
@@ -97,7 +105,7 @@ def test_run_node__any_array_input(vellum_client):
97
105
  # AND we should have invoked the Prompt Deployment with the expected inputs
98
106
  call_kwargs = vellum_client.execute_prompt_stream.call_args.kwargs
99
107
  assert call_kwargs["inputs"] == [
100
- JsonInputRequest(name="fruits", value=["apple", "banana", "cherry"]),
108
+ JsonInputRequest(name="fruits", value=input_value),
101
109
  ]
102
110
 
103
111
 
@@ -1,3 +1,4 @@
1
+ from unittest import mock
1
2
  from uuid import uuid4
2
3
  from typing import Any, Iterator, List
3
4
 
@@ -75,6 +76,6 @@ def test_text_prompt_deployment_node__basic(vellum_client):
75
76
  raw_overrides=OMIT,
76
77
  release_tag="LATEST",
77
78
  request_options={
78
- "additional_body_parameters": {"execution_context": {"parent_context": None, "trace_id": None}}
79
+ "additional_body_parameters": {"execution_context": {"parent_context": None, "trace_id": mock.ANY}}
79
80
  },
80
81
  )
@@ -145,7 +145,7 @@ class _BaseOutputsMeta(type):
145
145
  # we iterate through its inheritance hierarchy to find the first base class that has this attribute
146
146
  # and use its mapping.
147
147
  instance = vars(cls).get(name, undefined)
148
- if not instance:
148
+ if instance is undefined:
149
149
  for base in cls.__mro__[1:]:
150
150
  if hasattr(base, name):
151
151
  instance = getattr(base, name)
@@ -30,17 +30,17 @@ class LazyReference(BaseDescriptor[_T], Generic[_T]):
30
30
  from vellum.workflows.descriptors.utils import resolve_value
31
31
 
32
32
  if isinstance(self._get, str):
33
- # The full solution will involve creating a nodes registry on `WorkflowContext`. I want to update
34
- # how WorkflowContext works so that we could just access it directly instead of it needing to be
35
- # passed in, similar to get_workflow_context(). Because we don't want this to slow down p1 issues
36
- # that we are debugging with existing workflows, using the following workaround for now.
33
+ # We are comparing Output string references - when if we want to be exact,
34
+ # should be comparing the Output class themselves
37
35
  for output_reference, value in state.meta.node_outputs.items():
38
36
  if str(output_reference) == self._get:
39
37
  return value
40
38
 
39
+ child_reference = self.resolve(state.meta.parent) if state.meta.parent else None
40
+
41
41
  # Fix typing surrounding the return value of node outputs/output descriptors
42
42
  # https://app.shortcut.com/vellum/story/4783
43
- return undefined # type: ignore[return-value]
43
+ return child_reference or undefined # type: ignore[return-value]
44
44
 
45
45
  return resolve_value(self._get(), state)
46
46
 
@@ -26,3 +26,16 @@ class Store:
26
26
  @property
27
27
  def state_snapshots(self) -> Iterator[BaseState]:
28
28
  return iter(self._state_snapshots)
29
+
30
+
31
+ class EmptyStore(Store):
32
+ """
33
+ A store that does not record any events or state snapshots, for workflows
34
+ that want to opt out of the memory footprint of the traditional store entirely.
35
+ """
36
+
37
+ def append_event(self, event: WorkflowEvent) -> None:
38
+ pass
39
+
40
+ def append_state_snapshot(self, state: BaseState) -> None:
41
+ pass
@@ -165,12 +165,13 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
165
165
  parent_state: Optional[BaseState] = None,
166
166
  emitters: Optional[List[BaseWorkflowEmitter]] = None,
167
167
  resolvers: Optional[List[BaseWorkflowResolver]] = None,
168
+ store: Optional[Store] = None,
168
169
  ):
169
170
  self._parent_state = parent_state
170
171
  self.emitters = emitters or (self.emitters if hasattr(self, "emitters") else [])
171
172
  self.resolvers = resolvers or (self.resolvers if hasattr(self, "resolvers") else [])
172
173
  self._context = context or WorkflowContext()
173
- self._store = Store()
174
+ self._store = store or Store()
174
175
  self._execution_context = self._context.execution_context
175
176
 
176
177
  self.validate()
@@ -488,7 +489,7 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
488
489
  workflow_inputs=workflow_inputs or self.get_default_inputs(),
489
490
  trace_id=execution_context.trace_id,
490
491
  )
491
- if execution_context and execution_context.trace_id
492
+ if execution_context and int(execution_context.trace_id)
492
493
  else StateMeta(
493
494
  parent=self._parent_state,
494
495
  workflow_inputs=workflow_inputs or self.get_default_inputs(),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 0.14.28
3
+ Version: 0.14.30
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -7,7 +7,7 @@ vellum_cli/image_push.py,sha256=8DDvRDJEZ-FukUCqGW1827bg1ybF4xBbx9WyqWYQE-g,6816
7
7
  vellum_cli/init.py,sha256=WpnMXPItPmh0f0bBGIer3p-e5gu8DUGwSArT_FuoMEw,5093
8
8
  vellum_cli/logger.py,sha256=PuRFa0WCh4sAGFS5aqWB0QIYpS6nBWwPJrIXpWxugV4,1022
9
9
  vellum_cli/ping.py,sha256=lWyJw6sziXjyTopTYRdFF5hV-sYPVDdX0yVbG5fzcY4,585
10
- vellum_cli/pull.py,sha256=XrlJqImcqZcr6SRGqJ4x3yyvc_0LHDejBcfeVRpY1mY,9169
10
+ vellum_cli/pull.py,sha256=GTYNJDzEeJ3Z48i6YjL2sF2ERFt4czOR7T6lEoQ_J6Q,9176
11
11
  vellum_cli/push.py,sha256=xjTNbLwOVFNU3kpBrm56Bk5QkSRrJ9z86qceghCzfIA,9655
12
12
  vellum_cli/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  vellum_cli/tests/conftest.py,sha256=AFYZryKA2qnUuCPBxBKmHLFoPiE0WhBFFej9tNwSHdc,1526
@@ -16,7 +16,7 @@ vellum_cli/tests/test_image_push.py,sha256=i3lJuW8nFRwL1M1OF6752IZYvGAFgKmkB2hd_
16
16
  vellum_cli/tests/test_init.py,sha256=8UOc_ThfouR4ja5cCl_URuLk7ohr9JXfCnG4yka1OUQ,18754
17
17
  vellum_cli/tests/test_main.py,sha256=qDZG-aQauPwBwM6A2DIu1494n47v3pL28XakTbLGZ-k,272
18
18
  vellum_cli/tests/test_ping.py,sha256=QtbhYKMYn1DFnDyBij2mkQO32j9KOpZ5Pf0yek7k_Ao,1284
19
- vellum_cli/tests/test_pull.py,sha256=09BkkBoFvqJXIFRxdCu-_a6CE6FtGzqXkXMPaKlcvwE,30178
19
+ vellum_cli/tests/test_pull.py,sha256=qIoft5Pu49Os6760rgtOG80PNYW0ERdF10N1h0380sA,32089
20
20
  vellum_cli/tests/test_push.py,sha256=zDv_Q1hbXtLwmTJDPRAvwDjbuHC09uNRYOy4FQujUow,23476
21
21
  vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -128,7 +128,7 @@ vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
128
128
  vellum/client/__init__.py,sha256=Jv9sI5BNFo2OYA9px_aREFSIp655ryC3eaZSRI6yH1k,117826
129
129
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
130
130
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
131
- vellum/client/core/client_wrapper.py,sha256=UH9Mq4cJvem6l6xfFj0IWt_RPwlM-wLXpcS5F1LSo2k,1869
131
+ vellum/client/core/client_wrapper.py,sha256=n2emxJFfx0tF0nSGCah08qytcpcFnvJBgH2UN0WLCFA,1869
132
132
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
133
133
  vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
134
134
  vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
@@ -173,7 +173,7 @@ vellum/client/resources/organizations/client.py,sha256=Uye92moqjAcOCs4astmuFpT92
173
173
  vellum/client/resources/prompts/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
174
174
  vellum/client/resources/prompts/client.py,sha256=_rNTUjhl_ZF3vyQa_M1BSTrX4DlFXU_SXkwwCEYKD2s,6598
175
175
  vellum/client/resources/sandboxes/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
176
- vellum/client/resources/sandboxes/client.py,sha256=i-6DHap5k6gFcYS-kWI8ayJFVZxb-GENRft6BJwVam4,17158
176
+ vellum/client/resources/sandboxes/client.py,sha256=SG4BV0NG1Ow10mXSu52ybj-c6hR7wcNxfGJK4eiHu_8,17670
177
177
  vellum/client/resources/test_suite_runs/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
178
178
  vellum/client/resources/test_suite_runs/client.py,sha256=tU-N1fEfXQPomt2f058PUNIhnGoMV4vo471PlQanTAs,15128
179
179
  vellum/client/resources/test_suites/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
@@ -184,7 +184,7 @@ vellum/client/resources/workflow_deployments/types/__init__.py,sha256=W7DKJ1nduw
184
184
  vellum/client/resources/workflow_deployments/types/list_workflow_release_tags_request_source.py,sha256=LPETHLX9Ygha_JRT9oWZAZR6clv-W1tTelXzktkTBX8,178
185
185
  vellum/client/resources/workflow_deployments/types/workflow_deployments_list_request_status.py,sha256=FXVkVmGM6DZ2RpTGnZXWJYiVlLQ-K5fDtX3WMaBPaWk,182
186
186
  vellum/client/resources/workflow_sandboxes/__init__.py,sha256=OR3wE3pTgsZlTS-0ukeMWzSuEZF8PszuQTCHDh6JybI,175
187
- vellum/client/resources/workflow_sandboxes/client.py,sha256=4FfB7DCAo8DDd3CDlmRiycMjnZhP4oWEbfGuhtzVfwo,12404
187
+ vellum/client/resources/workflow_sandboxes/client.py,sha256=H6GP3yitYKSSb5D0fSF_dKG467d2NYMQf5bnFo0nSZM,12916
188
188
  vellum/client/resources/workflow_sandboxes/types/__init__.py,sha256=EaGVRU1w6kJiiHrbZOeEa0c3ggjfgv_jBqsyOkCRWOI,212
189
189
  vellum/client/resources/workflow_sandboxes/types/list_workflow_sandbox_examples_request_tag.py,sha256=TEwWit20W3X-zWPPLAhmUG05UudG9gaBSJ4Q4-rNJws,188
190
190
  vellum/client/resources/workflows/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
@@ -1430,7 +1430,7 @@ vellum/version.py,sha256=jq-1PlAYxN9AXuaZqbYk9ak27SgE2lw9Ia5gx1b1gVI,76
1430
1430
  vellum/workflows/README.md,sha256=hZdTKBIcsTKPofK68oPkBhyt0nnRh0csqC12k4FMHHA,3597
1431
1431
  vellum/workflows/__init__.py,sha256=CssPsbNvN6rDhoLuqpEv7MMKGa51vE6dvAh6U31Pcio,71
1432
1432
  vellum/workflows/constants.py,sha256=2yg4_uo5gpqViy3ZLSwfC8qTybleYCtOnhA4Rj6bacM,1310
1433
- vellum/workflows/context.py,sha256=DwSf8lO9NHABiqOoD3exgrjUoRuNsKtutaL5TgRbD-A,1441
1433
+ vellum/workflows/context.py,sha256=jvMuyeRluay8BQa7GX1TqUlmoHLCycAVYKkp87sfXSo,1644
1434
1434
  vellum/workflows/descriptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1435
1435
  vellum/workflows/descriptors/base.py,sha256=bvF3MWsc4Xyw5Z2s1A0fbsfMCebIbPYcGvbQ9uoa_Pg,14655
1436
1436
  vellum/workflows/descriptors/exceptions.py,sha256=gUy4UD9JFUKSeQnQpeuDSLiRqWjWiIsxLahB7p_q3JY,54
@@ -1498,7 +1498,7 @@ vellum/workflows/nodes/bases/__init__.py,sha256=cniHuz_RXdJ4TQgD8CBzoiKDiPxg62Er
1498
1498
  vellum/workflows/nodes/bases/base.py,sha256=eW-3RSkBgtuGY8x2nmbHYiUg_HXS5U57n3k6Fh-dJ9s,15330
1499
1499
  vellum/workflows/nodes/bases/base_adornment_node.py,sha256=afMwJLHK2Ke7sBpceVLnNdZMlU2O-6UgyG7lBt9SAQ8,3039
1500
1500
  vellum/workflows/nodes/bases/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1501
- vellum/workflows/nodes/bases/tests/test_base_node.py,sha256=4SOdZzvugVtN8CIuo5RrapAxSYGXnxUwQ77dXJ64oTU,6295
1501
+ vellum/workflows/nodes/bases/tests/test_base_node.py,sha256=4PGjzFh4ZkvffOn-7xhqzsqfdLsYe_2IGqn002TL4LE,7099
1502
1502
  vellum/workflows/nodes/core/__init__.py,sha256=5zDMCmyt1v0HTJzlUBwq3U9L825yZGZhT9JL18-mRR4,455
1503
1503
  vellum/workflows/nodes/core/error_node/__init__.py,sha256=g7RRnlHhqu4qByfLjBwCunmgGA8dI5gNsjS3h6TwlSI,60
1504
1504
  vellum/workflows/nodes/core/error_node/node.py,sha256=MFHU5vITYSK-L9CuMZ49In2ZeNLWnhZD0f8r5dWvb5Y,1270
@@ -1533,10 +1533,10 @@ vellum/workflows/nodes/displayable/bases/base_prompt_node/__init__.py,sha256=Org
1533
1533
  vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py,sha256=HGNoGLJ9lbqflGdYFDIiuHFyi0iJ-agJu4kkJ7D3dGs,3212
1534
1534
  vellum/workflows/nodes/displayable/bases/inline_prompt_node/__init__.py,sha256=Hl35IAoepRpE-j4cALaXVJIYTYOF3qszyVbxTj4kS1s,82
1535
1535
  vellum/workflows/nodes/displayable/bases/inline_prompt_node/constants.py,sha256=fnjiRWLoRlC4Puo5oQcpZD5Hd-EesxsAo9l5tGAkpZQ,270
1536
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py,sha256=_vBcgtQgtJ9l19X9tsacGR1WKMPHljO_X3OhTYfLFgA,8336
1536
+ vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py,sha256=rga24gkK9_STRhFwhBwGL7oHhTTZvLWS_rXHHrp85p4,8386
1537
1537
  vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1538
1538
  vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py,sha256=ZCXCZs-_OyPk4nqCpuWY-vw87lg92TDZ2tK_gckJ7mg,10450
1539
- vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py,sha256=SSIAktt3Y0Q2ceAvwtrBnaVQNgp5JpdElOZuS9n9GQs,5624
1539
+ vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py,sha256=mjTAN2GERUaoKVsr5h1ecW60Hw_fZ-EYzgaKMZ33P-s,5674
1540
1540
  vellum/workflows/nodes/displayable/bases/search_node.py,sha256=3UtbqY3QO4kzfJHbmUNZGnEEfJmaoiF892u8H6TGjp8,5381
1541
1541
  vellum/workflows/nodes/displayable/bases/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1542
1542
  vellum/workflows/nodes/displayable/bases/tests/test_utils.py,sha256=eqdqbKNRWVMDPevgwLg1i6YK0g4L4bCy-7xCBN5yYZI,3156
@@ -1562,7 +1562,7 @@ vellum/workflows/nodes/displayable/guardrail_node/test_node.py,sha256=1yPIAt4_GW
1562
1562
  vellum/workflows/nodes/displayable/inline_prompt_node/__init__.py,sha256=gSUOoEZLlrx35-tQhSAd3An8WDwBqyiQh-sIebLU9wU,74
1563
1563
  vellum/workflows/nodes/displayable/inline_prompt_node/node.py,sha256=8RXZqWMzViUjFfbpmcy1gkSsKnEpci8BGwsuPYv4xMQ,3380
1564
1564
  vellum/workflows/nodes/displayable/inline_prompt_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1565
- vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py,sha256=D06GymJGoMt9StKpTzqYk8wvmRedkvcRdYUHPUznvK4,10554
1565
+ vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py,sha256=2Xg37rrzWA5-LrLjO3yQQN0hMn6gDQWyPv6Lye64ujQ,10663
1566
1566
  vellum/workflows/nodes/displayable/merge_node/__init__.py,sha256=J8IC08dSH7P76wKlNuxe1sn7toNGtSQdFirUbtPDEs0,60
1567
1567
  vellum/workflows/nodes/displayable/merge_node/node.py,sha256=nZtGGVAvY4fvGg8vwV6sTQ8_QLRnigeXt0vf2FL272A,450
1568
1568
  vellum/workflows/nodes/displayable/note_node/__init__.py,sha256=KWA3P4fyYJ-fOTky8qNGlcOotQ-HeHJ9AjZt6mRQmCE,58
@@ -1570,7 +1570,7 @@ vellum/workflows/nodes/displayable/note_node/node.py,sha256=sIN1VBQ7zeT3GhN0kupX
1570
1570
  vellum/workflows/nodes/displayable/prompt_deployment_node/__init__.py,sha256=krX1Hds-TSVYZsx0wJFX4wsAKkEFYOX1ifwRGiIM-EA,82
1571
1571
  vellum/workflows/nodes/displayable/prompt_deployment_node/node.py,sha256=pb-KbrnfTRL7mmNtVAMmiCiys8raXkl5Od7sIu682xU,2707
1572
1572
  vellum/workflows/nodes/displayable/prompt_deployment_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1573
- vellum/workflows/nodes/displayable/prompt_deployment_node/tests/test_node.py,sha256=D7J203inNoSw87CT94eZ3eQlUkO2jil1P3Fvz5gIHvM,5666
1573
+ vellum/workflows/nodes/displayable/prompt_deployment_node/tests/test_node.py,sha256=mHSecwE8bcwduM5wNKwDTzlLeh7ECdEEuT86BDgByPY,5798
1574
1574
  vellum/workflows/nodes/displayable/search_node/__init__.py,sha256=hpBpvbrDYf43DElRZFLzieSn8weXiwNiiNOJurERQbs,62
1575
1575
  vellum/workflows/nodes/displayable/search_node/node.py,sha256=_VHHuTNN4icZBgc7O5U9SVKrv1zgKipU72fOtxTyrQU,1453
1576
1576
  vellum/workflows/nodes/displayable/search_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1582,7 +1582,7 @@ vellum/workflows/nodes/displayable/subworkflow_deployment_node/tests/test_node.p
1582
1582
  vellum/workflows/nodes/displayable/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1583
1583
  vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py,sha256=LaxohBcKfSW2PSiBBlx67FdW_q4YC2BM2ouH-vuGPAA,4700
1584
1584
  vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py,sha256=VepO5z1277c1y5N6LLIC31nnWD1aak2m5oPFplfJHHs,6935
1585
- vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py,sha256=tYv-0kOuHTxmbnNTUd8jT0y4zrNVRchexwwL8uk3y50,2598
1585
+ vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py,sha256=sGlAzTgKz8OsqeT3e7FbQyPpvs_2Fk9_jfD6BRyc6M0,2628
1586
1586
  vellum/workflows/nodes/experimental/README.md,sha256=eF6DfIL8t-HbF9-mcofOMymKrraiBHDLKTlnBa51ZiE,284
1587
1587
  vellum/workflows/nodes/experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1588
1588
  vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py,sha256=lsyD9laR9p7kx5-BXGH2gUTM242UhKy8SMV0SR6S2iE,90
@@ -1593,7 +1593,7 @@ vellum/workflows/nodes/tests/test_mocks.py,sha256=mfPvrs75PKcsNsbJLQAN6PDFoVqs9T
1593
1593
  vellum/workflows/nodes/tests/test_utils.py,sha256=qNB6ApIsnFtE_9HDaEah9KD-zX8e10FhDixewS1uRcc,5199
1594
1594
  vellum/workflows/nodes/utils.py,sha256=Tc3TAmAytb-gi30BAyzGY7DG0uS1_7KTGYdjrvKUSt0,8362
1595
1595
  vellum/workflows/outputs/__init__.py,sha256=AyZ4pRh_ACQIGvkf0byJO46EDnSix1ZCAXfvh-ms1QE,94
1596
- vellum/workflows/outputs/base.py,sha256=b4Dnha1miKu3uFJYptKKflIHNuajPF2BNKy0BTt8Tjc,8622
1596
+ vellum/workflows/outputs/base.py,sha256=jOvSr44GEmtldits-bysJoxme7js6aN_LkdV-PpA7Dk,8631
1597
1597
  vellum/workflows/ports/__init__.py,sha256=bZuMt-R7z5bKwpu4uPW7LlJeePOQWmCcDSXe5frUY5g,101
1598
1598
  vellum/workflows/ports/node_ports.py,sha256=g4A-8iUAvEJSkaWppbvzAR8XU02R9U-qLN4rP2Kq4Aw,2743
1599
1599
  vellum/workflows/ports/port.py,sha256=eI2SOZPZ5rsC3jMsxW6Rbn0NpaYQsrR7AapiIbbiy8Q,3635
@@ -1604,7 +1604,7 @@ vellum/workflows/references/environment_variable.py,sha256=-gfOcdYwVp9ztSUYz6h2W
1604
1604
  vellum/workflows/references/execution_count.py,sha256=JILHqt8ELdc9ct-WsVCA5X-rKiP1rmJODw-XTf4kpHI,722
1605
1605
  vellum/workflows/references/external_input.py,sha256=c_4SojTpykCSbGS1Pjmx9FfquyYGMPksoj0AbrWv7Go,2064
1606
1606
  vellum/workflows/references/input.py,sha256=3INu-TLTi4dziWmva6LO3WvgDlPzsjayUx61cVvqLJA,325
1607
- vellum/workflows/references/lazy.py,sha256=abGEQ7EUKEAZqUrQtt0YLq6Ipi7vqc-oDJhUkwAFW8c,3116
1607
+ vellum/workflows/references/lazy.py,sha256=jgUYmgt-yAybzPf_R-74MzdU8VuNwMYI8EQqrj9lVR0,2948
1608
1608
  vellum/workflows/references/node.py,sha256=LP854wDVs-9I_aZ7-nkbwXqL2H7W2_3LED2e9FixNS8,1418
1609
1609
  vellum/workflows/references/output.py,sha256=-g97wkZDf-6_l_-NM4C_8v1VBt2c7NCPhtrG2lBLKSc,2808
1610
1610
  vellum/workflows/references/state_value.py,sha256=bInUF0A3Pt4-zhA0f6LdSuyv8tz7n5QRkHAEn4gsmqI,711
@@ -1620,7 +1620,7 @@ vellum/workflows/state/__init__.py,sha256=yUUdR-_Vl7UiixNDYQZ-GEM_kJI9dnOia75Ttu
1620
1620
  vellum/workflows/state/base.py,sha256=Vkhneko3VlQrPsMLU1PYSzXU_W1u7_AraJsghiv5O-4,15512
1621
1621
  vellum/workflows/state/context.py,sha256=yePVr4CCTQn5bjo1697JOO24fKFQpVNzooL07xL4gL0,2702
1622
1622
  vellum/workflows/state/encoder.py,sha256=TnOQojc5lTQ83g9QbpA4UCqShJvutmTMxbpKt-9gNe4,1911
1623
- vellum/workflows/state/store.py,sha256=VYGBQgN1bpd1as5eGiouV_7scg8QsRs4_1aqZAGIsRQ,793
1623
+ vellum/workflows/state/store.py,sha256=uVe-oN73KwGV6M6YLhwZMMUQhzTQomsVfVnb8V91gVo,1147
1624
1624
  vellum/workflows/state/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1625
1625
  vellum/workflows/state/tests/test_state.py,sha256=jBynFR4m74Vn51DdmKBLkxb1loTy1CnJPtzPmdAFQUo,5159
1626
1626
  vellum/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1645,13 +1645,13 @@ vellum/workflows/utils/uuids.py,sha256=DFzPv9RCvsKhvdTEIQyfSek2A31D6S_QcmeLPbgrg
1645
1645
  vellum/workflows/utils/vellum_variables.py,sha256=UiGlUh0a8vel2FbW3w-xbHxSv_jNutkDdqMVtP_b42A,3385
1646
1646
  vellum/workflows/vellum_client.py,sha256=GxOy3dX6A04xiY69vPv1S4YGuQ_TMxwHi6WRMimQBBE,762
1647
1647
  vellum/workflows/workflows/__init__.py,sha256=KY45TqvavCCvXIkyCFMEc0dc6jTMOUci93U2DUrlZYc,66
1648
- vellum/workflows/workflows/base.py,sha256=VPG6XqBxxqjQBKM1okUkg10JyDlybltfZiANgy7iGFw,22696
1648
+ vellum/workflows/workflows/base.py,sha256=DycL6YgN8CyaHub2gG8r9NB5PVxR9jP0wV4J09iaBnE,22749
1649
1649
  vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnadGsrSZGa7t7LpJA,2008
1650
1650
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1651
1651
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=tCxrV3QBHL8wfdEO3bvKteDdw32xBlUl1_WxkAwaONw,8344
1652
1652
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1653
- vellum_ai-0.14.28.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1654
- vellum_ai-0.14.28.dist-info/METADATA,sha256=N7nbaIH39gbgUyBqZttgkemMWEoUpoQUJLtpd6bxTl0,5484
1655
- vellum_ai-0.14.28.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1656
- vellum_ai-0.14.28.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1657
- vellum_ai-0.14.28.dist-info/RECORD,,
1653
+ vellum_ai-0.14.30.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1654
+ vellum_ai-0.14.30.dist-info/METADATA,sha256=RwawNldGcpTbIALzGk6SBEVUdlVisV1xiZTdYAdfLRs,5484
1655
+ vellum_ai-0.14.30.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1656
+ vellum_ai-0.14.30.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1657
+ vellum_ai-0.14.30.dist-info/RECORD,,
vellum_cli/pull.py CHANGED
@@ -9,7 +9,6 @@ from dotenv import load_dotenv
9
9
  from pydash import snake_case
10
10
 
11
11
  from vellum.client.core.pydantic_utilities import UniversalBaseModel
12
- from vellum.utils.uuid import is_valid_uuid
13
12
  from vellum.workflows.vellum_client import create_vellum_client
14
13
  from vellum_cli.config import VellumCliConfig, WorkflowConfig, load_vellum_cli_config
15
14
  from vellum_cli.logger import load_cli_logger
@@ -31,6 +30,7 @@ class RunnerConfig(UniversalBaseModel):
31
30
  class PullContentsMetadata(UniversalBaseModel):
32
31
  label: Optional[str] = None
33
32
  runner_config: Optional[RunnerConfig] = None
33
+ deployment_name: Optional[str] = None
34
34
 
35
35
 
36
36
  def _resolve_workflow_config(
@@ -78,13 +78,8 @@ def _resolve_workflow_config(
78
78
  pk=workflow_config.workflow_sandbox_id,
79
79
  )
80
80
  elif workflow_deployment:
81
- module = (
82
- f"workflow_{workflow_deployment.split('-')[0]}"
83
- if is_valid_uuid(workflow_deployment)
84
- else snake_case(workflow_deployment)
85
- )
86
81
  workflow_config = WorkflowConfig(
87
- module=module,
82
+ module="",
88
83
  )
89
84
  config.workflows.append(workflow_config)
90
85
  return WorkflowConfigResolutionResult(
@@ -169,7 +164,8 @@ def pull_command(
169
164
  workflow_config.container_image_tag = pull_contents_metadata.runner_config.container_image_tag
170
165
  if workflow_config.container_image_name and not workflow_config.container_image_tag:
171
166
  workflow_config.container_image_tag = "latest"
172
-
167
+ if not workflow_config.module and workflow_deployment and pull_contents_metadata.deployment_name:
168
+ workflow_config.module = snake_case(pull_contents_metadata.deployment_name)
173
169
  if not workflow_config.module and pull_contents_metadata.label:
174
170
  workflow_config.module = snake_case(pull_contents_metadata.label)
175
171
 
@@ -338,7 +338,16 @@ def test_pull__workflow_deployment_with_no_config(vellum_client):
338
338
  workflow_deployment = "my-deployment"
339
339
 
340
340
  # AND the workflow pull API call returns a zip file
341
- vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
341
+ vellum_client.workflows.pull.return_value = iter(
342
+ [
343
+ _zip_file_map(
344
+ {
345
+ "workflow.py": "print('hello')",
346
+ "metadata.json": json.dumps({"deployment_name": workflow_deployment, "label": "Some Label"}),
347
+ }
348
+ )
349
+ ]
350
+ )
342
351
 
343
352
  # AND we are currently in a new directory
344
353
  current_dir = os.getcwd()
@@ -822,3 +831,47 @@ def test_pull__multiple_instances_of_same_module__keep_when_pulling_another_modu
822
831
  with open(lock_json) as f:
823
832
  lock_data = json.load(f)
824
833
  assert len(lock_data["workflows"]) == 3
834
+
835
+
836
+ def test_pull__module_name_from_deployment_name(vellum_client):
837
+ # GIVEN a workflow deployment
838
+ workflow_deployment = "test-workflow-deployment-id"
839
+
840
+ # AND the workflow pull API call returns a zip file with metadata containing a deployment_name
841
+ deployment_name = "Test Deployment"
842
+ vellum_client.workflows.pull.return_value = iter(
843
+ [
844
+ _zip_file_map(
845
+ {
846
+ "workflow.py": "print('hello')",
847
+ "metadata.json": json.dumps({"deployment_name": deployment_name, "label": "Some Label"}),
848
+ }
849
+ )
850
+ ]
851
+ )
852
+
853
+ # AND we are currently in a new directory
854
+ current_dir = os.getcwd()
855
+ temp_dir = tempfile.mkdtemp()
856
+ os.chdir(temp_dir)
857
+
858
+ # WHEN the user runs the pull command with the workflow deployment
859
+ runner = CliRunner()
860
+ result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", workflow_deployment])
861
+ os.chdir(current_dir)
862
+
863
+ # THEN the command returns successfully
864
+ assert result.exit_code == 0
865
+
866
+ # AND the module name is derived from the deployment_name in metadata.json (snake_cased)
867
+ vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
868
+ assert os.path.exists(vellum_lock_json)
869
+ with open(vellum_lock_json) as f:
870
+ lock_data = json.loads(f.read())
871
+ assert lock_data["workflows"][0]["module"] == "test_deployment"
872
+
873
+ # AND the workflow.py file is written to the module directory with the correct name
874
+ workflow_py = os.path.join(temp_dir, "test_deployment", "workflow.py")
875
+ assert os.path.exists(workflow_py)
876
+ with open(workflow_py) as f:
877
+ assert f.read() == "print('hello')"