vellum-ai 0.13.14__py3-none-any.whl → 0.13.15__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.13.13",
21
+ "X-Fern-SDK-Version": "0.13.15",
22
22
  }
23
23
  headers["X_API_KEY"] = self.api_key
24
24
  return headers
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 0.13.14
3
+ Version: 0.13.15
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -1,20 +1,20 @@
1
1
  vellum_cli/CONTRIBUTING.md,sha256=FtDC7BGxSeMnwCXAUssFsAIElXtmJE-O5Z7BpolcgvI,2935
2
2
  vellum_cli/README.md,sha256=2NudRoLzWxNKqnuVy1JuQ7DerIaxWGYkrH8kMd-asIE,90
3
- vellum_cli/__init__.py,sha256=tRcOi6oLzT3EXORlHwGzo0Sn1-npf8Jf7mMi90NDUh0,10035
3
+ vellum_cli/__init__.py,sha256=uEn2Nlt2Z0kBc79NcO4-rgOIE7H9nsMEEjWF6MLDlPo,10591
4
4
  vellum_cli/aliased_group.py,sha256=ugW498j0yv4ALJ8vS9MsO7ctDW7Jlir9j6nE_uHAP8c,3363
5
5
  vellum_cli/config.py,sha256=LVRB-SEJcpQYfg2QGcjKHmRSAijdSFADbS90gDY4AI8,6829
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
9
  vellum_cli/pull.py,sha256=zf0y22XptUYI_hMP_4Q1CEo9s2wALsTJcCXNd-_ibd8,7551
10
- vellum_cli/push.py,sha256=0Q3Of25KzqDq0KTgkXQ22nAhSH4_JI424hKwKkj3RWs,8627
10
+ vellum_cli/push.py,sha256=VuAQ26stoweihwiY8ms8RpU6i200fO5SjU7MiMIDvAo,9217
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
16
  vellum_cli/tests/test_pull.py,sha256=KRvQ_fwsFH6tBU49O-KOOAzcHxdB-DPbUkdIdahjuTs,19841
17
- vellum_cli/tests/test_push.py,sha256=3to7bOOVoWB888ee7TIAVl2VgXCsJhP_aPcrXwF9c_c,14023
17
+ vellum_cli/tests/test_push.py,sha256=AgJVaDeIDa--iVvDrUMxjuvOoC-ylwD_A5KumUJTtJU,18810
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
@@ -112,7 +112,7 @@ vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
112
112
  vellum/client/__init__.py,sha256=8nZt88C9SVwWanjLbIQMU3rzb32h5UZfFMBx3VPHB50,111887
113
113
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
114
114
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
115
- vellum/client/core/client_wrapper.py,sha256=WBZxe9jwGagoDy-B3qFt1xxFJYUu4kED2-27248SBsQ,1869
115
+ vellum/client/core/client_wrapper.py,sha256=_D3P4oNIluyIpVSoNUIGx4jK18hJGMdrTL62pI-ApYg,1869
116
116
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
117
117
  vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
118
118
  vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
@@ -1441,8 +1441,8 @@ vellum/workflows/vellum_client.py,sha256=ODrq_TSl-drX2aezXegf7pizpWDVJuTXH-j6528
1441
1441
  vellum/workflows/workflows/__init__.py,sha256=KY45TqvavCCvXIkyCFMEc0dc6jTMOUci93U2DUrlZYc,66
1442
1442
  vellum/workflows/workflows/base.py,sha256=k0kUWWko4fHyCqLSU_1cBK_pXZpl9MXekWiG-bdOAo0,18353
1443
1443
  vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnadGsrSZGa7t7LpJA,2008
1444
- vellum_ai-0.13.14.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1445
- vellum_ai-0.13.14.dist-info/METADATA,sha256=-XRE0gS6VYSqoJt7wrYK1LcXz99LGaGwgY-9L7THqsg,5335
1446
- vellum_ai-0.13.14.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1447
- vellum_ai-0.13.14.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1448
- vellum_ai-0.13.14.dist-info/RECORD,,
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,,
vellum_cli/__init__.py CHANGED
@@ -56,6 +56,12 @@ def workflows():
56
56
 
57
57
  @workflows.command(name="push")
58
58
  @click.argument("module", required=False)
