vellum-ai 1.11.2__py3-none-any.whl → 1.13.5__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.

Potentially problematic release.


This version of vellum-ai might be problematic. Click here for more details.

Files changed (275) hide show
  1. vellum/__init__.py +18 -0
  2. vellum/client/README.md +1 -1
  3. vellum/client/core/client_wrapper.py +2 -2
  4. vellum/client/core/force_multipart.py +4 -2
  5. vellum/client/core/http_response.py +1 -1
  6. vellum/client/core/pydantic_utilities.py +7 -4
  7. vellum/client/errors/too_many_requests_error.py +1 -2
  8. vellum/client/reference.md +677 -76
  9. vellum/client/resources/container_images/client.py +299 -0
  10. vellum/client/resources/container_images/raw_client.py +286 -0
  11. vellum/client/resources/documents/client.py +20 -10
  12. vellum/client/resources/documents/raw_client.py +20 -10
  13. vellum/client/resources/events/raw_client.py +4 -4
  14. vellum/client/resources/integration_auth_configs/client.py +2 -0
  15. vellum/client/resources/integration_auth_configs/raw_client.py +2 -0
  16. vellum/client/resources/integration_providers/client.py +28 -2
  17. vellum/client/resources/integration_providers/raw_client.py +24 -0
  18. vellum/client/resources/integrations/client.py +52 -4
  19. vellum/client/resources/integrations/raw_client.py +61 -0
  20. vellum/client/resources/workflow_deployments/client.py +156 -0
  21. vellum/client/resources/workflow_deployments/raw_client.py +334 -0
  22. vellum/client/resources/workflows/client.py +212 -8
  23. vellum/client/resources/workflows/raw_client.py +343 -6
  24. vellum/client/types/__init__.py +18 -0
  25. vellum/client/types/api_actor_type_enum.py +1 -1
  26. vellum/client/types/check_workflow_execution_status_error.py +21 -0
  27. vellum/client/types/check_workflow_execution_status_response.py +29 -0
  28. vellum/client/types/code_execution_package_request.py +21 -0
  29. vellum/client/types/composio_execute_tool_request.py +5 -0
  30. vellum/client/types/composio_tool_definition.py +1 -0
  31. vellum/client/types/container_image_build_config.py +1 -0
  32. vellum/client/types/container_image_container_image_tag.py +1 -0
  33. vellum/client/types/dataset_row_push_request.py +3 -0
  34. vellum/client/types/document_document_to_document_index.py +1 -0
  35. vellum/client/types/integration_name.py +24 -0
  36. vellum/client/types/node_execution_fulfilled_body.py +1 -0
  37. vellum/client/types/node_execution_log_body.py +24 -0
  38. vellum/client/types/node_execution_log_event.py +47 -0
  39. vellum/client/types/prompt_deployment_release_prompt_deployment.py +1 -0
  40. vellum/client/types/runner_config_request.py +24 -0
  41. vellum/client/types/severity_enum.py +5 -0
  42. vellum/client/types/slim_composio_tool_definition.py +1 -0
  43. vellum/client/types/slim_document_document_to_document_index.py +2 -0
  44. vellum/client/types/type_checker_enum.py +5 -0
  45. vellum/client/types/vellum_audio.py +5 -1
  46. vellum/client/types/vellum_audio_request.py +5 -1
  47. vellum/client/types/vellum_document.py +5 -1
  48. vellum/client/types/vellum_document_request.py +5 -1
  49. vellum/client/types/vellum_image.py +5 -1
  50. vellum/client/types/vellum_image_request.py +5 -1
  51. vellum/client/types/vellum_node_execution_event.py +2 -0
  52. vellum/client/types/vellum_variable.py +5 -0
  53. vellum/client/types/vellum_variable_extensions.py +1 -0
  54. vellum/client/types/vellum_variable_type.py +1 -0
  55. vellum/client/types/vellum_video.py +5 -1
  56. vellum/client/types/vellum_video_request.py +5 -1
  57. vellum/client/types/workflow_deployment_release_workflow_deployment.py +1 -0
  58. vellum/client/types/workflow_event.py +2 -0
  59. vellum/client/types/workflow_execution_fulfilled_body.py +1 -0
  60. vellum/client/types/workflow_result_event_output_data_array.py +1 -1
  61. vellum/client/types/workflow_result_event_output_data_chat_history.py +1 -1
  62. vellum/client/types/workflow_result_event_output_data_error.py +1 -1
  63. vellum/client/types/workflow_result_event_output_data_function_call.py +1 -1
  64. vellum/client/types/workflow_result_event_output_data_json.py +1 -1
  65. vellum/client/types/workflow_result_event_output_data_number.py +1 -1
  66. vellum/client/types/workflow_result_event_output_data_search_results.py +1 -1
  67. vellum/client/types/workflow_result_event_output_data_string.py +1 -1
  68. vellum/client/types/workflow_sandbox_execute_node_response.py +8 -0
  69. vellum/plugins/vellum_mypy.py +37 -2
  70. vellum/types/check_workflow_execution_status_error.py +3 -0
  71. vellum/types/check_workflow_execution_status_response.py +3 -0
  72. vellum/types/code_execution_package_request.py +3 -0
  73. vellum/types/node_execution_log_body.py +3 -0
  74. vellum/types/node_execution_log_event.py +3 -0
  75. vellum/types/runner_config_request.py +3 -0
  76. vellum/types/severity_enum.py +3 -0
  77. vellum/types/type_checker_enum.py +3 -0
  78. vellum/types/workflow_sandbox_execute_node_response.py +3 -0
  79. vellum/utils/files/mixin.py +26 -0
  80. vellum/utils/files/tests/test_mixin.py +62 -0
  81. vellum/utils/tests/test_vellum_client.py +95 -0
  82. vellum/utils/uuid.py +19 -2
  83. vellum/utils/vellum_client.py +10 -3
  84. vellum/workflows/__init__.py +7 -1
  85. vellum/workflows/descriptors/base.py +86 -0
  86. vellum/workflows/descriptors/tests/test_utils.py +9 -0
  87. vellum/workflows/errors/tests/__init__.py +0 -0
  88. vellum/workflows/errors/tests/test_types.py +52 -0
  89. vellum/workflows/errors/types.py +1 -0
  90. vellum/workflows/events/node.py +24 -0
  91. vellum/workflows/events/tests/test_event.py +123 -0
  92. vellum/workflows/events/types.py +2 -1
  93. vellum/workflows/events/workflow.py +28 -2
  94. vellum/workflows/expressions/add.py +3 -0
  95. vellum/workflows/expressions/tests/test_add.py +24 -0
  96. vellum/workflows/graph/graph.py +26 -5
  97. vellum/workflows/graph/tests/test_graph.py +228 -1
  98. vellum/workflows/inputs/base.py +22 -6
  99. vellum/workflows/inputs/dataset_row.py +121 -16
  100. vellum/workflows/inputs/tests/test_inputs.py +3 -3
  101. vellum/workflows/integrations/tests/test_vellum_integration_service.py +84 -0
  102. vellum/workflows/integrations/vellum_integration_service.py +12 -1
  103. vellum/workflows/loaders/base.py +2 -0
  104. vellum/workflows/nodes/bases/base.py +37 -16
  105. vellum/workflows/nodes/bases/tests/test_base_node.py +104 -1
  106. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +1 -0
  107. vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +1 -1
  108. vellum/workflows/nodes/core/map_node/node.py +7 -5
  109. vellum/workflows/nodes/core/map_node/tests/test_node.py +33 -0
  110. vellum/workflows/nodes/core/retry_node/node.py +1 -0
  111. vellum/workflows/nodes/core/try_node/node.py +1 -0
  112. vellum/workflows/nodes/displayable/api_node/node.py +3 -2
  113. vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +38 -0
  114. vellum/workflows/nodes/displayable/bases/api_node/node.py +1 -1
  115. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +18 -1
  116. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +109 -2
  117. vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +13 -2
  118. vellum/workflows/nodes/displayable/code_execution_node/node.py +9 -15
  119. vellum/workflows/nodes/displayable/code_execution_node/tests/test_node.py +65 -24
  120. vellum/workflows/nodes/displayable/code_execution_node/utils.py +3 -0
  121. vellum/workflows/nodes/displayable/final_output_node/node.py +24 -69
  122. vellum/workflows/nodes/displayable/final_output_node/tests/test_node.py +53 -3
  123. vellum/workflows/nodes/displayable/note_node/node.py +4 -1
  124. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +16 -5
  125. vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +47 -0
  126. vellum/workflows/nodes/displayable/tool_calling_node/node.py +74 -34
  127. vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py +204 -8
  128. vellum/workflows/nodes/displayable/tool_calling_node/utils.py +92 -71
  129. vellum/workflows/nodes/mocks.py +47 -213
  130. vellum/workflows/nodes/tests/test_mocks.py +0 -177
  131. vellum/workflows/nodes/utils.py +23 -8
  132. vellum/workflows/outputs/base.py +36 -3
  133. vellum/workflows/references/environment_variable.py +1 -11
  134. vellum/workflows/references/lazy.py +8 -0
  135. vellum/workflows/references/state_value.py +24 -1
  136. vellum/workflows/references/tests/test_lazy.py +58 -0
  137. vellum/workflows/references/trigger.py +8 -3
  138. vellum/workflows/references/workflow_input.py +8 -0
  139. vellum/workflows/resolvers/resolver.py +13 -3
  140. vellum/workflows/resolvers/tests/test_resolver.py +31 -0
  141. vellum/workflows/runner/runner.py +159 -14
  142. vellum/workflows/runner/tests/__init__.py +0 -0
  143. vellum/workflows/runner/tests/test_runner.py +170 -0
  144. vellum/workflows/sandbox.py +7 -8
  145. vellum/workflows/state/base.py +89 -30
  146. vellum/workflows/state/context.py +74 -3
  147. vellum/workflows/state/tests/test_state.py +269 -1
  148. vellum/workflows/tests/test_dataset_row.py +8 -7
  149. vellum/workflows/tests/test_sandbox.py +97 -8
  150. vellum/workflows/triggers/__init__.py +2 -1
  151. vellum/workflows/triggers/base.py +160 -28
  152. vellum/workflows/triggers/chat_message.py +141 -0
  153. vellum/workflows/triggers/integration.py +12 -0
  154. vellum/workflows/triggers/manual.py +3 -1
  155. vellum/workflows/triggers/schedule.py +3 -1
  156. vellum/workflows/triggers/tests/test_chat_message.py +257 -0
  157. vellum/workflows/types/core.py +18 -0
  158. vellum/workflows/types/definition.py +6 -13
  159. vellum/workflows/types/generics.py +12 -0
  160. vellum/workflows/types/tests/test_utils.py +12 -0
  161. vellum/workflows/types/utils.py +32 -2
  162. vellum/workflows/types/workflow_metadata.py +124 -0
  163. vellum/workflows/utils/functions.py +152 -16
  164. vellum/workflows/utils/pydantic_schema.py +19 -1
  165. vellum/workflows/utils/tests/test_functions.py +123 -8
  166. vellum/workflows/utils/tests/test_validate.py +79 -0
  167. vellum/workflows/utils/tests/test_vellum_variables.py +62 -2
  168. vellum/workflows/utils/uuids.py +90 -0
  169. vellum/workflows/utils/validate.py +108 -0
  170. vellum/workflows/utils/vellum_variables.py +96 -16
  171. vellum/workflows/workflows/base.py +177 -35
  172. vellum/workflows/workflows/tests/test_base_workflow.py +51 -0
  173. {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/METADATA +6 -1
  174. {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/RECORD +274 -227
  175. vellum_cli/__init__.py +21 -0
  176. vellum_cli/config.py +16 -2
  177. vellum_cli/pull.py +2 -0
  178. vellum_cli/push.py +23 -10
  179. vellum_cli/tests/conftest.py +8 -13
  180. vellum_cli/tests/test_image_push.py +4 -11
  181. vellum_cli/tests/test_pull.py +83 -68
  182. vellum_cli/tests/test_push.py +251 -2
  183. vellum_ee/assets/node-definitions.json +225 -12
  184. vellum_ee/scripts/generate_node_definitions.py +15 -3
  185. vellum_ee/workflows/display/base.py +4 -3
  186. vellum_ee/workflows/display/nodes/base_node_display.py +44 -11
  187. vellum_ee/workflows/display/nodes/tests/test_base_node_display.py +93 -0
  188. vellum_ee/workflows/display/nodes/types.py +1 -0
  189. vellum_ee/workflows/display/nodes/vellum/__init__.py +0 -2
  190. vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py +5 -2
  191. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -1
  192. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +10 -2
  193. vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +17 -14
  194. vellum_ee/workflows/display/nodes/vellum/map_node.py +2 -0
  195. vellum_ee/workflows/display/nodes/vellum/note_node.py +18 -3
  196. vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +37 -14
  197. vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py +62 -2
  198. vellum_ee/workflows/display/nodes/vellum/tests/test_final_output_node.py +136 -0
  199. vellum_ee/workflows/display/nodes/vellum/tests/test_note_node.py +44 -7
  200. vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_node.py +5 -13
  201. vellum_ee/workflows/display/nodes/vellum/tests/test_subworkflow_deployment_node.py +27 -17
  202. vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +145 -22
  203. vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +107 -2
  204. vellum_ee/workflows/display/nodes/vellum/utils.py +54 -12
  205. vellum_ee/workflows/display/tests/test_base_workflow_display.py +13 -16
  206. vellum_ee/workflows/display/tests/test_json_schema_validation.py +190 -0
  207. vellum_ee/workflows/display/tests/test_mocks.py +912 -0
  208. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +14 -2
  209. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +109 -0
  210. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py +3 -0
  211. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py +187 -1
  212. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +34 -325
  213. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +42 -393
  214. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +13 -315
  215. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_default_state_serialization.py +2 -122
  216. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +24 -115
  217. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +4 -93
  218. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +7 -80
  219. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py +9 -101
  220. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +77 -308
  221. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +62 -324
  222. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +3 -82
  223. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +4 -142
  224. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +1 -61
  225. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_set_state_node_serialization.py +4 -4
  226. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +205 -134
  227. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +34 -146
  228. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +2 -0
  229. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py +8 -6
  230. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +137 -266
  231. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_tool_wrapper_serialization.py +84 -0
  232. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_mcp_serialization.py +55 -16
  233. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +15 -1
  234. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_tool_wrapper_serialization.py +71 -0
  235. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_vellum_integration_serialization.py +119 -0
  236. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_workflow_deployment_serialization.py +1 -1
  237. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +0 -2
  238. vellum_ee/workflows/display/tests/workflow_serialization/test_chat_message_dict_reference_serialization.py +22 -1
  239. vellum_ee/workflows/display/tests/workflow_serialization/test_chat_message_trigger_serialization.py +412 -0
  240. vellum_ee/workflows/display/tests/workflow_serialization/test_code_tool_node_reference_error.py +106 -0
  241. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +9 -41
  242. vellum_ee/workflows/display/tests/workflow_serialization/test_duplicate_trigger_name_validation.py +208 -0
  243. vellum_ee/workflows/display/tests/workflow_serialization/test_final_output_node_not_referenced_by_workflow_outputs.py +45 -0
  244. vellum_ee/workflows/display/tests/workflow_serialization/test_infinite_loop_validation.py +66 -0
  245. vellum_ee/workflows/display/tests/workflow_serialization/test_int_input_serialization.py +40 -0
  246. vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_serialization.py +8 -14
  247. vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_validation.py +173 -0
  248. vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_with_entrypoint_node_id.py +16 -13
  249. vellum_ee/workflows/display/tests/workflow_serialization/test_list_vellum_document_serialization.py +5 -1
  250. vellum_ee/workflows/display/tests/workflow_serialization/test_manual_trigger_serialization.py +12 -2
  251. vellum_ee/workflows/display/tests/workflow_serialization/test_multi_trigger_same_node_serialization.py +111 -0
  252. vellum_ee/workflows/display/tests/workflow_serialization/test_no_triggers_no_entrypoint_validation.py +64 -0
  253. vellum_ee/workflows/display/tests/workflow_serialization/test_partial_workflow_meta_display_override.py +55 -0
  254. vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_dataset_mocks_serialization.py +268 -0
  255. vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_invalid_pdf_data_url.py +49 -0
  256. vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_validation_errors.py +112 -0
  257. vellum_ee/workflows/display/tests/workflow_serialization/test_scheduled_trigger_serialization.py +25 -16
  258. vellum_ee/workflows/display/tests/workflow_serialization/test_terminal_node_in_unused_graphs_serialization.py +53 -0
  259. vellum_ee/workflows/display/utils/exceptions.py +34 -0
  260. vellum_ee/workflows/display/utils/expressions.py +463 -52
  261. vellum_ee/workflows/display/utils/metadata.py +98 -33
  262. vellum_ee/workflows/display/utils/tests/test_metadata.py +31 -0
  263. vellum_ee/workflows/display/utils/triggers.py +153 -0
  264. vellum_ee/workflows/display/utils/vellum.py +59 -5
  265. vellum_ee/workflows/display/workflows/base_workflow_display.py +656 -254
  266. vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +26 -0
  267. vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +77 -29
  268. vellum_ee/workflows/server/namespaces.py +18 -0
  269. vellum_ee/workflows/tests/test_display_meta.py +2 -0
  270. vellum_ee/workflows/tests/test_serialize_module.py +174 -7
  271. vellum_ee/workflows/tests/test_server.py +0 -3
  272. vellum_ee/workflows/display/nodes/vellum/function_node.py +0 -14
  273. {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/LICENSE +0 -0
  274. {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/WHEEL +0 -0
  275. {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/entry_points.txt +0 -0
vellum_cli/__init__.py CHANGED
@@ -208,6 +208,11 @@ Helpful for running and debugging workflows locally.""",
208
208
  help="""Directory to pull the workflow into. If not specified, \
209
209
  the workflow will be pulled into the current working directory.""",
210
210
  )
211
+ @click.option(
212
+ "--release-tag",
213
+ type=str,
214
+ help="""Release tag to use when pulling from a deployment.""",
215
+ )
211
216
  def pull(
212
217
  ctx: click.Context,
213
218
  include_json: Optional[bool],
@@ -215,6 +220,7 @@ def pull(
215
220
  strict: Optional[bool],
216
221
  include_sandbox: Optional[bool],
217
222
  target_directory: Optional[str],
223
+ release_tag: Optional[str],
218
224
  ) -> None:
219
225
  """Pull Resources from Vellum"""
220
226
 
@@ -225,6 +231,7 @@ def pull(
225
231
  strict=strict,
226
232
  include_sandbox=include_sandbox,
227
233
  target_directory=target_directory,
234
+ release_tag=release_tag,
228
235
  )
229
236
 
230
237
 
@@ -271,6 +278,11 @@ the workflow will be pulled into the current working directory.""",
271
278
  type=str,
272
279
  help="The specific Workspace config to use when pulling",
273
280
  )
281
+ @click.option(
282
+ "--release-tag",
283
+ type=str,
284
+ help="""Release tag to use when pulling from a deployment.""",
285
+ )
274
286
  def workflows_pull(
275
287
  module: Optional[str],
276
288
  include_json: Optional[bool],
@@ -281,6 +293,7 @@ def workflows_pull(
281
293
  include_sandbox: Optional[bool],
282
294
  target_directory: Optional[str],
283
295
  workspace: Optional[str],
296
+ release_tag: Optional[str],
284
297
  ) -> None:
285
298
  """
286
299
  Pull Workflows from Vellum. If a module is provided, only the Workflow for that module will be pulled.
@@ -297,6 +310,7 @@ def workflows_pull(
297
310
  include_sandbox=include_sandbox,
298
311
  target_directory=target_directory,
299
312
  workspace=workspace,
313
+ release_tag=release_tag,
300
314
  )
301
315
 
302
316
 
@@ -332,6 +346,11 @@ Helpful for running and debugging resources locally.""",
332
346
  help="""Directory to pull the workflow into. If not specified, \
333
347
  the workflow will be pulled into the current working directory.""",
334
348
  )
349
+ @click.option(
350
+ "--release-tag",
351
+ type=str,
352
+ help="""Release tag to use when pulling from a deployment.""",
353
+ )
335
354
  def pull_module(
336
355
  ctx: click.Context,
337
356
  include_json: Optional[bool],
@@ -339,6 +358,7 @@ def pull_module(
339
358
  strict: Optional[bool],
340
359
  include_sandbox: Optional[bool],
341
360
  target_directory: Optional[str],
361
+ release_tag: Optional[str],
342
362
  ) -> None:
343
363
  """Pull a specific module from Vellum"""
344
364
 
@@ -350,6 +370,7 @@ def pull_module(
350
370
  strict=strict,
351
371
  include_sandbox=include_sandbox,
352
372
  target_directory=target_directory,
373
+ release_tag=release_tag,
353
374
  )
354
375
 
355
376
 
vellum_cli/config.py CHANGED
@@ -120,22 +120,36 @@ def merge_workflows_by_sandbox_id(
120
120
  else:
121
121
  # If the user defines a workflow in the pyproject.toml with a sandbox_id,
122
122
  # we merge the workflow with one of the ones in the lockfile with the same sandbox_id.
123
+ # First, try to find a lockfile workflow matching both sandbox_id AND workspace.
123
124
  other_workflow = next(
124
125
  (
125
126
  other_workflow
126
127
  for other_workflow in other_workflows
127
128
  if self_workflow.workflow_sandbox_id == other_workflow.workflow_sandbox_id
129
+ and self_workflow.workspace == other_workflow.workspace
128
130
  ),
129
131
  None,
130
132
  )
133
+ # If no exact match, fall back to matching by sandbox_id alone.
134
+ if other_workflow is None:
135
+ other_workflow = next(
136
+ (
137
+ other_workflow
138
+ for other_workflow in other_workflows
139
+ if self_workflow.workflow_sandbox_id == other_workflow.workflow_sandbox_id
140
+ ),
141
+ None,
142
+ )
131
143
  if other_workflow is not None:
132
144
  merged_workflows.append(self_workflow.merge(other_workflow))
133
145
  else:
134
146
  merged_workflows.append(self_workflow)
135
147
 
136
- workflow_sandbox_ids_so_far = {workflow.workflow_sandbox_id for workflow in merged_workflows}
148
+ # Use (workflow_sandbox_id, workspace) as the dedupe key to preserve workflows
149
+ # with the same sandbox_id but different workspaces.
150
+ workflow_keys_so_far = {(workflow.workflow_sandbox_id, workflow.workspace) for workflow in merged_workflows}
137
151
  for other_workflow in other_workflows:
138
- if other_workflow.workflow_sandbox_id not in workflow_sandbox_ids_so_far:
152
+ if (other_workflow.workflow_sandbox_id, other_workflow.workspace) not in workflow_keys_so_far:
139
153
  merged_workflows.append(other_workflow)
140
154
 
141
155
  return merged_workflows
vellum_cli/pull.py CHANGED
@@ -145,6 +145,7 @@ def pull_command(
145
145
  include_sandbox: Optional[bool] = None,
146
146
  target_directory: Optional[str] = None,
147
147
  workspace: Optional[str] = None,
148
+ release_tag: Optional[str] = None,
148
149
  ) -> None:
149
150
  load_dotenv(dotenv_path=os.path.join(os.getcwd(), ".env"))
150
151
  logger = load_cli_logger()
@@ -198,6 +199,7 @@ def pull_command(
198
199
 
199
200
  response = client.workflows.pull(
200
201
  pk,
202
+ release_tag=release_tag,
201
203
  request_options={"additional_query_parameters": query_parameters},
202
204
  )
203
205
 
vellum_cli/push.py CHANGED
@@ -130,13 +130,25 @@ def push_command(
130
130
  )
131
131
  sys.path.insert(0, os.getcwd())
132
132
 
133
- # Remove this once we could serialize using the artifact in Vembda
134
- # https://app.shortcut.com/vellum/story/5585
135
- serialization_result = BaseWorkflowDisplay.serialize_module(
136
- workflow_config.module,
137
- client=client,
138
- dry_run=dry_run or False,
139
- )
133
+ try:
134
+ serialization_result = BaseWorkflowDisplay.serialize_module(
135
+ workflow_config.module,
136
+ client=client,
137
+ dry_run=dry_run or False,
138
+ )
139
+ except ValidationError as e:
140
+ handle_cli_error(
141
+ logger,
142
+ title=f"Validation error while trying to push {workflow_config.module}",
143
+ message=str(e),
144
+ )
145
+ except Exception as e:
146
+ handle_cli_error(
147
+ logger,
148
+ title=f"Error while trying to push {workflow_config.module}",
149
+ message=str(e),
150
+ )
151
+
140
152
  exec_config = serialization_result.exec_config
141
153
 
142
154
  container_tag = workflow_config.container_image_tag
@@ -189,7 +201,7 @@ def push_command(
189
201
  module_dir = workflow_config.module.replace(".", os.path.sep)
190
202
  for root, _, files in os.walk(module_dir):
191
203
  for filename in files:
192
- if not filename.endswith(".py"):
204
+ if not BaseWorkflowDisplay.should_include_file(filename):
193
205
  continue
194
206
 
195
207
  file_path = os.path.join(root, filename)
@@ -208,16 +220,17 @@ def push_command(
208
220
 
209
221
  provided_id = workflow_config.workflow_sandbox_id or workflow_sandbox_id
210
222
 
223
+ dataset_serialized = json.dumps(serialization_result.dataset) if serialization_result.dataset else OMIT
224
+
211
225
  try:
212
226
  response = client.workflows.push(
213
- # Remove this once we could serialize using the artifact in Vembda
214
- # https://app.shortcut.com/vellum/story/5585
215
227
  exec_config=json.dumps(exec_config),
216
228
  workflow_sandbox_id=provided_id,
217
229
  artifact=artifact,
218
230
  # We should check with fern if we could auto-serialize typed object fields for us
219
231
  # https://app.shortcut.com/vellum/story/5568
220
232
  deployment_config=deployment_config_serialized, # type: ignore[arg-type]
233
+ dataset=dataset_serialized, # type: ignore[arg-type]
221
234
  dry_run=dry_run,
222
235
  strict=strict,
223
236
  )
@@ -1,8 +1,6 @@
1
1
  import pytest
2
2
  from dataclasses import dataclass
3
- import os
4
- import shutil
5
- import tempfile
3
+ import pathlib
6
4
  from uuid import uuid4
7
5
  from typing import Any, Callable, Dict, Generator
8
6
 
@@ -18,17 +16,17 @@ class MockModuleResult:
18
16
 
19
17
 
20
18
  @pytest.fixture
21
- def mock_module(request) -> Generator[MockModuleResult, None, None]:
22
- current_dir = os.getcwd()
23
- temp_dir = tempfile.mkdtemp()
24
- os.chdir(temp_dir)
19
+ def mock_module(
20
+ request, monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
21
+ ) -> Generator[MockModuleResult, None, None]:
22
+ monkeypatch.chdir(tmp_path)
25
23
 
26
24
  # Use the test name to create a unique module path
27
25
  module = f"examples.mock.{request.node.name}"
28
26
  workflow_sandbox_id = str(uuid4())
29
27
 
30
28
  def set_pyproject_toml(vellum_config: Dict[str, Any]) -> None:
31
- pyproject_toml_path = os.path.join(temp_dir, "pyproject.toml")
29
+ pyproject_toml_path = tmp_path / "pyproject.toml"
32
30
  with open(pyproject_toml_path, "wb") as f:
33
31
  tomli_w.dump(
34
32
  {"tool": {"vellum": vellum_config}},
@@ -46,19 +44,16 @@ def mock_module(request) -> Generator[MockModuleResult, None, None]:
46
44
  }
47
45
  )
48
46
 
49
- with open(os.path.join(temp_dir, ".env"), "w") as f:
47
+ with open(tmp_path / ".env", "w") as f:
50
48
  f.write("VELLUM_API_KEY=abcdef123456")
51
49
 
52
50
  yield MockModuleResult(
53
- temp_dir=temp_dir,
51
+ temp_dir=str(tmp_path),
54
52
  module=module,
55
53
  set_pyproject_toml=set_pyproject_toml,
56
54
  workflow_sandbox_id=workflow_sandbox_id,
57
55
  )
58
56
 
59
- os.chdir(current_dir)
60
- shutil.rmtree(temp_dir)
61
-
62
57
 
63
58
  @pytest.fixture
64
59
  def info_log_level(monkeypatch):
@@ -1,9 +1,8 @@
1
1
  import pytest
2
2
  import json
3
3
  import os
4
- import shutil
4
+ import pathlib
5
5
  import subprocess
6
- import tempfile
7
6
  from unittest.mock import MagicMock
8
7
  from uuid import uuid4
9
8
  from typing import Generator
@@ -16,15 +15,9 @@ from vellum_cli import main as cli_main
16
15
 
17
16
 
18
17
  @pytest.fixture
19
- def mock_temp_dir() -> Generator[str, None, None]:
20
- current_dir = os.getcwd()
21
- temp_dir = tempfile.mkdtemp()
22
- os.chdir(temp_dir)
23
-
24
- yield temp_dir
25
-
26
- os.chdir(current_dir)
27
- shutil.rmtree(temp_dir)
18
+ def mock_temp_dir(monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path) -> Generator[str, None, None]:
19
+ monkeypatch.chdir(tmp_path)
20
+ yield str(tmp_path)
28
21
 
29
22
 
30
23
  @pytest.fixture
@@ -1,7 +1,7 @@
1
1
  import pytest
2
2
  import json
3
3
  import os
4
- import tempfile
4
+ import pathlib
5
5
  from unittest import mock
6
6
  from uuid import uuid4
7
7
 
@@ -227,7 +227,7 @@ def test_pull__with_nested_target_dir(vellum_client, mock_module, base_command):
227
227
  }
228
228
 
229
229
 
230
- def test_pull__sandbox_id_with_no_config(vellum_client):
230
+ def test_pull__sandbox_id_with_no_config(vellum_client, monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path):
231
231
  # GIVEN a workflow sandbox id
232
232
  workflow_sandbox_id = "87654321-0000-0000-0000-000000000000"
233
233
 
@@ -237,28 +237,25 @@ def test_pull__sandbox_id_with_no_config(vellum_client):
237
237
  )
238
238
 
239
239
  # AND we are currently in a new directory
240
- current_dir = os.getcwd()
241
- temp_dir = tempfile.mkdtemp()
242
- os.chdir(temp_dir)
240
+ monkeypatch.chdir(tmp_path)
243
241
 
244
242
  # WHEN the user runs the pull command with the workflow sandbox id and no module
245
243
  runner = CliRunner()
246
244
  result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-sandbox-id", workflow_sandbox_id])
247
- os.chdir(current_dir)
248
245
 
249
246
  # THEN the command returns successfully
250
247
  assert result.exit_code == 0
251
248
 
252
249
  # AND the pull api is called with the workflow sandbox id
253
250
  vellum_client.workflows.pull.assert_called_once()
254
- workflow_py = os.path.join(temp_dir, "super_cool_workflow", "workflow.py")
255
- assert os.path.exists(workflow_py)
251
+ workflow_py = tmp_path / "super_cool_workflow" / "workflow.py"
252
+ assert workflow_py.exists()
256
253
  with open(workflow_py) as f:
257
254
  assert f.read() == "print('hello')"
258
255
 
259
256
  # AND the vellum.lock.json file is created
260
- vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
261
- assert os.path.exists(vellum_lock_json)
257
+ vellum_lock_json = tmp_path / "vellum.lock.json"
258
+ assert vellum_lock_json.exists()
262
259
  with open(vellum_lock_json) as f:
263
260
  lock_data = json.loads(f.read())
264
261
  assert lock_data == {
@@ -310,7 +307,9 @@ def test_pull__sandbox_id_with_other_workflow_configured(vellum_client, mock_mod
310
307
  assert f.read() == "print('hello')"
311
308
 
312
309
 
313
- def test_pull__workflow_deployment_with_no_config(vellum_client):
310
+ def test_pull__workflow_deployment_with_no_config(
311
+ vellum_client, monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
312
+ ):
314
313
  # GIVEN a workflow deployment
315
314
  workflow_deployment = "my-deployment"
316
315
  deployment_id = str(uuid4())
@@ -330,28 +329,25 @@ def test_pull__workflow_deployment_with_no_config(vellum_client):
330
329
  )
331
330
 
332
331
  # AND we are currently in a new directory
333
- current_dir = os.getcwd()
334
- temp_dir = tempfile.mkdtemp()
335
- os.chdir(temp_dir)
332
+ monkeypatch.chdir(tmp_path)
336
333
 
337
334
  # WHEN the user runs the pull command with the workflow deployment
338
335
  runner = CliRunner()
339
336
  result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", workflow_deployment])
340
- os.chdir(current_dir)
341
337
 
342
338
  # THEN the command returns successfully
343
339
  assert result.exit_code == 0
344
340
 
345
341
  # AND the pull api is called with the workflow deployment
346
342
  vellum_client.workflows.pull.assert_called_once()
347
- workflow_py = os.path.join(temp_dir, "my_deployment", "workflow.py")
348
- assert os.path.exists(workflow_py)
343
+ workflow_py = tmp_path / "my_deployment" / "workflow.py"
344
+ assert workflow_py.exists()
349
345
  with open(workflow_py) as f:
350
346
  assert f.read() == "print('hello')"
351
347
 
352
348
  # AND the vellum.lock.json file is created
353
- vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
354
- assert os.path.exists(vellum_lock_json)
349
+ vellum_lock_json = tmp_path / "vellum.lock.json"
350
+ assert vellum_lock_json.exists()
355
351
  with open(vellum_lock_json) as f:
356
352
  lock_data = json.loads(f.read())
357
353
  assert lock_data == {
@@ -381,7 +377,9 @@ def test_pull__workflow_deployment_with_no_config(vellum_client):
381
377
  }
382
378
 
383
379
 
384
- def test_pull__both_workflow_sandbox_id_and_deployment(vellum_client):
380
+ def test_pull__both_workflow_sandbox_id_and_deployment(
381
+ vellum_client, monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
382
+ ):
385
383
  # GIVEN a workflow sandbox id
386
384
  workflow_sandbox_id = "87654321-0000-0000-0000-000000000000"
387
385
 
@@ -392,9 +390,7 @@ def test_pull__both_workflow_sandbox_id_and_deployment(vellum_client):
392
390
  vellum_client.workflows.pull.return_value = iter([zip_file_map({"workflow.py": "print('hello')"})])
393
391
 
394
392
  # AND we are currently in a new directory
395
- current_dir = os.getcwd()
396
- temp_dir = tempfile.mkdtemp()
397
- os.chdir(temp_dir)
393
+ monkeypatch.chdir(tmp_path)
398
394
 
399
395
  # WHEN the user runs the pull command with the workflow deployment
400
396
  runner = CliRunner()
@@ -409,7 +405,6 @@ def test_pull__both_workflow_sandbox_id_and_deployment(vellum_client):
409
405
  workflow_deployment,
410
406
  ],
411
407
  )
412
- os.chdir(current_dir)
413
408
 
414
409
  # THEN the command returns successfully
415
410
  assert result.exit_code == 1
@@ -823,7 +818,7 @@ def test_pull__multiple_instances_of_same_module__keep_when_pulling_another_modu
823
818
  assert len(lock_data["workflows"]) == 3
824
819
 
825
820
 
826
- def test_pull__module_name_from_deployment_name(vellum_client):
821
+ def test_pull__module_name_from_deployment_name(vellum_client, monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path):
827
822
  # GIVEN a workflow deployment
828
823
  workflow_deployment = "test-workflow-deployment-id"
829
824
 
@@ -840,29 +835,28 @@ def test_pull__module_name_from_deployment_name(vellum_client):
840
835
  ]
841
836
  )
842
837
 
843
- # AND we are currently in a new directory
844
- current_dir = os.getcwd()
845
- temp_dir = tempfile.mkdtemp()
846
- os.chdir(temp_dir)
838
+ # AND we are currently in a new directory with a .env file
839
+ monkeypatch.chdir(tmp_path)
840
+ with open(tmp_path / ".env", "w") as f:
841
+ f.write("VELLUM_API_KEY=test-api-key")
847
842
 
848
843
  # WHEN the user runs the pull command with the workflow deployment
849
844
  runner = CliRunner()
850
845
  result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", workflow_deployment])
851
- os.chdir(current_dir)
852
846
 
853
847
  # THEN the command returns successfully
854
848
  assert result.exit_code == 0
855
849
 
856
850
  # AND the module name is derived from the deployment_name in metadata.json (snake_cased)
857
- vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
858
- assert os.path.exists(vellum_lock_json)
851
+ vellum_lock_json = tmp_path / "vellum.lock.json"
852
+ assert vellum_lock_json.exists()
859
853
  with open(vellum_lock_json) as f:
860
854
  lock_data = json.loads(f.read())
861
855
  assert lock_data["workflows"][0]["module"] == "test_deployment"
862
856
 
863
857
  # AND the workflow.py file is written to the module directory with the correct name
864
- workflow_py = os.path.join(temp_dir, "test_deployment", "workflow.py")
865
- assert os.path.exists(workflow_py)
858
+ workflow_py = tmp_path / "test_deployment" / "workflow.py"
859
+ assert workflow_py.exists()
866
860
  with open(workflow_py) as f:
867
861
  assert f.read() == "print('hello')"
868
862
 
@@ -958,10 +952,12 @@ def test_pull__unexpected_error_path(vellum_client):
958
952
  "workflow_deployment",
959
953
  [
960
954
  "test-workflow-deployment-id",
961
- str(uuid4()),
955
+ "377a59fb-1dd0-4760-9568-ae4bd6188da0",
962
956
  ],
963
957
  )
964
- def test_pull__workflow_deployment_adds_deployment_to_config(vellum_client, workflow_deployment):
958
+ def test_pull__workflow_deployment_adds_deployment_to_config(
959
+ vellum_client, workflow_deployment, monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
960
+ ):
965
961
  # GIVEN a workflow deployment ID
966
962
  deployment_id = str(uuid4()) # config will always use the deployment_id return from the API
967
963
  deployment_name = "Test Deployment"
@@ -986,9 +982,8 @@ def test_pull__workflow_deployment_adds_deployment_to_config(vellum_client, work
986
982
  )
987
983
 
988
984
  # AND we are currently in a new directory
989
- current_dir = os.getcwd()
990
- temp_dir = tempfile.mkdtemp()
991
- os.chdir(temp_dir)
985
+ monkeypatch.chdir(tmp_path)
986
+ monkeypatch.setenv("VELLUM_API_KEY", "abcdef123456")
992
987
 
993
988
  # WHEN the user runs the pull command with the workflow deployment
994
989
  runner = CliRunner()
@@ -998,8 +993,8 @@ def test_pull__workflow_deployment_adds_deployment_to_config(vellum_client, work
998
993
  assert result.exit_code == 0
999
994
 
1000
995
  # AND the deployment is saved in the config
1001
- vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
1002
- assert os.path.exists(vellum_lock_json)
996
+ vellum_lock_json = tmp_path / "vellum.lock.json"
997
+ assert vellum_lock_json.exists()
1003
998
  with open(vellum_lock_json) as f:
1004
999
  lock_data = json.loads(f.read())
1005
1000
  assert len(lock_data["workflows"]) == 1
@@ -1009,21 +1004,18 @@ def test_pull__workflow_deployment_adds_deployment_to_config(vellum_client, work
1009
1004
  assert deployment["name"] == deployment_name
1010
1005
  assert deployment["label"] == deployment_label
1011
1006
 
1012
- os.chdir(current_dir)
1013
-
1014
1007
 
1015
- def test_pull__workflow_deployment_name_is_uuid(vellum_client):
1008
+ def test_pull__workflow_deployment_name_is_uuid(vellum_client, monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path):
1016
1009
  # GIVEN a workflow deployment name that is a valid UUID
1017
1010
  deployment_id = str(uuid4())
1018
1011
  deployment_name = str(uuid4())
1019
1012
 
1020
1013
  # AND an existing configuration with this deployment
1021
- current_dir = os.getcwd()
1022
- temp_dir = tempfile.mkdtemp()
1023
- os.chdir(temp_dir)
1014
+ monkeypatch.chdir(tmp_path)
1015
+ monkeypatch.setenv("VELLUM_API_KEY", "abcdef123456")
1024
1016
 
1025
1017
  # Create initial config with a deployment
1026
- vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
1018
+ vellum_lock_json = tmp_path / "vellum.lock.json"
1027
1019
  with open(vellum_lock_json, "w") as f:
1028
1020
  json.dump(
1029
1021
  {
@@ -1089,11 +1081,11 @@ def test_pull__workflow_deployment_name_is_uuid(vellum_client):
1089
1081
  assert deployment["name"] == deployment_name
1090
1082
  assert deployment["label"] == updated_label
1091
1083
 
1092
- os.chdir(current_dir)
1093
-
1094
1084
 
1095
1085
  @pytest.mark.parametrize("get_identifier", [(lambda d: d), (lambda d: "Test Name")])
1096
- def test_pull__workflow_deployment_updates_existing_deployment(vellum_client, get_identifier):
1086
+ def test_pull__workflow_deployment_updates_existing_deployment(
1087
+ vellum_client, get_identifier, monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
1088
+ ):
1097
1089
  """
1098
1090
  This test is to ensure that the deployment info is updated in the config
1099
1091
  when the user runs the pull command with the workflow deployment
@@ -1106,12 +1098,11 @@ def test_pull__workflow_deployment_updates_existing_deployment(vellum_client, ge
1106
1098
  deployment_name = "Test Name"
1107
1099
 
1108
1100
  # AND an existing configuration with this deployment
1109
- current_dir = os.getcwd()
1110
- temp_dir = tempfile.mkdtemp()
1111
- os.chdir(temp_dir)
1101
+ monkeypatch.chdir(tmp_path)
1102
+ monkeypatch.setenv("VELLUM_API_KEY", "abcdef123456")
1112
1103
 
1113
1104
  # Create initial config with a deployment
1114
- vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
1105
+ vellum_lock_json = tmp_path / "vellum.lock.json"
1115
1106
  with open(vellum_lock_json, "w") as f:
1116
1107
  json.dump(
1117
1108
  {
@@ -1178,10 +1169,10 @@ def test_pull__workflow_deployment_updates_existing_deployment(vellum_client, ge
1178
1169
  assert deployment["name"] == deployment_name
1179
1170
  assert deployment["label"] == updated_label
1180
1171
 
1181
- os.chdir(current_dir)
1182
-
1183
1172
 
1184
- def test_pull__workflow_deployment_with_name_and_id(vellum_client):
1173
+ def test_pull__workflow_deployment_with_name_and_id(
1174
+ vellum_client, monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
1175
+ ):
1185
1176
  """
1186
1177
  This test is to ensure that pulling with id and name will not add a new deployment to the config
1187
1178
  """
@@ -1210,9 +1201,8 @@ def test_pull__workflow_deployment_with_name_and_id(vellum_client):
1210
1201
  )
1211
1202
 
1212
1203
  # AND we are currently in a new directory
1213
- current_dir = os.getcwd()
1214
- temp_dir = tempfile.mkdtemp()
1215
- os.chdir(temp_dir)
1204
+ monkeypatch.chdir(tmp_path)
1205
+ monkeypatch.setenv("VELLUM_API_KEY", "abcdef123456")
1216
1206
 
1217
1207
  # WHEN the user runs the pull command with the workflow deployment
1218
1208
  runner = CliRunner()
@@ -1222,8 +1212,8 @@ def test_pull__workflow_deployment_with_name_and_id(vellum_client):
1222
1212
  assert result.exit_code == 0
1223
1213
 
1224
1214
  # AND the deployment is saved in the config
1225
- vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
1226
- assert os.path.exists(vellum_lock_json)
1215
+ vellum_lock_json = tmp_path / "vellum.lock.json"
1216
+ assert vellum_lock_json.exists()
1227
1217
  with open(vellum_lock_json) as f:
1228
1218
  lock_data = json.loads(f.read())
1229
1219
  assert len(lock_data["workflows"]) == 1
@@ -1234,8 +1224,6 @@ def test_pull__workflow_deployment_with_name_and_id(vellum_client):
1234
1224
  assert deployment["label"] == deployment_label
1235
1225
  assert lock_data["workflows"][0]["workflow_sandbox_id"] == workflow_sandbox_id
1236
1226
 
1237
- os.chdir(current_dir)
1238
-
1239
1227
  # AND pull with name will not add a new deployment to the config
1240
1228
  vellum_client.workflows.pull.return_value = iter(
1241
1229
  [
@@ -1254,7 +1242,6 @@ def test_pull__workflow_deployment_with_name_and_id(vellum_client):
1254
1242
  ]
1255
1243
  )
1256
1244
 
1257
- os.chdir(temp_dir)
1258
1245
  result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", deployment_name])
1259
1246
  assert result.exit_code == 0
1260
1247
  with open(vellum_lock_json) as f:
@@ -1264,8 +1251,6 @@ def test_pull__workflow_deployment_with_name_and_id(vellum_client):
1264
1251
  assert lock_data["workflows"][0]["deployments"][0]["name"] == deployment_name
1265
1252
  assert lock_data["workflows"][0]["deployments"][0]["label"] == deployment_label
1266
1253
 
1267
- os.chdir(current_dir)
1268
-
1269
1254
 
1270
1255
  def test_pull__workspace_option__uses_different_api_key(mock_module, vellum_client_class):
1271
1256
  # GIVEN a module and workflow_sandbox_id
@@ -1337,3 +1322,33 @@ MY_OTHER_VELLUM_API_KEY=aaabbbcccddd
1337
1322
  assert os.path.exists(workflow_py)
1338
1323
  with open(workflow_py) as f:
1339
1324
  assert f.read() == "print('hello')"
1325
+
1326
+
1327
+ @pytest.mark.parametrize(
1328
+ "base_command",
1329
+ [
1330
+ ["pull"],
1331
+ ["workflows", "pull"],
1332
+ ],
1333
+ ids=["pull", "workflows_pull"],
1334
+ )
1335
+ def test_pull__release_tag(vellum_client, mock_module, base_command):
1336
+ """Tests that the --release-tag option is passed to the API."""
1337
+
1338
+ # GIVEN a module on the user's filesystem
1339
+ module = mock_module.module
1340
+
1341
+ # AND the workflow pull API call returns a zip file
1342
+ vellum_client.workflows.pull.return_value = iter([zip_file_map({"workflow.py": "print('hello')"})])
1343
+
1344
+ # WHEN the user runs the pull command with --release-tag
1345
+ runner = CliRunner()
1346
+ result = runner.invoke(cli_main, base_command + [module, "--release-tag", "my-release-tag"])
1347
+
1348
+ # THEN the command returns successfully
1349
+ assert result.exit_code == 0
1350
+
1351
+ # AND the pull api is called with release_tag="my-release-tag"
1352
+ vellum_client.workflows.pull.assert_called_once()
1353
+ call_args = vellum_client.workflows.pull.call_args.kwargs
1354
+ assert call_args["release_tag"] == "my-release-tag"