vellum-ai 0.13.15__py3-none-any.whl → 0.13.19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. vellum/client/core/client_wrapper.py +1 -1
  2. vellum/client/resources/workflows/client.py +0 -10
  3. vellum/workflows/nodes/core/retry_node/node.py +13 -7
  4. vellum/workflows/nodes/core/templating_node/node.py +4 -47
  5. vellum/workflows/nodes/displayable/code_execution_node/node.py +29 -23
  6. vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +169 -5
  7. vellum/workflows/nodes/displayable/code_execution_node/utils.py +98 -1
  8. vellum/workflows/nodes/utils.py +50 -1
  9. vellum/workflows/outputs/base.py +11 -0
  10. vellum/workflows/references/external_input.py +14 -0
  11. vellum/workflows/state/base.py +7 -0
  12. vellum/workflows/state/tests/test_state.py +42 -0
  13. {vellum_ai-0.13.15.dist-info → vellum_ai-0.13.19.dist-info}/METADATA +1 -1
  14. {vellum_ai-0.13.15.dist-info → vellum_ai-0.13.19.dist-info}/RECORD +28 -27
  15. vellum_cli/config.py +69 -11
  16. vellum_cli/pull.py +57 -20
  17. vellum_cli/push.py +1 -5
  18. vellum_cli/tests/test_pull.py +157 -9
  19. vellum_cli/tests/test_push.py +0 -8
  20. vellum_ee/workflows/display/nodes/base_node_display.py +2 -2
  21. vellum_ee/workflows/display/nodes/get_node_display_class.py +16 -20
  22. vellum_ee/workflows/display/nodes/vellum/__init__.py +2 -0
  23. vellum_ee/workflows/display/nodes/vellum/retry_node.py +10 -0
  24. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +28 -0
  25. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +2 -2
  26. {vellum_ai-0.13.15.dist-info → vellum_ai-0.13.19.dist-info}/LICENSE +0 -0
  27. {vellum_ai-0.13.15.dist-info → vellum_ai-0.13.19.dist-info}/WHEEL +0 -0
  28. {vellum_ai-0.13.15.dist-info → vellum_ai-0.13.19.dist-info}/entry_points.txt +0 -0
@@ -1,11 +1,15 @@
1
1
  from functools import cache
2
+ import json
2
3
  import sys
3
4
  from types import ModuleType
4
- from typing import Any, Callable, Optional, Type, TypeVar
5
+ from typing import Any, Callable, Optional, Type, TypeVar, get_args, get_origin
6
+
7
+ from pydantic import BaseModel
5
8
 
6
9
  from vellum.workflows.nodes import BaseNode
7
10
  from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
8
11
  from vellum.workflows.ports.port import Port
12
+ from vellum.workflows.types.core import Json
9
13
  from vellum.workflows.types.generics import NodeType
10
14
 
11
15
  ADORNMENT_MODULE_NAME = "<adornment>"
