vellum-ai 0.14.29__py3-none-any.whl → 0.14.31__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.29",
21
+ "X-Fern-SDK-Version": "0.14.31",
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,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
@@ -227,7 +227,8 @@ class CodeExecutionNode(BaseNode[StateType], Generic[StateType, _OutputType], me
227
227
  )
228
228
 
229
229
  root = inspect.getfile(self.__class__)
230
- code = read_file_from_path(node_filepath=root, script_filepath=self.filepath)
230
+
231
+ code = read_file_from_path(node_filepath=root, script_filepath=self.filepath, context=self._context)
231
232
  if not code:
232
233
  raise NodeException(
233
234
  message=f"Filepath '{self.filepath}' does not exist",
@@ -1,19 +1,33 @@
1
1
  import io
2
2
  import os
3
- from typing import Any, Tuple, Union
3
+ from typing import Any, Optional, Tuple, Union
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
7
7
  from vellum.workflows.errors.types import WorkflowErrorCode
8
8
  from vellum.workflows.exceptions import NodeException
9
9
  from vellum.workflows.nodes.utils import cast_to_output_type
10
+ from vellum.workflows.state.context import WorkflowContext
10
11
  from vellum.workflows.types.core import EntityInputsInterface
11
12
 
12
13
 
13
- def read_file_from_path(node_filepath: str, script_filepath: str) -> Union[str, None]:
14
+ def read_file_from_path(
15
+ node_filepath: str, script_filepath: str, context: Optional[WorkflowContext] = None
16
+ ) -> Union[str, None]:
14
17
  node_filepath_dir = os.path.dirname(node_filepath)
15
18
  full_filepath = os.path.join(node_filepath_dir, script_filepath)
16
19
 
20
+ # If dynamic file loader is present, try and read the code from there
21
+ if context and context.generated_files:
22
+ # Strip out namespace
23
+ normalized_path = os.path.normpath(full_filepath)
24
+ stripped_node_filepath = "/".join(normalized_path.split("/")[1:])
25
+
26
+ code = context.generated_files.get(stripped_node_filepath, None)
27
+ if code is not None:
28
+ return code
29
+
30
+ # Default logic for reading from filesystem
17
31
  try:
18
32
  with open(full_filepath) as file:
19
33
  return file.read()
@@ -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)
@@ -19,6 +19,7 @@ class WorkflowContext:
19
19
  *,
20
20
  vellum_client: Optional[Vellum] = None,
21
21
  execution_context: Optional[ExecutionContext] = None,
22
+ generated_files: Optional[dict[str, str]] = None,
22
23
  ):
23
24
  self._vellum_client = vellum_client
24
25
  self._event_queue: Optional[Queue["WorkflowEvent"]] = None
@@ -26,6 +27,7 @@ class WorkflowContext:
26
27
  self._execution_context = get_execution_context()
27
28
  if not self._execution_context.parent_context and execution_context:
28
29
  self._execution_context = execution_context
30
+ self._generated_files = generated_files
29
31
 
30
32
  @cached_property
31
33
  def vellum_client(self) -> Vellum:
@@ -38,6 +40,10 @@ class WorkflowContext:
38
40
  def execution_context(self) -> ExecutionContext:
39
41
  return self._execution_context
40
42
 
43
+ @cached_property
44
+ def generated_files(self) -> Optional[dict[str, str]]:
45
+ return self._generated_files
46
+
41
47
  @cached_property
42
48
  def node_output_mocks_map(self) -> Dict[Type[BaseOutputs], List[MockNodeExecution]]:
43
49
  return self._node_output_mocks_map
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 0.14.29
3
+ Version: 0.14.31
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=Cz4_LWeaMNRGw4qvKpUBFnOpY6PXzJJ-9d7NU90S9Jc,9629
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=asWPuOOeGY1Tj_JGu574A3-pnNKCDY0HqjaSWI0BXss,32824
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
@@ -121,14 +121,14 @@ vellum_ee/workflows/tests/local_workflow/nodes/final_output.py,sha256=ZX7zBv87zi
121
121
  vellum_ee/workflows/tests/local_workflow/nodes/templating_node.py,sha256=NQwFN61QkHfI3Vssz-B0NKGfupK8PU0FDSAIAhYBLi0,325
122
122
  vellum_ee/workflows/tests/local_workflow/workflow.py,sha256=A4qOzOPNwePYxWbcAgIPLsmrVS_aVEZEc-wULSv787Q,393