59
+ @click.option(
60
+ "--workflow-sandbox-id",
61
+ type=str,
62
+ help="""The specific Workflow Sandbox ID to use when pushing. Must either be already associated \
63
+ with the provided module or be available for use. The Workflow Sandbox must also exist in Vellum.""",
64
+ )
59
65
  @click.option("--deploy", is_flag=True, help="Deploy the Workflow after pushing it to Vellum")
60
66
  @click.option("--deployment-label", type=str, help="Label to use for the Deployment")
61
67
  @click.option("--deployment-name", type=str, help="Unique name for the Deployment")
@@ -74,6 +80,7 @@ def workflows():
74
80
  @click.option("--workspace", type=str, help="The specific Workspace config to use when pushing")
75
81
  def workflows_push(
76
82
  module: Optional[str],
83
+ workflow_sandbox_id: Optional[str],
77
84
  deploy: Optional[bool],
78
85
  deployment_label: Optional[str],
79
86
  deployment_name: Optional[str],
@@ -90,6 +97,7 @@ def workflows_push(
90
97
 
91
98
  push_command(
92
99
  module=module,
100
+ workflow_sandbox_id=workflow_sandbox_id,
93
101
  deploy=deploy,
94
102
  deployment_label=deployment_label,
95
103
  deployment_name=deployment_name,
@@ -103,6 +111,7 @@ def workflows_push(
103
111
 
104
112
  @push.command(name="*", hidden=True)
105
113
  @click.pass_context
114
+ @click.option("--workflow-sandbox-id", type=str, help="The specific Workflow Sandbox ID to use when pushing")
106
115
  @click.option("--deploy", is_flag=True, help="Deploy the Resource after pushing it to Vellum")
107
116
  @click.option("--deployment-label", type=str, help="Label to use for the Deployment")
108
117
  @click.option("--deployment-name", type=str, help="Unique name for the Deployment")
@@ -121,6 +130,7 @@ def workflows_push(
121
130
  @click.option("--workspace", type=str, help="The specific Workspace config to use when pushing")
122
131
  def push_module(
123
132
  ctx: click.Context,
133
+ workflow_sandbox_id: Optional[str],
124
134
  deploy: Optional[bool],
125
135
  deployment_label: Optional[str],
126
136
  deployment_name: Optional[str],
@@ -135,6 +145,7 @@ def push_module(
135
145
  if ctx.parent:
136
146
  push_command(
137
147
  module=ctx.parent.invoked_subcommand,
148
+ workflow_sandbox_id=workflow_sandbox_id,
138
149
  deploy=deploy,
139
150
  deployment_label=deployment_label,
140
151
  deployment_name=deployment_name,
vellum_cli/push.py CHANGED
@@ -23,6 +23,7 @@ from vellum_ee.workflows.display.workflows.vellum_workflow_display import Vellum
23
23
 
24
24
  def push_command(
25
25
  module: Optional[str] = None,
26
+ workflow_sandbox_id: Optional[str] = None,
26
27
  deploy: Optional[bool] = None,
27
28
  deployment_label: Optional[str] = None,
28
29
  deployment_name: Optional[str] = None,
@@ -39,13 +40,24 @@ def push_command(
39
40
  if not config.workflows:
40
41
  raise ValueError("No Workflows found in project to push.")
41
42
 
42
- if len(config.workflows) > 1 and not module:
43
- raise ValueError("Multiple workflows found in project to push. Pushing only a single workflow is supported.")
43
+ workflow_configs = (
44
+ [
45
+ w
46
+ for w in config.workflows
47
+ if (module and w.module == module) or (workflow_sandbox_id and w.workflow_sandbox_id == workflow_sandbox_id)
48
+ ]
49
+ if module or workflow_sandbox_id
50
+ else config.workflows
51
+ )
44
52
 
45
- workflow_config = next((w for w in config.workflows if w.module == module), None) if module else config.workflows[0]
46
- if workflow_config is None:
53
+ if len(workflow_configs) == 0:
47
54
  raise ValueError(f"No workflow config for '{module}' found in project to push.")
48
55
 
56
+ if len(workflow_configs) > 1:
57
+ raise ValueError("Multiple workflows found in project to push. Pushing only a single workflow is supported.")
58
+
59
+ workflow_config = workflow_configs[0]
60
+
49
61
  logger.info(f"Loading workflow from {workflow_config.module}")
50
62
  resolved_workspace = workspace or workflow_config.workspace or DEFAULT_WORKSPACE_CONFIG.name
51
63
  workspace_config = (
@@ -65,6 +77,15 @@ def push_command(
65
77
  )
66
78
  config.workflows.append(workflow_config)
67
79
 
80
+ if (
81
+ workflow_sandbox_id
82
+ and workflow_config.workflow_sandbox_id
83
+ and workflow_config.workflow_sandbox_id != workflow_sandbox_id
84
+ ):
85
+ raise ValueError(
86
+ f"Workflow sandbox id '{workflow_sandbox_id}' is already associated with '{workflow_config.module}'."
87
+ )
88
+
68
89
  client = create_vellum_client(
69
90
  api_key=api_key,
70
91
  )
@@ -138,7 +159,7 @@ def push_command(
138
159
  # https://app.shortcut.com/vellum/story/5585
139
160
  exec_config=json.dumps(exec_config),
140
161
  label=label,
141
- workflow_sandbox_id=workflow_config.workflow_sandbox_id,
162
+ workflow_sandbox_id=workflow_config.workflow_sandbox_id or workflow_sandbox_id,
142
163
  artifact=artifact,
143
164
  # We should check with fern if we could auto-serialize typed object fields for us
144
165
  # https://app.shortcut.com/vellum/story/5568
@@ -29,6 +29,21 @@ def _extract_tar_gz(tar_gz_bytes: bytes) -> dict[str, str]:
29
29
  return files
30
30
 
31
31
 
32
+ def _ensure_workflow_py(temp_dir: str, module: str) -> str:
33
+ base_dir = os.path.join(temp_dir, *module.split("."))
34
+ os.makedirs(base_dir, exist_ok=True)
35
+ workflow_py_file_content = """\
36
+ from vellum.workflows import BaseWorkflow
37
+
38
+ class ExampleWorkflow(BaseWorkflow):
39
+ pass
40
+ """
41
+ with open(os.path.join(temp_dir, *module.split("."), "workflow.py"), "w") as f:
42
+ f.write(workflow_py_file_content)
43
+
44
+ return workflow_py_file_content
45
+
46
+
32
47
  def test_push__no_config(mock_module):
33
48
  # GIVEN no config file set
34
49
  mock_module.set_pyproject_toml({"workflows": []})
@@ -89,16 +104,7 @@ def test_push__happy_path(mock_module, vellum_client, base_command):
89
104
  module = mock_module.module
90
105
 
91
106
  # AND a workflow exists in the module successfully
92
- base_dir = os.path.join(temp_dir, *module.split("."))
93
- os.makedirs(base_dir, exist_ok=True)
94
- workflow_py_file_content = """\
95
- from vellum.workflows import BaseWorkflow
96
-
97
- class ExampleWorkflow(BaseWorkflow):
98
- pass
99
- """
100
- with open(os.path.join(temp_dir, *module.split("."), "workflow.py"), "w") as f:
101
- f.write(workflow_py_file_content)
107
+ workflow_py_file_content = _ensure_workflow_py(temp_dir, module)
102
108
 
103
109
  # AND the push API call returns successfully
104
110
  vellum_client.workflows.push.return_value = WorkflowPushResponse(
@@ -137,22 +143,146 @@ class ExampleWorkflow(BaseWorkflow):
137
143
  ],
138
144
  ids=["push", "workflows_push"],
139
145
  )
140
- def test_push__deployment(mock_module, vellum_client, base_command):
146
+ def test_push__workflow_sandbox_option__existing_id(mock_module, vellum_client, base_command):
141
147
  # GIVEN a single workflow configured
142
148
  temp_dir = mock_module.temp_dir
143
149
  module = mock_module.module
150
+ existing_workflow_sandbox_id = mock_module.workflow_sandbox_id
144
151
 
145
152
  # AND a workflow exists in the module successfully
146
- base_dir = os.path.join(temp_dir, *module.split("."))
147
- os.makedirs(base_dir, exist_ok=True)
148
- workflow_py_file_content = """\
149
- from vellum.workflows import BaseWorkflow
153
+ workflow_py_file_content = _ensure_workflow_py(temp_dir, module)
150
154
 
151
- class ExampleWorkflow(BaseWorkflow):
152
- pass
153
- """
154
- with open(os.path.join(temp_dir, *module.split("."), "workflow.py"), "w") as f:
155
- f.write(workflow_py_file_content)
155
+ # AND the push API call would return successfully
156
+ vellum_client.workflows.push.return_value = WorkflowPushResponse(
157
+ workflow_sandbox_id=existing_workflow_sandbox_id,
158
+ )
159
+
160
+ # WHEN calling `vellum push` with the workflow sandbox option on an existing config
161
+ runner = CliRunner()
162
+ result = runner.invoke(cli_main, base_command + [module, "--workflow-sandbox-id", existing_workflow_sandbox_id])
163
+
164
+ # THEN it should succeed
165
+ assert result.exit_code == 0
166
+
167
+ # Get the last part of the module path and format it
168
+ expected_label = mock_module.module.split(".")[-1].replace("_", " ").title()
169
+ expected_artifact_name = f"{mock_module.module.replace('.', '__')}.tar.gz"
170
+
171
+ # AND we should have called the push API with the correct args
172
+ vellum_client.workflows.push.assert_called_once()
173
+ call_args = vellum_client.workflows.push.call_args.kwargs
174
+ assert json.loads(call_args["exec_config"])["workflow_raw_data"]["definition"]["name"] == "ExampleWorkflow"
175
+ assert call_args["label"] == expected_label
176
+ assert call_args["workflow_sandbox_id"] == existing_workflow_sandbox_id
177
+ assert call_args["artifact"].name == expected_artifact_name
178
+ assert "deplyment_config" not in call_args
179
+
180
+ extracted_files = _extract_tar_gz(call_args["artifact"].read())
181
+ assert extracted_files["workflow.py"] == workflow_py_file_content
182
+
183
+
184
+ def test_push__workflow_sandbox_option__existing_no_module(mock_module, vellum_client):
185
+ # GIVEN a single workflow configured
186
+ temp_dir = mock_module.temp_dir
187
+ first_module = mock_module.module
188
+ second_module = f"{first_module}2"
189
+ first_workflow_sandbox_id = mock_module.workflow_sandbox_id
190
+ second_workflow_sandbox_id = str(uuid4())
191
+
192
+ # AND the pyproject.toml has two workflow sandboxes configured
193
+ mock_module.set_pyproject_toml(
194
+ {
195
+ "workflows": [
196
+ {"module": first_module, "workflow_sandbox_id": first_workflow_sandbox_id},
197
+ {"module": second_module, "workflow_sandbox_id": second_workflow_sandbox_id},
198
+ ]
199
+ }
200
+ )
201
+
202
+ # AND a workflow exists for both modules
203
+ _ensure_workflow_py(temp_dir, first_module)
204
+ workflow_py_file_content = _ensure_workflow_py(temp_dir, second_module)
205
+
206
+ # AND the push API call would return successfully for the second module
207
+ vellum_client.workflows.push.return_value = WorkflowPushResponse(
208
+ workflow_sandbox_id=second_workflow_sandbox_id,
209
+ )
210
+
211
+ # WHEN calling `vellum push` with the workflow sandbox option on the second module
212
+ runner = CliRunner()
213
+ result = runner.invoke(cli_main, ["workflows", "push", "--workflow-sandbox-id", second_workflow_sandbox_id])
214
+
215
+ # THEN it should succeed
216
+ assert result.exit_code == 0
217
+
218
+ # Get the last part of the module path and format it
219
+ expected_label = second_module.split(".")[-1].replace("_", " ").title()
220
+ expected_artifact_name = f"{second_module.replace('.', '__')}.tar.gz"
221
+
222
+ # AND we should have called the push API with the correct args
223
+ vellum_client.workflows.push.assert_called_once()
224
+ call_args = vellum_client.workflows.push.call_args.kwargs
225
+ assert json.loads(call_args["exec_config"])["workflow_raw_data"]["definition"]["name"] == "ExampleWorkflow"
226
+ assert call_args["label"] == expected_label
227
+ assert call_args["workflow_sandbox_id"] == second_workflow_sandbox_id
228
+ assert call_args["artifact"].name == expected_artifact_name
229
+ assert "deplyment_config" not in call_args
230
+
231
+ extracted_files = _extract_tar_gz(call_args["artifact"].read())
232
+ assert extracted_files["workflow.py"] == workflow_py_file_content
233
+
234
+
235
+ def test_push__workflow_sandbox_option__existing_id_different_module(mock_module):
236
+ # GIVEN a single workflow configured
237
+ temp_dir = mock_module.temp_dir
238
+ module = mock_module.module
239
+ second_module = f"{module}2"
240
+ first_workflow_sandbox_id = mock_module.workflow_sandbox_id
241
+ second_workflow_sandbox_id = str(uuid4())
242
+ set_pyproject_toml = mock_module.set_pyproject_toml
243
+
244
+ # AND the pyproject.toml has two workflow sandboxes configured
245
+ set_pyproject_toml(
246
+ {
247
+ "workflows": [
248
+ {"module": module, "workflow_sandbox_id": first_workflow_sandbox_id},
249
+ {"module": second_module, "workflow_sandbox_id": second_workflow_sandbox_id},
250
+ ]
251
+ }
252
+ )
253
+
254
+ # AND a workflow exists in both modules successfully
255
+ _ensure_workflow_py(temp_dir, module)
256
+ _ensure_workflow_py(temp_dir, second_module)
257
+
258
+ # WHEN calling `vellum push` with the first module and the second workflow sandbox id
259
+ runner = CliRunner()
260
+ result = runner.invoke(cli_main, ["workflows", "push", module, "--workflow-sandbox-id", second_workflow_sandbox_id])
261
+
262
+ # THEN it should fail
263
+ assert result.exit_code == 1
264
+ assert result.exception
265
+ assert (
266
+ str(result.exception)
267
+ == "Multiple workflows found in project to push. Pushing only a single workflow is supported."
268
+ )
269
+
270
+
271
+ @pytest.mark.parametrize(
272
+ "base_command",
273
+ [
274
+ ["push"],
275
+ ["workflows", "push"],
276
+ ],
277
+ ids=["push", "workflows_push"],
278
+ )
279
+ def test_push__deployment(mock_module, vellum_client, base_command):
280
+ # GIVEN a single workflow configured
281
+ temp_dir = mock_module.temp_dir
282
+ module = mock_module.module
283
+
284
+ # AND a workflow exists in the module successfully
285
+ workflow_py_file_content = _ensure_workflow_py(temp_dir, module)
156
286
 
157
287
  # AND the push API call returns successfully
158
288
  vellum_client.workflows.push.return_value = WorkflowPushResponse(
@@ -252,16 +382,7 @@ def test_push__strict_option_returns_diffs(mock_module, vellum_client):
252
382
  module = mock_module.module
253
383
 
254
384
  # AND a workflow exists in the module successfully
255
- base_dir = os.path.join(temp_dir, *module.split("."))
256
- os.makedirs(base_dir, exist_ok=True)
257
- workflow_py_file_content = """\
258
- from vellum.workflows import BaseWorkflow
259
-
260
- class ExampleWorkflow(BaseWorkflow):
261
- pass
262
- """
263
- with open(os.path.join(temp_dir, *module.split("."), "workflow.py"), "w") as f:
264
- f.write(workflow_py_file_content)
385
+ _ensure_workflow_py(temp_dir, module)
265
386
 
266
387
  # AND the push API call returns a 4xx response with diffs
267
388
  vellum_client.workflows.push.side_effect = ApiError(
@@ -356,16 +477,7 @@ MY_OTHER_VELLUM_API_KEY=aaabbbcccddd
356
477
  )
357
478
 
358
479
  # AND a workflow exists in the module successfully
359
- base_dir = os.path.join(temp_dir, *module.split("."))
360
- os.makedirs(base_dir, exist_ok=True)
361
- workflow_py_file_content = """\
362
- from vellum.workflows import BaseWorkflow
363
-
364
- class ExampleWorkflow(BaseWorkflow):
365
- pass
366
- """
367
- with open(os.path.join(temp_dir, *module.split("."), "workflow.py"), "w") as f:
368
- f.write(workflow_py_file_content)
480
+ _ensure_workflow_py(temp_dir, module)
369
481
 
370
482
  # AND the push API call returns a new workflow sandbox id
371
483
  new_workflow_sandbox_id = str(uuid4())