@@ -73,3 +77,48 @@ def create_adornment(
73
77
  return WrappedNode
74
78
 
75
79
  return decorator
80
+
81
+
82
+ def parse_type_from_str(result_as_str: str, output_type: Any) -> Any:
83
+ if output_type is str:
84
+ return result_as_str
85
+
86
+ if output_type is float:
87
+ return float(result_as_str)
88
+
89
+ if output_type is int:
90
+ return int(result_as_str)
91
+
92
+ if output_type is bool:
93
+ return bool(result_as_str)
94
+
95
+ if get_origin(output_type) is list:
96
+ try:
97
+ data = json.loads(result_as_str)
98
+ except json.JSONDecodeError:
99
+ raise ValueError("Invalid JSON Array format for result_as_str")
100
+
101
+ if not isinstance(data, list):
102
+ raise ValueError(f"Expected a list of items for result_as_str, received {data.__class__.__name__}")
103
+
104
+ inner_type = get_args(output_type)[0]
105
+ if issubclass(inner_type, BaseModel):
106
+ return [inner_type.model_validate(item) for item in data]
107
+ else:
108
+ return data
109
+
110
+ if output_type is Json:
111
+ try:
112
+ return json.loads(result_as_str)
113
+ except json.JSONDecodeError:
114
+ raise ValueError("Invalid JSON format for result_as_str")
115
+
116
+ if issubclass(output_type, BaseModel):
117
+ try:
118
+ data = json.loads(result_as_str)
119
+ except json.JSONDecodeError:
120
+ raise ValueError("Invalid JSON format for result_as_str")
121
+
122
+ return output_type.model_validate(data)
123
+
124
+ raise ValueError(f"Unsupported output type: {output_type}")
@@ -6,6 +6,8 @@ from pydantic_core import core_schema
6
6
 
7
7
  from vellum.workflows.constants import UNDEF
8
8
  from vellum.workflows.descriptors.base import BaseDescriptor
9
+ from vellum.workflows.errors.types import WorkflowErrorCode
10
+ from vellum.workflows.exceptions import NodeException
9
11
  from vellum.workflows.references.output import OutputReference
10
12
  from vellum.workflows.types.utils import get_class_attr_names, infer_types
11
13
 
@@ -183,6 +185,15 @@ class _BaseOutputsMeta(type):
183
185
 
184
186
  class BaseOutputs(metaclass=_BaseOutputsMeta):
185
187
  def __init__(self, **kwargs: Any) -> None:
188
+ declared_fields = {descriptor.name for descriptor in self.__class__}
189
+ provided_fields = set(kwargs.keys())
190
+
191
+ if not provided_fields.issubset(declared_fields):
192
+ raise NodeException(
193
+ message=f"Unexpected outputs: {provided_fields - declared_fields}",
194
+ code=WorkflowErrorCode.INVALID_OUTPUTS,
195
+ )
196
+
186
197
  for name, value in kwargs.items():
187
198
  setattr(self, name, value)
188
199
 
@@ -42,6 +42,20 @@ class ExternalInputReference(BaseDescriptor[_InputType], Generic[_InputType]):
42
42
 
43
43
  raise NodeException(f"Missing required Node Input: {self._name}", code=WorkflowErrorCode.INVALID_INPUTS)
44
44
 
45
+ def __eq__(self, other: object) -> bool:
46
+ if not isinstance(other, type(self)):
47
+ return False
48
+
49
+ # Check equality of the name
50
+ base_equal = super().__eq__(other)
51
+ if not base_equal:
52
+ return False
53
+
54
+ return self._inputs_class == other._inputs_class
55
+
56
+ def __hash__(self) -> int:
57
+ return hash((self._name, self._inputs_class))
58
+
45
59
  @classmethod
46
60
  def __get_pydantic_core_schema__(
47
61
  cls, source_type: Type[Any], handler: GetCoreSchemaHandler
@@ -201,6 +201,7 @@ class StateMeta(UniversalBaseModel):
201
201
 
202
202
  def add_snapshot_callback(self, callback: Callable[[], None]) -> None:
203
203
  self.node_outputs = _make_snapshottable(self.node_outputs, callback)
204
+ self.external_inputs = _make_snapshottable(self.external_inputs, callback)
204
205
  self.__snapshot_callback__ = callback
205
206
 
206
207
  def __setattr__(self, name: str, value: Any) -> None:
@@ -231,7 +232,13 @@ class StateMeta(UniversalBaseModel):
231
232
  for descriptor, value in self.node_outputs.items()
232
233
  }
233
234
 
235
+ new_external_inputs = {
236
+ descriptor: value if isinstance(value, Queue) else deepcopy(value, memo)
237
+ for descriptor, value in self.external_inputs.items()
238
+ }
239
+
234
240
  memo[id(self.node_outputs)] = new_node_outputs
241
+ memo[id(self.external_inputs)] = new_external_inputs
235
242
  memo[id(self.__snapshot_callback__)] = None
236
243
 
237
244
  return super().__deepcopy__(memo)
@@ -64,6 +64,22 @@ def test_state_snapshot__nested_dictionary_edit():
64
64
  assert snapshot_count[id(state)] == 1
65
65
 
66
66
 
67
+ def test_state_snapshot__external_input_edit():
68
+ # GIVEN an initial state instance
69
+ state = MockState(foo="bar")
70
+ assert snapshot_count[id(state)] == 0
71
+
72
+ # WHEN we add an external input to state
73
+ class MockExternalInputs(BaseNode.ExternalInputs):
74
+ message: str
75
+
76
+ # WHEN we edit external inputs dictionary
77
+ state.meta.external_inputs[MockExternalInputs.message] = "hello"
78
+
79
+ # THEN the snapshot is emitted
80
+ assert snapshot_count[id(state)] == 1
81
+
82
+
67
83
  def test_state_deepcopy():
68
84
  # GIVEN an initial state instance
69
85
  state = MockState(foo="bar")
@@ -116,6 +132,32 @@ def test_state_json_serialization__with_node_output_updates():
116
132
  assert json_state["meta"]["node_outputs"] == {"MockNode.Outputs.baz": "hello"}
117
133
 
118
134
 
135
+ def test_state_deepcopy__with_external_input_updates():
136
+ # GIVEN an initial state instance
137
+ state = MockState(foo="bar")
138
+
139
+ # AND we add an external input to state
140
+ class MockExternalInputs(BaseNode.ExternalInputs):
141
+ message: str
142
+
143
+ state.meta.external_inputs[MockExternalInputs.message] = "hello"
144
+
145
+ # AND we deepcopy the state
146
+ deepcopied_state = deepcopy(state)
147
+
148
+ # AND we update the original state
149
+ state.meta.external_inputs[MockExternalInputs.message] = "world"
150
+
151
+ # THEN the copied state is not updated
152
+ assert deepcopied_state.meta.external_inputs[MockExternalInputs.message] == "hello"
153
+
154
+ # AND the original state has had the correct number of snapshots
155
+ assert snapshot_count[id(state)] == 2
156
+
157
+ # AND the copied state has had the correct number of snapshots
158
+ assert snapshot_count[id(deepcopied_state)] == 0
159
+
160
+
119
161
  def test_state_json_serialization__with_queue():
120
162
  # GIVEN an initial state instance
121
163
  state = MockState(foo="bar")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 0.13.15
3
+ Version: 0.13.19
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -2,33 +2,33 @@ vellum_cli/CONTRIBUTING.md,sha256=FtDC7BGxSeMnwCXAUssFsAIElXtmJE-O5Z7BpolcgvI,29
2
2
  vellum_cli/README.md,sha256=2NudRoLzWxNKqnuVy1JuQ7DerIaxWGYkrH8kMd-asIE,90
3
3
  vellum_cli/__init__.py,sha256=uEn2Nlt2Z0kBc79NcO4-rgOIE7H9nsMEEjWF6MLDlPo,10591
4
4
  vellum_cli/aliased_group.py,sha256=ugW498j0yv4ALJ8vS9MsO7ctDW7Jlir9j6nE_uHAP8c,3363
5
- vellum_cli/config.py,sha256=LVRB-SEJcpQYfg2QGcjKHmRSAijdSFADbS90gDY4AI8,6829
5
+ vellum_cli/config.py,sha256=Bsb3mnvKvv3oOTcCuxpgC7lWPMqt6eJhgRA6VEE-vL4,9266
6
6
  vellum_cli/image_push.py,sha256=SJwhwWJsLjwGNezNVd_oCVpFMfPsAB3dfLWmriZZUtw,4419
7
7
  vellum_cli/logger.py,sha256=PuRFa0WCh4sAGFS5aqWB0QIYpS6nBWwPJrIXpWxugV4,1022
8
8
  vellum_cli/ping.py,sha256=lWyJw6sziXjyTopTYRdFF5hV-sYPVDdX0yVbG5fzcY4,585
9
- vellum_cli/pull.py,sha256=zf0y22XptUYI_hMP_4Q1CEo9s2wALsTJcCXNd-_ibd8,7551
10
- vellum_cli/push.py,sha256=VuAQ26stoweihwiY8ms8RpU6i200fO5SjU7MiMIDvAo,9217
9
+ vellum_cli/pull.py,sha256=7yvg4oBOgsbBEsgXtCpYlNR4AOR8hPICamY-4HI-3kM,9031
10
+ vellum_cli/push.py,sha256=qQCI_cHvVjeDGA8FLYmSN7UE_wIJQnUXX_5cGsm9OTs,9076
11
11
  vellum_cli/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  vellum_cli/tests/conftest.py,sha256=AFYZryKA2qnUuCPBxBKmHLFoPiE0WhBFFej9tNwSHdc,1526
13
13
  vellum_cli/tests/test_config.py,sha256=uvKGDc8BoVyT9_H0Z-g8469zVxomn6Oi3Zj-vK7O_wU,2631
14
14
  vellum_cli/tests/test_main.py,sha256=qDZG-aQauPwBwM6A2DIu1494n47v3pL28XakTbLGZ-k,272
15
15
  vellum_cli/tests/test_ping.py,sha256=QtbhYKMYn1DFnDyBij2mkQO32j9KOpZ5Pf0yek7k_Ao,1284
16
- vellum_cli/tests/test_pull.py,sha256=KRvQ_fwsFH6tBU49O-KOOAzcHxdB-DPbUkdIdahjuTs,19841
17
- vellum_cli/tests/test_push.py,sha256=AgJVaDeIDa--iVvDrUMxjuvOoC-ylwD_A5KumUJTtJU,18810
16
+ vellum_cli/tests/test_pull.py,sha256=JURmgGs5lSnpzefSx4K13eF2swv7O8OF86-4df81Zjo,25241
17
+ vellum_cli/tests/test_push.py,sha256=kcM8jp6vTRBwfk6cAC_tHa4Eliuy_zZ-X1DfIpSEIW0,18299
18
18
  vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  vellum_ee/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  vellum_ee/workflows/display/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  vellum_ee/workflows/display/base.py,sha256=3ZFUYRNKL24fBqXhKpa_Dq2W1a-a86J20dmJYA3H2eY,1755
23
23
  vellum_ee/workflows/display/nodes/__init__.py,sha256=5XOcZJXYUgaLS55QgRJzyQ_W1tpeprjnYAeYVezqoGw,160
24
- vellum_ee/workflows/display/nodes/base_node_display.py,sha256=YBXHPwf453j-xaJoeJsoGhnW039WPSmhbQO-G5UGtcY,15787
24
+ vellum_ee/workflows/display/nodes/base_node_display.py,sha256=ErIK_1DYax0LlFX4AvV1oua8I7JlpXNncjGNadVe-bo,15801
25
25
  vellum_ee/workflows/display/nodes/base_node_vellum_display.py,sha256=pLO0dORfRu--Ne9NgoyFT_CNjfpr5fGCsgbsMkUF5GM,2845
26
- vellum_ee/workflows/display/nodes/get_node_display_class.py,sha256=VpGL-tnMM9wEOkuHNxX74RHAFhz5jcVXvmBkKciOBg0,1367
26
+ vellum_ee/workflows/display/nodes/get_node_display_class.py,sha256=0S6ksPp53WXWh1RQVH71nj2bkCWBj4ZaFYhTxW3N2F4,1230
27
27
  vellum_ee/workflows/display/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  vellum_ee/workflows/display/nodes/tests/test_base_node_display.py,sha256=QqR3Ly0RNrXwOeLdW5nERDFt0gRPf76n1bPES6o5UN4,1093
29
29
  vellum_ee/workflows/display/nodes/types.py,sha256=St1BB6no528OyELGiyRabWao0GGw6mLhstQAvEACbGk,247
30
30
  vellum_ee/workflows/display/nodes/utils.py,sha256=sloya5TpXsnot1HURc9L51INwflRqUzHxRVnCS9Cd-4,973
31
- vellum_ee/workflows/display/nodes/vellum/__init__.py,sha256=nmPLj8vkbVCS46XQqmHq8Xj8Mr36wCK_vWf26A9KIkw,1505
31
+ vellum_ee/workflows/display/nodes/vellum/__init__.py,sha256=VHx6wSs9wuuiFlZpSQSd3mhECz4SUy2wEBuTSv--_As,1578
32
32
  vellum_ee/workflows/display/nodes/vellum/api_node.py,sha256=hoV-cUtS6H9kmRQXHd2py95GRWI_dAnnaPwvlNBkDOQ,8571
33
33
  vellum_ee/workflows/display/nodes/vellum/code_execution_node.py,sha256=z00Z3L0d4PsUQo4S8FRDTtOFLtjdi17TJbatNVF4nM8,4288
34
34
  vellum_ee/workflows/display/nodes/vellum/conditional_node.py,sha256=ybLIa4uclqVIy3VAQvI1ivg2tnK5Ug_1R5a69DFqL7E,11104
@@ -41,6 +41,7 @@ vellum_ee/workflows/display/nodes/vellum/map_node.py,sha256=VlO3UwkspCOdDQ-h3v8k
41
41
  vellum_ee/workflows/display/nodes/vellum/merge_node.py,sha256=HkNMgdQELiON42jdO-xDLmqrEKdGx1RVqrz2DXNTLS8,3239
42
42
  vellum_ee/workflows/display/nodes/vellum/note_node.py,sha256=TMb8txILu2uWjzoxaghjgjlzeBAgzn4vkP_8zSh2qoE,1151
43
43
  vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py,sha256=LFjLUrH6sJ4czPnExdRqFr0PB_yKBMLXLvK5GAzIAgc,3273
44
+ vellum_ee/workflows/display/nodes/vellum/retry_node.py,sha256=i0XGbKecLiHtf7mBf2rbqldPgLcs1TitIphzcHRIvkA,341
44
45
  vellum_ee/workflows/display/nodes/vellum/search_node.py,sha256=TxcAGZDl_hvJ7Y1hUi9YVEVrj9Ie0hKkASdpfRL4_cs,9227
45
46
  vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py,sha256=lfevlHpGEX14dEDym6qmnkw3nvzQPTB1_D2ch12B_Rk,2701
46
47
  vellum_ee/workflows/display/nodes/vellum/templating_node.py,sha256=JVIMPR3WpveOCWZubHKZkE04mavnTdb_9QY_r3XliRg,3424
@@ -55,13 +56,13 @@ vellum_ee/workflows/display/tests/test_vellum_workflow_display.py,sha256=h4bE187
55
56
  vellum_ee/workflows/display/tests/workflow_serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
57
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
58
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py,sha256=EenmEdBtHUFQ0OS-kE7Vboax3JnDdj-K4Qixt5oR0Po,2253
58
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=pYBubuTzwNczAGFTYhaMCwvMLAcoJfCLkn5PMPbrlMI,7187
59
+ vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=KYdohS5pRgHM0DcUaK0tHa40f0gSvDKi2K5On0zNEU8,8305
59
60
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py,sha256=yPXhdZxEDunNl5LL5Obb0jeO34djt7F-GiaTJhp_fmo,16742
60
61
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py,sha256=-12ZkZb3f5gyoNASV2yeQtMo5HmNsVEo8nXwL6IC-I8,6261
61
62
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py,sha256=6th6kCwzql6lddjkTQx4Jbvvs4ChqtJwctW-B4QuBhI,37352
62
63
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py,sha256=EbVgg_3_ipTt3MOop4RARX0fmNjwqZtkhIXzx9nGw7Y,4487
63
64
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py,sha256=bXZWxOKAVjZlbP3iLHPHGA4aPs0EguKlQqmYPNGv3cU,16391
64
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py,sha256=uvABhpstyxNNOz50-XJMAr3SKp8gluPp1mUtJjvRL0c,29410
65
+ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py,sha256=s2mBoL5Vvpoc-rcWcSGQjLp4yzVVwFmkTbS3--ErzCI,29486
65
66
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py,sha256=n3F_Eyru0DYOszBH4jplz7Mzt2FfBNxGlCkTFqvrX-M,48399
66
67
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py,sha256=8LhQgW0uzVOhzz0AwdM-EYugVO-0mGWglxWo_lON4D8,6079
67
68
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py,sha256=yWDykoHUjuiVYdevcJxWmYDDmbIszpHKYBu19vqR-I8,5746
@@ -112,7 +113,7 @@ vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
112
113
  vellum/client/__init__.py,sha256=8nZt88C9SVwWanjLbIQMU3rzb32h5UZfFMBx3VPHB50,111887
113
114
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
114
115
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
115
- vellum/client/core/client_wrapper.py,sha256=_D3P4oNIluyIpVSoNUIGx4jK18hJGMdrTL62pI-ApYg,1869
116
+ vellum/client/core/client_wrapper.py,sha256=rnVChhyob3hO3iXrfL4gsCAYGAOdglXQdXoLL07FKLg,1869
116
117
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
117
118
  vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
118
119
  vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
@@ -168,7 +169,7 @@ vellum/client/resources/workflow_deployments/types/workflow_deployments_list_req
168
169
  vellum/client/resources/workflow_sandboxes/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
169
170
  vellum/client/resources/workflow_sandboxes/client.py,sha256=3wVQxkjrJ5bIS8fB5FpKXCP2dX38299ghWrJ8YmXxwQ,7435
170
171
  vellum/client/resources/workflows/__init__.py,sha256=Z4xi8Nxd9U4t35FQSepTt1p-ns0X1xtdNs168kUcuBk,153
171
- vellum/client/resources/workflows/client.py,sha256=9y2Tak0kduS0D1yu57FlgvV_svJZzJlRbGTJ6bz-QuA,11419
172
+ vellum/client/resources/workflows/client.py,sha256=hM7FDn05XHhQk599ti8CI4moIg0RVoEFil3Wp9v9UZk,11215
172
173
  vellum/client/resources/workflows/types/__init__.py,sha256=-uFca4ypncAOvfsg6sjD-5C9zWdA5qNvU6m675GphVg,177
173
174
  vellum/client/resources/workflows/types/workflows_pull_request_format.py,sha256=dOWE_jnDnniIJLoeseeCms23aklghyBkoPmBFzcqqZk,165
174
175
  vellum/client/resources/workspace_secrets/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
@@ -1322,11 +1323,11 @@ vellum/workflows/nodes/core/map_node/node.py,sha256=bjCVMAzkqJUvaLWVBObjskcutwLG
1322
1323
  vellum/workflows/nodes/core/map_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1323
1324
  vellum/workflows/nodes/core/map_node/tests/test_node.py,sha256=P7ImQyeLcK-aEJUYCX1gPQyuOxdxnSPpI3mNPpKQ62Y,1919
1324
1325
  vellum/workflows/nodes/core/retry_node/__init__.py,sha256=lN2bIy5a3Uzhs_FYCrooADyYU6ZGShtvLKFWpelwPvo,60
1325
- vellum/workflows/nodes/core/retry_node/node.py,sha256=QEpxhKOyxDkRoAn2b0PToZWtAGQetSQYVTpb9yCOLlw,4028
1326
+ vellum/workflows/nodes/core/retry_node/node.py,sha256=WD96o-eOj3dwEEe2nqxwBbmLTIyPRYB3Lk4T6XHRX74,4214
1326
1327
  vellum/workflows/nodes/core/retry_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1327
1328
  vellum/workflows/nodes/core/retry_node/tests/test_node.py,sha256=RM_OHwxrHwyxvlQQBJPqVBxpedFuWQ9h2-Xa3kP75sc,4399
1328
1329
  vellum/workflows/nodes/core/templating_node/__init__.py,sha256=GmyuYo81_A1_Bz6id69ozVFS6FKiuDsZTiA3I6MaL2U,70
1329
- vellum/workflows/nodes/core/templating_node/node.py,sha256=GxsVS1FoV84kOtluu00V8H74MEnYvEf55p8mt4ub-5w,5188
1330
+ vellum/workflows/nodes/core/templating_node/node.py,sha256=zCYhq88qLTvoC9LetVrD9sLXkwHZsaWekxMhru_nV70,3752
1330
1331
  vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py,sha256=5iZWQWdJKDHMXBY8bhpb-Dpy9FTfW1HXxGUTivykZAA,4621
1331
1332
  vellum/workflows/nodes/core/try_node/__init__.py,sha256=JVD4DrldTIqFQQFrubs9KtWCCc0YCAc7Fzol5ZWIWeM,56
1332
1333
  vellum/workflows/nodes/core/try_node/node.py,sha256=_lTmSYCiz7lktaxpNWUCglNi8_5Sfy8Rpiov5SeKVMw,3920
@@ -1350,12 +1351,12 @@ vellum/workflows/nodes/displayable/bases/tests/test_utils.py,sha256=eqdqbKNRWVMD
1350
1351
  vellum/workflows/nodes/displayable/bases/types.py,sha256=C37B2Qh2YP7s7pUjd-EYKc2Zl1TbnCgI_mENuUSb8bo,1706
1351
1352
  vellum/workflows/nodes/displayable/bases/utils.py,sha256=ckMUenSsNkiYmSw6FmjSMHYaCk8Y8_sUjL6lkFFEqts,5412
1352
1353
  vellum/workflows/nodes/displayable/code_execution_node/__init__.py,sha256=0FLWMMktpzSnmBMizQglBpcPrP80fzVsoJwJgf822Cg,76
1353
- vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=Hzc0h9sogX8DfFbJqdDLxGU2CMY1WvGosZ7taJC4sPE,8754
1354
+ vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=KZ5d3_mdpsrPF_ScmEqSfBhfup421RscO9hNiGa52T4,9068
1354
1355
  vellum/workflows/nodes/displayable/code_execution_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1355
1356
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1356
1357
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/main.py,sha256=5QsbmkzSlSbcbWTG_JmIqcP-JNJzOPTKxGzdHos19W4,79
1357
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=s93M_EnU-4n60iSKv3FCf0kppwzFH5FNi9o9E58fQ3I,7510
1358
- vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=LfI3kj2zQz6UGMld_uA9z2LjZobqRcgxQO4jdUWkg7o,376
1358
+ vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=ueVDw0GTSGzBZMFLs0NTir_0AE-pUrAYvpgg3Stex7Q,12350
1359
+ vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=zRYM7B2t7355LzaAn6LkIn4tM5K7eQ0Kvje4NO6Kq30,3443
1359
1360
  vellum/workflows/nodes/displayable/conditional_node/__init__.py,sha256=AS_EIqFdU1F9t8aLmbZU-rLh9ry6LCJ0uj0D8F0L5Uw,72
1360
1361
  vellum/workflows/nodes/displayable/conditional_node/node.py,sha256=Qjfl33gZ3JEgxBA1EgzSUebboGvsARthIxxcQyvx5Gg,1152
1361
1362
  vellum/workflows/nodes/displayable/final_output_node/__init__.py,sha256=G7VXM4OWpubvSJtVkGmMNeqgb9GkM7qZT838eL18XU4,72
@@ -1386,9 +1387,9 @@ vellum/workflows/nodes/experimental/README.md,sha256=eF6DfIL8t-HbF9-mcofOMymKrra
1386
1387
  vellum/workflows/nodes/experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1387
1388
  vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py,sha256=lsyD9laR9p7kx5-BXGH2gUTM242UhKy8SMV0SR6S2iE,90
1388
1389
  vellum/workflows/nodes/experimental/openai_chat_completion_node/node.py,sha256=1EGeiaT-Zoo6pttQFKKBcdf3dmhAbjKGaErYD5FFwlc,10185
1389
- vellum/workflows/nodes/utils.py,sha256=tjBsootwm7vUq9qU4ttDL3CWH0C9Sd6QlX7IhfCCy34,2512
1390
+ vellum/workflows/nodes/utils.py,sha256=T7krLipDSI007JkkH2zsfyQ-tfOBH4hyFzQ0YJKYpuw,4025
1390
1391
  vellum/workflows/outputs/__init__.py,sha256=AyZ4pRh_ACQIGvkf0byJO46EDnSix1ZCAXfvh-ms1QE,94
1391
- vellum/workflows/outputs/base.py,sha256=a7W6rNSDSawwGAXYjNTF2iHb9lnZu7WFSOagZIyy__k,7976
1392
+ vellum/workflows/outputs/base.py,sha256=Wu48tqyQoxpflBUcwzKeZjSVp1LPKrBwuIvnx__9H90,8459
1392
1393
  vellum/workflows/ports/__init__.py,sha256=bZuMt-R7z5bKwpu4uPW7LlJeePOQWmCcDSXe5frUY5g,101
1393
1394
  vellum/workflows/ports/node_ports.py,sha256=g4A-8iUAvEJSkaWppbvzAR8XU02R9U-qLN4rP2Kq4Aw,2743
1394
1395
  vellum/workflows/ports/port.py,sha256=rc3GB7dDQCUs0IbY08a92-31YzJHQgBeww13brSJ2Js,3172
@@ -1397,7 +1398,7 @@ vellum/workflows/references/__init__.py,sha256=glHFC1VfXmcbNvH5VzFbkT03d8_D7MMcv
1397
1398
  vellum/workflows/references/constant.py,sha256=6yUT4q1sMj1hkI_tzzQ9AYcmeeDYFUNCqUq_W2DN0S8,540
1398
1399
  vellum/workflows/references/environment_variable.py,sha256=-gfOcdYwVp9ztSUYz6h2WI2Cg95zqxq5hhFf3Yr7aQg,791
1399
1400
  vellum/workflows/references/execution_count.py,sha256=JILHqt8ELdc9ct-WsVCA5X-rKiP1rmJODw-XTf4kpHI,722
1400
- vellum/workflows/references/external_input.py,sha256=XHugauKYvAmsGoFnjgJh00FcXjSMIqBvRun_CZuJD64,1662
1401
+ vellum/workflows/references/external_input.py,sha256=WyBC6uMDu77431YVSU_WvTt-nGLC_bW65tIsplUJXa4,2056
1401
1402
  vellum/workflows/references/input.py,sha256=3INu-TLTi4dziWmva6LO3WvgDlPzsjayUx61cVvqLJA,325
1402
1403
  vellum/workflows/references/lazy.py,sha256=SXwZUCTzUR-R2-uK0XHALtvp1x84l-QkNY-Ds6KynYA,1932
1403
1404
  vellum/workflows/references/node.py,sha256=LP854wDVs-9I_aZ7-nkbwXqL2H7W2_3LED2e9FixNS8,1418
@@ -1411,12 +1412,12 @@ vellum/workflows/runner/__init__.py,sha256=i1iG5sAhtpdsrlvwgH6B-m49JsINkiWyPWs8v
1411
1412
  vellum/workflows/runner/runner.py,sha256=LvP1UwLmwV1nCZYYrsiwtCzay73voMp1TDRVVBrlMj8,29196
1412
1413
  vellum/workflows/sandbox.py,sha256=GVJzVjMuYzOBnSrboB0_6MMRZWBluAyQ2o7syeaeBd0,2235
1413
1414
  vellum/workflows/state/__init__.py,sha256=yUUdR-_Vl7UiixNDYQZ-GEM_kJI9dnOia75TtuNEsnE,60
1414
- vellum/workflows/state/base.py,sha256=jpSzF1OQd3-fqi6dMGlNsQl-7JnJxCdzWIigmX8Wz-I,14425
1415
+ vellum/workflows/state/base.py,sha256=IIl76sJtn0GfbFWBqMnpGuvtZyVyQMEXv0QKDfLy8Wg,14763
1415
1416
  vellum/workflows/state/context.py,sha256=_NeGQpYo8yNuh0Tfh3OvcB_bG_-GC8b3ZLLl83Pf8-I,1279
1416
1417
  vellum/workflows/state/encoder.py,sha256=WdUidpOaBDx5lilJl8V8McFDHQYiCLCJR9dmktdzdZY,1836
1417
1418
  vellum/workflows/state/store.py,sha256=VYGBQgN1bpd1as5eGiouV_7scg8QsRs4_1aqZAGIsRQ,793
1418
1419
  vellum/workflows/state/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1419
- vellum/workflows/state/tests/test_state.py,sha256=7ap_Z9GJqyonZ1eVXwNyyuhV0AL5XiQ6uYCRhS7PFN4,3921
1420
+ vellum/workflows/state/tests/test_state.py,sha256=ucy7U8886J3CinIKQhOqv4dvkKWQk0fyK3JjTSiKZC4,5266
1420
1421
  vellum/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1421
1422
  vellum/workflows/tests/test_sandbox.py,sha256=JKwaluI-lODQo7Ek9sjDstjL_WTdSqUlVik6ZVTfVOA,1826
1422
1423
  vellum/workflows/types/__init__.py,sha256=KxUTMBGzuRCfiMqzzsykOeVvrrkaZmTTo1a7SLu8gRM,68
@@ -1441,8 +1442,8 @@ vellum/workflows/vellum_client.py,sha256=ODrq_TSl-drX2aezXegf7pizpWDVJuTXH-j6528
1441
1442
  vellum/workflows/workflows/__init__.py,sha256=KY45TqvavCCvXIkyCFMEc0dc6jTMOUci93U2DUrlZYc,66
1442
1443
  vellum/workflows/workflows/base.py,sha256=k0kUWWko4fHyCqLSU_1cBK_pXZpl9MXekWiG-bdOAo0,18353
1443
1444
  vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnadGsrSZGa7t7LpJA,2008
1444
- vellum_ai-0.13.15.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1445
- vellum_ai-0.13.15.dist-info/METADATA,sha256=H6f0l4ZqdCd9ANG3dLIdY-vX9NNebMI9n_hBLBQ49AI,5335
1446
- vellum_ai-0.13.15.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1447
- vellum_ai-0.13.15.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1448
- vellum_ai-0.13.15.dist-info/RECORD,,
1445
+ vellum_ai-0.13.19.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1446
+ vellum_ai-0.13.19.dist-info/METADATA,sha256=NzphP-OKn-7lL7hjSD1e1eT709mTax682Cs85AT0O_4,5335
1447
+ vellum_ai-0.13.19.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1448
+ vellum_ai-0.13.19.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1449
+ vellum_ai-0.13.19.dist-info/RECORD,,
vellum_cli/config.py CHANGED
@@ -1,3 +1,4 @@
1
+ from collections import defaultdict
1
2
  from dataclasses import field
2
3
  import json
3
4
  import os
@@ -88,6 +89,50 @@ class WorkflowConfig(UniversalBaseModel):
88
89
  )