123
123
  vellum_ee/workflows/tests/test_display_meta.py,sha256=C25dErwghPNXio49pvSRxyOuc96srH6eYEwTAWdE2zY,2258
124
- vellum_ee/workflows/tests/test_server.py,sha256=Ll4o9gg0Q4r8uX6Kt8LWgIz0u2zLwPiZxl3TuqoZpxg,4707
124
+ vellum_ee/workflows/tests/test_server.py,sha256=Q9XD93jMwYjZOkcwMPNM2F4BWvcY71wxkYp8HgMgGFU,4435
125
125
  vellum_ee/workflows/tests/test_virtual_files.py,sha256=TJEcMR0v2S8CkloXNmCHA0QW0K6pYNGaIjraJz7sFvY,2762
126
126
  vellum/__init__.py,sha256=88-79I29hBTQvR1uH_BOCGMWuj2a4Nx82R_8KIESg28,40470
127
127
  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=WRK8WU4WFDaADxcUMi93lvUkCQp0McOsjxKX1PJ4Zyc,1869
131
+ vellum/client/core/client_wrapper.py,sha256=PGwx4aPEP3yRHu1laYbSTTdEDpTOYg6DZU5rt8u-QZo,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
@@ -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
@@ -1543,12 +1543,12 @@ vellum/workflows/nodes/displayable/bases/tests/test_utils.py,sha256=eqdqbKNRWVMD
1543
1543
  vellum/workflows/nodes/displayable/bases/types.py,sha256=C37B2Qh2YP7s7pUjd-EYKc2Zl1TbnCgI_mENuUSb8bo,1706
1544
1544
  vellum/workflows/nodes/displayable/bases/utils.py,sha256=ckMUenSsNkiYmSw6FmjSMHYaCk8Y8_sUjL6lkFFEqts,5412
1545
1545
  vellum/workflows/nodes/displayable/code_execution_node/__init__.py,sha256=0FLWMMktpzSnmBMizQglBpcPrP80fzVsoJwJgf822Cg,76
1546
- vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=oUBfghBMe6JzrPm5e3tGNuBqILwnaBfnCnOSUX-HfOE,9552
1546
+ vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=Ko_Dy17AjfSx2A4u5Xno5R0KH2p5akEHq8L0rQkySGs,9576
1547
1547
  vellum/workflows/nodes/displayable/code_execution_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1548
1548
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1549
1549
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/main.py,sha256=5QsbmkzSlSbcbWTG_JmIqcP-JNJzOPTKxGzdHos19W4,79
1550
1550
  vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=xAaoOfQHQUlp0iKlig87t0aT2cJM-8PxiTb1QDg8VmY,24641
1551
- vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=PI0IQysC3uASv4nof23O4gIWpoNl3tRleb1q417bfTw,3896
1551
+ vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=G-sc7yOL5g6rLk99X8HAbXNcLxRaqpju9IXq1iUwnQI,4470
1552
1552
  vellum/workflows/nodes/displayable/conditional_node/__init__.py,sha256=AS_EIqFdU1F9t8aLmbZU-rLh9ry6LCJ0uj0D8F0L5Uw,72
1553
1553
  vellum/workflows/nodes/displayable/conditional_node/node.py,sha256=Qjfl33gZ3JEgxBA1EgzSUebboGvsARthIxxcQyvx5Gg,1152
1554
1554
  vellum/workflows/nodes/displayable/conftest.py,sha256=tD_WIiw5WjFqnzgnGLtEZDaMj2XhQ1DptnBTKYeBbI0,5705
@@ -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
@@ -1618,7 +1618,7 @@ vellum/workflows/runner/runner.py,sha256=ww4fjZJBENkB5HJxdj92kTz7k_EyifCeAreupy5
1618
1618
  vellum/workflows/sandbox.py,sha256=GVJzVjMuYzOBnSrboB0_6MMRZWBluAyQ2o7syeaeBd0,2235
1619
1619
  vellum/workflows/state/__init__.py,sha256=yUUdR-_Vl7UiixNDYQZ-GEM_kJI9dnOia75TtuNEsnE,60
1620
1620
  vellum/workflows/state/base.py,sha256=Vkhneko3VlQrPsMLU1PYSzXU_W1u7_AraJsghiv5O-4,15512