89
90
 
90
91
 
92
+ def merge_workflows_by_sandbox_id(
93
+ workflows: List[WorkflowConfig], other_workflows: List[WorkflowConfig]
94
+ ) -> List[WorkflowConfig]:
95
+ merged_workflows: List[WorkflowConfig] = []
96
+ for self_workflow in workflows:
97
+ if self_workflow.workflow_sandbox_id is None:
98
+ # If the user defines a workflow in the pyproject.toml without a sandbox_id,
99
+ # we merge the workflow with one of the ones in the lockfile.
100
+ other_workflow = next(
101
+ (
102
+ other_workflow
103
+ for other_workflow in other_workflows
104
+ if self_workflow.workspace == other_workflow.workspace
105
+ ),
106
+ None,
107
+ )
108
+ if other_workflow is not None:
109
+ merged_workflows.append(self_workflow.merge(other_workflow))
110
+ else:
111
+ merged_workflows.append(self_workflow)
112
+ else:
113
+ # If the user defines a workflow in the pyproject.toml with a sandbox_id,
114
+ # we merge the workflow with one of the ones in the lockfile with the same sandbox_id.
115
+ other_workflow = next(
116
+ (
117
+ other_workflow
118
+ for other_workflow in other_workflows
119
+ if self_workflow.workflow_sandbox_id == other_workflow.workflow_sandbox_id
120
+ ),
121
+ None,
122
+ )
123
+ if other_workflow is not None:
124
+ merged_workflows.append(self_workflow.merge(other_workflow))
125
+ else:
126
+ merged_workflows.append(self_workflow)
127
+
128
+ workflow_sandbox_ids_so_far = {workflow.workflow_sandbox_id for workflow in merged_workflows}
129
+ for other_workflow in other_workflows:
130
+ if other_workflow.workflow_sandbox_id not in workflow_sandbox_ids_so_far:
131
+ merged_workflows.append(other_workflow)
132
+
133
+ return merged_workflows
134
+
135
+
91
136
  class VellumCliConfig(UniversalBaseModel):
92
137
  version: Literal["1.0"] = "1.0"
93
138
  workflows: List[WorkflowConfig] = field(default_factory=list)
@@ -97,24 +142,31 @@ class VellumCliConfig(UniversalBaseModel):
97
142
  lockfile_path = os.path.join(os.getcwd(), LOCKFILE_PATH)
98
143
  with open(lockfile_path, "w") as f:
99
144
  json.dump(self.model_dump(), f, indent=2, cls=DefaultStateEncoder)
145
+ # Makes sure the file ends with a newline, consistent with most autoformatters
146
+ f.write("\n")
100
147
 
101
148
  def merge(self, other: "VellumCliConfig") -> "VellumCliConfig":
102
149
  if other.version != self.version:
103
150
  raise ValueError("Lockfile version mismatch")
104
151
 
105
- self_workflow_by_module = {workflow.module: workflow for workflow in self.workflows}
106
- other_workflow_by_module = {workflow.module: workflow for workflow in other.workflows}
107
- all_modules = sorted(set(self_workflow_by_module.keys()).union(set(other_workflow_by_module.keys())))
152
+ self_workflows_by_module = self.get_workflows_by_module_mapping()
153
+ other_workflows_by_module = other.get_workflows_by_module_mapping()
154
+ all_modules = sorted(set(self_workflows_by_module.keys()).union(set(other_workflows_by_module.keys())))
108
155
  merged_workflows = []