1621
- vellum/workflows/state/context.py,sha256=yePVr4CCTQn5bjo1697JOO24fKFQpVNzooL07xL4gL0,2702
1621
+ vellum/workflows/state/context.py,sha256=B-sGK9GpZPh7bgCPb8PJCrl_evod3Ru7rHs_RofisrU,2926
1622
1622
  vellum/workflows/state/encoder.py,sha256=TnOQojc5lTQ83g9QbpA4UCqShJvutmTMxbpKt-9gNe4,1911
1623
1623
  vellum/workflows/state/store.py,sha256=uVe-oN73KwGV6M6YLhwZMMUQhzTQomsVfVnb8V91gVo,1147
1624
1624
  vellum/workflows/state/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1650,8 +1650,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
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.29.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1654
- vellum_ai-0.14.29.dist-info/METADATA,sha256=KgSQqeoqVLwdeQuikF8QxV4hr9OaVVyym5OlArQRyPM,5484
1655
- vellum_ai-0.14.29.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1656
- vellum_ai-0.14.29.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1657
- vellum_ai-0.14.29.dist-info/RECORD,,
1653
+ vellum_ai-0.14.31.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1654
+ vellum_ai-0.14.31.dist-info/METADATA,sha256=BDTX1i0ZWziXvzVjwWKd3C6uEiD3QMQol9_a636y0-s,5484
1655
+ vellum_ai-0.14.31.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1656
+ vellum_ai-0.14.31.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1657
+ vellum_ai-0.14.31.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(
@@ -156,67 +151,73 @@ def pull_command(
156
151
 
157
152
  error_content = ""
158
153
 
159
- with zipfile.ZipFile(zip_buffer) as zip_file:
160
- if METADATA_FILE_NAME in zip_file.namelist():
161
- metadata_json: Optional[dict] = None
162
- with zip_file.open(METADATA_FILE_NAME) as source:
163
- metadata_json = json.load(source)
164
-
165
- pull_contents_metadata = PullContentsMetadata.model_validate(metadata_json)
166
-
167
- if pull_contents_metadata.runner_config:
168
- workflow_config.container_image_name = pull_contents_metadata.runner_config.container_image_name
169
- workflow_config.container_image_tag = pull_contents_metadata.runner_config.container_image_tag
170
- if workflow_config.container_image_name and not workflow_config.container_image_tag:
171
- workflow_config.container_image_tag = "latest"
172
-
173
- if not workflow_config.module and pull_contents_metadata.label:
174
- workflow_config.module = snake_case(pull_contents_metadata.label)
175
-
176
- if not workflow_config.module:
177
- raise ValueError(f"Failed to resolve a module name for Workflow {pk}")
178
-
179
- # Use target_directory if provided, otherwise use current working directory
180
- base_dir = os.path.join(os.getcwd(), target_directory) if target_directory else os.getcwd()
181
- target_dir = os.path.join(base_dir, *workflow_config.module.split("."))
182
- workflow_config.target_directory = target_dir if target_directory else None
183
-
184
- # Delete files in target_dir that aren't in the zip file
185
- if os.path.exists(target_dir):
186
- ignore_patterns = (
187
- workflow_config.ignore
188
- if isinstance(workflow_config.ignore, list)
189
- else [workflow_config.ignore] if isinstance(workflow_config.ignore, str) else []
190
- )
191
- existing_files = []
192
- for root, _, files in os.walk(target_dir):
193
- for file in files:
194
- rel_path = os.path.relpath(os.path.join(root, file), target_dir)
195
- existing_files.append(rel_path)
196
-
197
- for file in existing_files:
198
- if any(Path(file).match(ignore_pattern) for ignore_pattern in ignore_patterns):
199
- continue
200
-
201
- if file not in zip_file.namelist():
202
- file_path = os.path.join(target_dir, file)
203
- logger.info(f"Deleting {file_path}...")
204
- os.remove(file_path)
205
-
206
- for file_name in zip_file.namelist():
207
- with zip_file.open(file_name) as source:
208
- content = source.read().decode("utf-8")
209
- if file_name == ERROR_LOG_FILE_NAME:
210
- error_content = content
211
- continue
212
- if file_name == METADATA_FILE_NAME:
213
- continue
214
-
215
- target_file = os.path.join(target_dir, file_name)
216
- os.makedirs(os.path.dirname(target_file), exist_ok=True)
217
- with open(target_file, "w") as target:
218
- logger.info(f"Writing to {target_file}...")
219
- target.write(content)
154
+ try:
155
+ with zipfile.ZipFile(zip_buffer) as zip_file:
156
+ if METADATA_FILE_NAME in zip_file.namelist():
157
+ metadata_json: Optional[dict] = None
158
+ with zip_file.open(METADATA_FILE_NAME) as source:
159
+ metadata_json = json.load(source)
160
+
161
+ pull_contents_metadata = PullContentsMetadata.model_validate(metadata_json)
162
+
163
+ if pull_contents_metadata.runner_config:
164
+ workflow_config.container_image_name = pull_contents_metadata.runner_config.container_image_name
165
+ workflow_config.container_image_tag = pull_contents_metadata.runner_config.container_image_tag
166
+ if workflow_config.container_image_name and not workflow_config.container_image_tag:
167
+ workflow_config.container_image_tag = "latest"
168
+ if not workflow_config.module and workflow_deployment and pull_contents_metadata.deployment_name:
169
+ workflow_config.module = snake_case(pull_contents_metadata.deployment_name)
170
+ if not workflow_config.module and pull_contents_metadata.label:
171
+ workflow_config.module = snake_case(pull_contents_metadata.label)
172
+
173
+ if not workflow_config.module:
174
+ raise ValueError(f"Failed to resolve a module name for Workflow {pk}")
175
+
176
+ # Use target_directory if provided, otherwise use current working directory
177
+ base_dir = os.path.join(os.getcwd(), target_directory) if target_directory else os.getcwd()
178
+ target_dir = os.path.join(base_dir, *workflow_config.module.split("."))
179
+ workflow_config.target_directory = target_dir if target_directory else None
180
+
181
+ # Delete files in target_dir that aren't in the zip file
182
+ if os.path.exists(target_dir):
183
+ ignore_patterns = (
184
+ workflow_config.ignore
185
+ if isinstance(workflow_config.ignore, list)
186
+ else [workflow_config.ignore] if isinstance(workflow_config.ignore, str) else []
187
+ )
188
+ existing_files = []
189
+ for root, _, files in os.walk(target_dir):
190
+ for file in files:
191
+ rel_path = os.path.relpath(os.path.join(root, file), target_dir)
192
+ existing_files.append(rel_path)
193
+
194
+ for file in existing_files:
195
+ if any(Path(file).match(ignore_pattern) for ignore_pattern in ignore_patterns):
196
+ continue
197
+
198
+ if file not in zip_file.namelist():
199
+ file_path = os.path.join(target_dir, file)
200
+ logger.info(f"Deleting {file_path}...")
201
+ os.remove(file_path)
202
+
203
+ for file_name in zip_file.namelist():
204
+ with zip_file.open(file_name) as source:
205
+ content = source.read().decode("utf-8")
206
+ if file_name == ERROR_LOG_FILE_NAME:
207
+ error_content = content
208
+ continue
209
+ if file_name == METADATA_FILE_NAME:
210
+ continue
211
+
212
+ target_file = os.path.join(target_dir, file_name)
213
+ os.makedirs(os.path.dirname(target_file), exist_ok=True)
214
+ with open(target_file, "w") as target:
215
+ logger.info(f"Writing to {target_file}...")
216
+ target.write(content)
217
+ except zipfile.BadZipFile:
218
+ raise Exception(
219
+ "The API we tried to pull from returned an invalid zip file. Please make sure your `VELLUM_API_URL` environment variable is set correctly." # noqa: E501
220
+ )
220
221
 
221
222
  if include_json:
222
223
  logger.warning(
@@ -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,65 @@ 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')"
878
+
879
+
880
+ def test_pull__invalid_zip_file(vellum_client):
881
+ workflow_deployment = "test-workflow-deployment-id"
882
+
883
+ # GIVEN a workflow pull API call returns an invalid zip file
884
+ vellum_client.workflows.pull.return_value = iter([b"invalid"]) # Return bytes instead of string
885
+
886
+ # WHEN the user runs the pull command
887
+ runner = CliRunner()
888
+ result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", workflow_deployment])
889
+
890
+ # THEN the command returns an error
891
+ assert result.exit_code == 1
892
+ assert (
893
+ str(result.exception)
894
+ == "The API we tried to pull from returned an invalid zip file. Please make sure your `VELLUM_API_URL` environment variable is set correctly." # noqa: E501
895
+ )
@@ -1,4 +1,3 @@
1
- import pytest
2
1
  import sys
3
2
  from uuid import uuid4
4
3
  from typing import Type, cast
@@ -8,14 +7,10 @@ from vellum.client.types.code_executor_response import CodeExecutorResponse
8
7
  from vellum.client.types.number_vellum_value import NumberVellumValue
9
8
  from vellum.workflows import BaseWorkflow
10
9
  from vellum.workflows.nodes import BaseNode
10
+ from vellum.workflows.state.context import WorkflowContext
11
11
  from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
12
12
 
13
13
 
14
- @pytest.fixture
15
- def mock_open(mocker):
16
- return mocker.patch("vellum.workflows.nodes.displayable.code_execution_node.utils.open")
17
-
18
-
19
14
  def test_load_workflow_event_display_context():
20
15
  # DEPRECATED: Use `vellum.workflows.events.workflow.WorkflowEventDisplayContext` instead. Will be removed in 0.15.0
21
16
  from vellum_ee.workflows.display.types import WorkflowEventDisplayContext
@@ -80,7 +75,6 @@ class StartNode(BaseNode):
80
75
 
81
76
 
82
77
  def test_load_from_module__ts_code_in_file_loader(
83
- mock_open,
84
78
  vellum_client,
85
79
  ):
86
80
  # GIVEN typescript code
@@ -91,19 +85,22 @@ def test_load_from_module__ts_code_in_file_loader(
91
85
  # AND a workflow module with only a code execution node
92
86
  files = {
93
87
  "__init__.py": "",
94
- "workflow.py": """\
88
+ "workflow.py": """
95
89
  from vellum.workflows import BaseWorkflow
96
90
  from .nodes.code_execution_node import CodeExecutionNode
97
91
 
98
92
  class Workflow(BaseWorkflow):
99
93
  graph = CodeExecutionNode
94
+
95
+ class Outputs(BaseWorkflow.Outputs):
96
+ final_output = CodeExecutionNode.Outputs.result
100
97
  """,
101
- "nodes/__init__.py": """\
98
+ "nodes/__init__.py": """
102
99
  from .code_execution_node import CodeExecutionNode
103
100
 
104
101
  __all__ = ["CodeExecutionNode"]
105
102
  """,
106
- "nodes/code_execution_node.py": """\
103
+ "nodes/code_execution_node/__init__.py": """
107
104
  from typing import Any
108
105
 
109
106
  from vellum.workflows.nodes.displayable import CodeExecutionNode as BaseCodeExecutionNode
@@ -121,10 +118,8 @@ class CodeExecutionNode(BaseCodeExecutionNode[BaseState, int]):
121
118
  namespace = str(uuid4())
122
119
 
123
120
  # AND the virtual file loader is registered
124
- sys.meta_path.append(VirtualFileFinder(files, namespace))
125
-
126
- # AND the open function returns our file content
127
- mock_open.return_value.__enter__.return_value.read.return_value = ts_code
121
+ finder = VirtualFileFinder(files, namespace)
122
+ sys.meta_path.append(finder)
128
123
 
129
124
  # AND we know what the Code Execution Node will respond with
130
125
  mock_code_execution = CodeExecutorResponse(
@@ -135,16 +130,13 @@ class CodeExecutionNode(BaseCodeExecutionNode[BaseState, int]):
135
130
 
136
131
  # WHEN the workflow is loaded
137
132
  Workflow = BaseWorkflow.load_from_module(namespace)
138
- workflow = Workflow()
133
+ workflow = Workflow(context=WorkflowContext(generated_files=files))
139
134
 
140
135
  # THEN the workflow is successfully initialized
141
136
  assert workflow
142
137
 
143
138
  event = workflow.run()
144
- assert event.name == "workflow.execution.fulfilled", event.model_dump_json()
145
-
146
- # AND we pass in the correct file path to the open function
147
- assert mock_open.call_args[0][0] == f"{namespace}/nodes/./script.ts"
139
+ assert event.name == "workflow.execution.fulfilled"
148
140
 
149
- # AND we invoke the Code Execution Node with the correct code
150
- assert vellum_client.execute_code.call_args.kwargs["code"] == ts_code
141
+ # AND we get the code execution result
142
+ assert event.body.outputs == {"final_output": 5.0}