109
156
  for module in all_modules:
110
- self_workflow = self_workflow_by_module.get(module)
111
- other_workflow = other_workflow_by_module.get(module)
112
- if self_workflow and other_workflow:
113
- merged_workflows.append(self_workflow.merge(other_workflow))
114
- elif self_workflow:
115
- merged_workflows.append(self_workflow)
116
- elif other_workflow:
117
- merged_workflows.append(other_workflow)
157
+ self_workflows = self_workflows_by_module.get(module)
158
+ other_workflows = other_workflows_by_module.get(module)
159
+ if self_workflows and other_workflows:
160
+ merged_workflows.extend(
161
+ merge_workflows_by_sandbox_id(
162
+ self_workflows,
163
+ other_workflows,
164
+ )
165
+ )
166
+ elif self_workflows:
167
+ merged_workflows.extend(self_workflows)
168
+ elif other_workflows:
169
+ merged_workflows.extend(other_workflows)
118
170
 
119
171
  self_workspace_by_name = {workspace.name: workspace for workspace in self.workspaces}
120
172
  other_workspace_by_name = {workspace.name: workspace for workspace in other.workspaces}
@@ -132,6 +184,12 @@ class VellumCliConfig(UniversalBaseModel):
132
184
 
133
185
  return VellumCliConfig(workflows=merged_workflows, workspaces=merged_workspaces, version=self.version)
134
186
 
187
+ def get_workflows_by_module_mapping(self) -> Dict[str, List[WorkflowConfig]]:
188
+ workflows_by_module = defaultdict(list)
189
+ for workflow in self.workflows:
190
+ workflows_by_module[workflow.module].append(workflow)
191
+ return workflows_by_module
192
+
135
193
 
136
194
  def load_vellum_cli_config(root_dir: Optional[str] = None) -> VellumCliConfig:
137
195
  if root_dir is None:
vellum_cli/pull.py CHANGED
@@ -31,6 +31,16 @@ class WorkflowConfigResolutionResult(UniversalBaseModel):
31
31
  pk: Optional[str] = None
32
32
 
33
33
 
34
+ class RunnerConfig(UniversalBaseModel):
35
+ container_image_name: Optional[str] = None
36
+ container_image_tag: Optional[str] = None
37
+
38
+
39
+ class PullContentsMetadata(UniversalBaseModel):
40
+ label: Optional[str] = None
41
+ runner_config: Optional[RunnerConfig] = None
42
+
43
+
34
44
  def _resolve_workflow_config(
35
45
  config: VellumCliConfig,
36
46
  module: Optional[str] = None,
@@ -42,14 +52,33 @@ def _resolve_workflow_config(
42
52
 
43
53
  if module:
44
54
  workflow_config = next((w for w in config.workflows if w.module == module), None)
55
+ if not workflow_config and workflow_sandbox_id:
56
+ workflow_config = WorkflowConfig(
57
+ workflow_sandbox_id=workflow_sandbox_id,
58
+ module=module,
59
+ )
60
+ config.workflows.append(workflow_config)
61
+ return WorkflowConfigResolutionResult(
62
+ workflow_config=workflow_config,
63
+ pk=workflow_sandbox_id,
64
+ )
65
+
45
66
  return WorkflowConfigResolutionResult(
46
67
  workflow_config=workflow_config,
47
68
  pk=workflow_config.workflow_sandbox_id if workflow_config else None,
48
69
  )
49
70
  elif workflow_sandbox_id:
71
+ workflow_config = next((w for w in config.workflows if w.workflow_sandbox_id == workflow_sandbox_id), None)
72
+ if workflow_config:
73
+ return WorkflowConfigResolutionResult(
74
+ workflow_config=workflow_config,
75
+ pk=workflow_sandbox_id,
76
+ )
77
+
78
+ # We use an empty module name to indicate that we want to backfill it once we have the Workflow Sandbox Label
50
79
  workflow_config = WorkflowConfig(
51
80
  workflow_sandbox_id=workflow_sandbox_id,
52
- module=f"workflow_{workflow_sandbox_id.split('-')[0]}",
81
+ module="",
53
82
  )
54
83
  config.workflows.append(workflow_config)
55
84
  return WorkflowConfigResolutionResult(
@@ -98,7 +127,6 @@ def pull_command(
98
127
  workflow_sandbox_id=workflow_sandbox_id,
99
128
  workflow_deployment=workflow_deployment,
100
129
  )
101
- save_lock_file = not module
102
130
 
103
131
  workflow_config = workflow_config_result.workflow_config
104
132
  if not workflow_config:
@@ -108,7 +136,11 @@ def pull_command(
108
136
  if not pk:
109
137
  raise ValueError("No workflow sandbox ID found in project to pull from.")
110
138
 
111
- logger.info(f"Pulling workflow into {workflow_config.module}")
139
+ if workflow_config.module:
140
+ logger.info(f"Pulling workflow into {workflow_config.module}...")
141
+ else:
142
+ logger.info(f"Pulling workflow from {pk}...")
143
+
112
144
  client = create_vellum_client()
113
145
  query_parameters = {}
114
146
 
@@ -129,11 +161,30 @@ def pull_command(
129
161
  zip_bytes = b"".join(response)
130
162
  zip_buffer = io.BytesIO(zip_bytes)
131
163
 
132
- target_dir = os.path.join(os.getcwd(), *workflow_config.module.split("."))
133
164
  error_content = ""
134
- metadata_json: Optional[dict] = None
135
165
 
136
166
  with zipfile.ZipFile(zip_buffer) as zip_file:
167
+ if METADATA_FILE_NAME in zip_file.namelist():
168
+ metadata_json: Optional[dict] = None
169
+ with zip_file.open(METADATA_FILE_NAME) as source:
170
+ metadata_json = json.load(source)
171
+
172
+ pull_contents_metadata = PullContentsMetadata.model_validate(metadata_json)
173
+
174
+ if pull_contents_metadata.runner_config:
175
+ workflow_config.container_image_name = pull_contents_metadata.runner_config.container_image_name
176
+ workflow_config.container_image_tag = pull_contents_metadata.runner_config.container_image_tag
177
+ if workflow_config.container_image_name and not workflow_config.container_image_tag:
178
+ workflow_config.container_image_tag = "latest"
179
+
180
+ if not workflow_config.module and pull_contents_metadata.label:
181
+ workflow_config.module = snake_case(pull_contents_metadata.label)
182
+
183
+ if not workflow_config.module:
184
+ raise ValueError(f"Failed to resolve a module name for Workflow {pk}")
185
+
186
+ target_dir = os.path.join(os.getcwd(), *workflow_config.module.split("."))
187
+
137
188
  # Delete files in target_dir that aren't in the zip file
138
189
  if os.path.exists(target_dir):
139
190
  ignore_patterns = (
@@ -163,7 +214,6 @@ def pull_command(
163
214
  error_content = content
164
215
  continue
165
216
  if file_name == METADATA_FILE_NAME:
166
- metadata_json = json.loads(content)
167
217
  continue
168
218
 
169
219
  target_file = os.path.join(target_dir, file_name)
@@ -172,15 +222,6 @@ def pull_command(
172
222
  logger.info(f"Writing to {target_file}...")
173
223
  target.write(content)
174
224
 
175
- if metadata_json:
176
- runner_config = metadata_json.get("runner_config")
177
-
178
- if runner_config:
179
- workflow_config.container_image_name = runner_config.get("container_image_name")
180
- workflow_config.container_image_tag = runner_config.get("container_image_tag")
181
- if workflow_config.container_image_name and not workflow_config.container_image_tag:
182
- workflow_config.container_image_tag = "latest"
183
-
184
225
  if include_json:
185
226
  logger.warning(
186
227
  """The pulled JSON representation of the Workflow should be used for debugging purposely only. \
@@ -190,16 +231,12 @@ Its schema should be considered unstable and subject to change at any time."""
190
231
  if include_sandbox:
191
232
  if not workflow_config.ignore:
192
233
  workflow_config.ignore = "sandbox.py"
193
- save_lock_file = True
194
234
  elif isinstance(workflow_config.ignore, str) and "sandbox.py" != workflow_config.ignore:
195
235
  workflow_config.ignore = [workflow_config.ignore, "sandbox.py"]
196
- save_lock_file = True
197
236
  elif isinstance(workflow_config.ignore, list) and "sandbox.py" not in workflow_config.ignore:
198
237
  workflow_config.ignore.append("sandbox.py")
199
- save_lock_file = True
200
238
 
201
- if save_lock_file:
202
- config.save()
239
+ config.save()
203
240
 
204
241
  if error_content:
205
242
  logger.error(error_content)