vellum-ai 0.14.78__py3-none-any.whl → 0.14.80__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,7 +18,7 @@ class BaseClientWrapper:
18
18
  headers: typing.Dict[str, str] = {
19
19
  "X-Fern-Language": "Python",
20
20
  "X-Fern-SDK-Name": "vellum-ai",
21
- "X-Fern-SDK-Version": "0.14.78",
21
+ "X-Fern-SDK-Version": "0.14.80",
22
22
  }
23
23
  headers["X-API-KEY"] = self.api_key
24
24
  return headers
@@ -1,14 +1,15 @@
1
1
  # This file was auto-generated by Fern from our API Definition.
2
2
 
3
3
  from ..core.pydantic_utilities import UniversalBaseModel
4
- from ..core.pydantic_utilities import IS_PYDANTIC_V2
5
4
  import typing
5
+ from ..core.pydantic_utilities import IS_PYDANTIC_V2
6
6
  import pydantic
7
7
 
8
8
 
9
9
  class CodeExecutionPackage(UniversalBaseModel):
10
10
  version: str
11
11
  name: str
12
+ repository: typing.Optional[str] = None
12
13
 
13
14
  if IS_PYDANTIC_V2:
14
15
  model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
@@ -49,9 +49,12 @@ def resolve_combined_types(
49
49
  lhs_types = resolve_types(lhs)
50
50
  rhs_types = resolve_types(rhs)
51
51
 
52
- unique_results = set(lhs_types) | set(rhs_types)
52
+ result_types: List[Union[Type[_LHS], Type[_RHS]]] = list(lhs_types)
53
+ for rhs_type in rhs_types:
54
+ if rhs_type not in result_types:
55
+ result_types.append(rhs_type)
53
56
 
54
- return tuple(unique_results)
57
+ return tuple(result_types)
55
58
 
56
59
 
57
60
  def infer_types(object_: Type, attr_name: str, localns: Optional[Dict[str, Any]] = None) -> Tuple[Type, ...]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 0.14.78
3
+ Version: 0.14.80
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -1,6 +1,6 @@
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=oi-vvNVepu23IBBjhA5uUIDVYBPqQ3EzxWZAPn2S64c,12700
3
+ vellum_cli/__init__.py,sha256=50dYVkBzGUS1i-UKf_9zv1JMif1lLClriNUSs-Z7i4E,12678
4
4
  vellum_cli/aliased_group.py,sha256=ugW498j0yv4ALJ8vS9MsO7ctDW7Jlir9j6nE_uHAP8c,3363
5
5
  vellum_cli/config.py,sha256=v5BmZ-t_v4Jmqd7KVuQMZF2pRI-rbMspSkVYXIRoTmI,9448
6
6
  vellum_cli/image_push.py,sha256=rPeSfTlFpdTzwTdnXRL82OVd61qMoGFGLxnLjZLU98Q,10596
@@ -8,7 +8,7 @@ vellum_cli/init.py,sha256=WpnMXPItPmh0f0bBGIer3p-e5gu8DUGwSArT_FuoMEw,5093
8
8
  vellum_cli/logger.py,sha256=dcM_OmgqXLo93vDYswO5ylyUQQcTfnA5GTd5tbIt3wM,1446
9
9
  vellum_cli/ping.py,sha256=p_BCCRjgPhng6JktuECtkDQLbhopt6JpmrtGoLnLJT8,1161
10
10
  vellum_cli/pull.py,sha256=udYyPlJ6VKDdh78rApNJOZgxHl82fcV6iGnRPSdX1LY,14750
11
- vellum_cli/push.py,sha256=ibAaf6zO41Qrgfholl18bCq8sWVYd2PDCiQsPkEYAFw,10296
11
+ vellum_cli/push.py,sha256=sUo0d7lm7saGxQciH-JW2FB_gWfP2gwoEHO1wGnNQR0,10933
12
12
  vellum_cli/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  vellum_cli/tests/conftest.py,sha256=wx3PlJjVB0HRf5dr2b_idOIw27WPPl0J0FNbhIJJaVk,1689
14
14
  vellum_cli/tests/test_config.py,sha256=uvKGDc8BoVyT9_H0Z-g8469zVxomn6Oi3Zj-vK7O_wU,2631
@@ -18,7 +18,7 @@ vellum_cli/tests/test_init.py,sha256=8UOc_ThfouR4ja5cCl_URuLk7ohr9JXfCnG4yka1OUQ
18
18
  vellum_cli/tests/test_main.py,sha256=qDZG-aQauPwBwM6A2DIu1494n47v3pL28XakTbLGZ-k,272
19
19
  vellum_cli/tests/test_ping.py,sha256=178EJHxPZtnnPMNXXynsQt8DIFhsrdc2bL17_YsG17M,2580
20
20
  vellum_cli/tests/test_pull.py,sha256=hxMbW_j0weDDrkzVGpvLpFcwNQdn-fxTv4wBHeYizzc,49904
21
- vellum_cli/tests/test_push.py,sha256=rasQsxMYsSKgi_iaYz5cBD2ZBHusxhG9FAPx1Tn7mE4,35382
21
+ vellum_cli/tests/test_push.py,sha256=wk9jQTV565TeukR0Vz-2AUQyJa3VlOJrmWxkSTX_Ptw,37860
22
22
  vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  vellum_ee/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -35,7 +35,7 @@ vellum_ee/workflows/display/nodes/types.py,sha256=St1BB6no528OyELGiyRabWao0GGw6m
35
35
  vellum_ee/workflows/display/nodes/utils.py,sha256=sloya5TpXsnot1HURc9L51INwflRqUzHxRVnCS9Cd-4,973
36
36
  vellum_ee/workflows/display/nodes/vellum/__init__.py,sha256=nUIgH2s0-7IbQRNrBhLPyRNe8YIrx3Yo9HeeW-aXXFk,1668
37
37
  vellum_ee/workflows/display/nodes/vellum/api_node.py,sha256=5Uq8x08F64yrBcqbfsVeuoGnTa9eoOPumYzZZrDPmr0,8847
38
- vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py,sha256=rJbHZBg9A_v2bjk-R6MfWzShcrS2gcKIOyYGoqwTx8s,6353
38
+ vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py,sha256=FHhPoGmmny4Xcxi2pm12Sk3ZOREanWEVrOWcjRhncH4,6337
39
39
  vellum_ee/workflows/display/nodes/vellum/code_execution_node.py,sha256=6lavdBw297GwAQqyxjnPUtx5pHv6k5V9Vkuq7s2D0TM,4508
40
40
  vellum_ee/workflows/display/nodes/vellum/conditional_node.py,sha256=9GtbvSJUNF626tCYxnMxETVZm3Fq84vOZ3Nkdkl3n-M,11146
41
41
  vellum_ee/workflows/display/nodes/vellum/error_node.py,sha256=YhMsi2TG1zSR8E7IpxzzSncOyVLcvqTuGa3mr4RqHd8,2364
@@ -62,7 +62,7 @@ vellum_ee/workflows/display/nodes/vellum/tests/test_retry_node.py,sha256=h93ysol
62
62
  vellum_ee/workflows/display/nodes/vellum/tests/test_search_node.py,sha256=KvByxgbUkVyfPIVxTUBUk6a92JiJMi8eReZWxzfPExU,3864
63
63
  vellum_ee/workflows/display/nodes/vellum/tests/test_subworkflow_deployment_node.py,sha256=BUzHJgjdWnPeZxjFjHfDBKnbFjYjnbXPjc-1hne1B2Y,3965
64
64
  vellum_ee/workflows/display/nodes/vellum/tests/test_templating_node.py,sha256=LSk2gx9TpGXbAqKe8dggQW8yJZqj-Cf0EGJFeGGlEcw,3321
65
- vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py,sha256=ZsLIGnJ9QKrXjYeDW8LEN8M9FnWRQ9TohHFyB6144HY,7970
65
+ vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py,sha256=YMoLNWknmSsFj7MtprenOYuXTX1VWUQ2bXZs_p76gBg,8163
66
66
  vellum_ee/workflows/display/nodes/vellum/tests/test_try_node.py,sha256=Khjsb53PKpZuyhKoRMgKAL45eGp5hZqXvHmVeQWRw4w,2289
67
67
  vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py,sha256=3LS1O4DGPWit05oj_ubeW8AlHGnoBxdUMferGQuAiZs,4851
68
68
  vellum_ee/workflows/display/nodes/vellum/try_node.py,sha256=z9Omo676RRc7mQjLoL7hjiHhUj0OWVLhrrb97YTN4QA,4086
@@ -72,7 +72,7 @@ vellum_ee/workflows/display/tests/test_base_workflow_display.py,sha256=axUOLwpIh
72
72
  vellum_ee/workflows/display/tests/workflow_serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
73
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
74
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py,sha256=XOQDDRiG46etxTC7-_RUEutoNumXc02fo7oho4GYM0c,1900
75
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=IuoR3QrsnshZSb1ggLBvQJnTw244lHBo70KmKLd0fwk,12852
75
+ vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=aEmF6zpmmHdFgiG4V81EweOk5qO6cfukR782dYSE960,14060
76
76
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py,sha256=DFNp_iUxlyE-zCJBH9ab3e1jQEK-NSE9M2CQd4NCjY8,24853
77
77
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py,sha256=s6_mnk0pkztU59wYpSfOFpMhAJaRjmyfxM6WJGtnD4Y,6456
78
78
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py,sha256=PkSgghJDz0fpDB72HHPjLjo8LkZk-HpUkCQzRLX-iVw,40611
@@ -94,7 +94,7 @@ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_
94
94
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py,sha256=V8b6gKghLlO7PJI8xeNdnfn8aII0W_IFQvSQBQM62UQ,7721
95
95
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py,sha256=hDWtKXmGI1CKhTwTNqpu_d5RkE5n7SolMLtgd87KqTI,3856
96
96
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py,sha256=6q-lTGMp5HbQba3NsHUFSit8_zEBxIVPGE8yCw4SVx0,25720
97
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py,sha256=nG5orqTpRwx_jJDaf0Z3rPXe0SD8i4Rrp29GVmKCPBQ,9328
97
+ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py,sha256=aY76l9s_2nezx7hCfUZsPYFzvYMy-Rjd73G0DlL6s6A,9348
98
98
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_workflow_deployment_serialization.py,sha256=mova0sPD3evHiHIN1O0VynxlCp-uOcEIKve5Pd_oCDg,4069
99
99
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py,sha256=pLCyMScV88DTBXRH7jXaXOEA1GBq8NIipCUFwIAWnwI,2771
100
100
  vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py,sha256=J4ouI8KxbMfxQP2Zq_9cWMGYgbjCWmKzjCJEtnSJb0I,5829
@@ -144,7 +144,7 @@ vellum/client/README.md,sha256=CuGUYnaE0Imt0KqQ4sIPaUghCjLHkF3DdEvZWu14-8s,4807
144
144
  vellum/client/__init__.py,sha256=AYopGv2ZRVn3zsU8_km6KOvEHDbXiTPCVuYVI7bWvdA,120166
145
145
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
146
146
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
147
- vellum/client/core/client_wrapper.py,sha256=WsBBC0YLC-9nYHit_KXXwpCcD6m2VY4xxUFnXvAE9Zw,1869
147
+ vellum/client/core/client_wrapper.py,sha256=28--LC1-hrUv3Yo5HQtt5VDbxX6iu7PRzaZPVD9oylU,1869
148
148
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
149
149
  vellum/client/core/file.py,sha256=d4NNbX8XvXP32z8KpK2Xovv33nFfruIrpz0QWxlgpZk,2663
150
150
  vellum/client/core/http_client.py,sha256=Z77OIxIbL4OAB2IDqjRq_sYa5yNYAWfmdhdCSSvh6Y4,19552
@@ -265,7 +265,7 @@ vellum/client/types/code_execution_node_result_data.py,sha256=yLRzycagwxgwi-NGx8
265
265
  vellum/client/types/code_execution_node_result_output.py,sha256=ZddnayoP01i5alC12OaoNdSgLoXr_Y4Nl343xGJvJeU,1069
266
266
  vellum/client/types/code_execution_node_search_results_result.py,sha256=yPh94v7pgFL-4x1JPSnXXGihUi42i-OfOaNVHLN4jE8,740
267
267
  vellum/client/types/code_execution_node_string_result.py,sha256=uH6KO57muMko4u_xISchhvP0E-VzJfhKD_rijXgisZ8,655
268
- vellum/client/types/code_execution_package.py,sha256=2cPKwG_5pxbHND-ChLH-4lk4O5TRWSv3KCsxNZYXZyw,580
268
+ vellum/client/types/code_execution_package.py,sha256=ONMwxtIcz6zSZOd0wKaPC4Rz6S_scbYeiNZHuDMD8U4,624
269
269
  vellum/client/types/code_execution_runtime.py,sha256=ucMXj7mUYl9-B_KentTfRuRIeDq33PV3OV_GWKiG-lo,181
270
270
  vellum/client/types/code_executor_input.py,sha256=Dj6XRaIRHjbXDZ-wR_XusJkrxYD_lJ1-UZY0Ga4xeqI,694
271
271
  vellum/client/types/code_executor_response.py,sha256=Xj2d86RMtodxHdfa4L0iaMkwyF0FgM7y6uBK-8WwOgE,673
@@ -1726,7 +1726,7 @@ vellum/workflows/types/stack.py,sha256=h7NE0vXR7l9DevFBIzIAk1Zh59K-kECQtDTKOUunw
1726
1726
  vellum/workflows/types/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1727
1727
  vellum/workflows/types/tests/test_definition.py,sha256=5wh_WEnE51epkoo-4PE-JbPlg8OGJUNlaBVWa9TcNSw,993
1728
1728
  vellum/workflows/types/tests/test_utils.py,sha256=UnZog59tR577mVwqZRqqWn2fScoOU1H6up0EzS8zYhw,2536
1729
- vellum/workflows/types/utils.py,sha256=axxHbPLsnjhEOnMZrc5YarFd-P2bnsacBDQGNCvY8OY,6367
1729
+ vellum/workflows/types/utils.py,sha256=mTctHITBybpt4855x32oCKALBEcMNLn-9cCmfEKgJHQ,6498
1730
1730
  vellum/workflows/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1731
1731
  vellum/workflows/utils/functions.py,sha256=ZN0rrIBF4R_KNt1CbRPVNGR36xEMUa1T7FkgZioou-Y,7185
1732
1732
  vellum/workflows/utils/names.py,sha256=QLUqfJ1tmSEeUwBKTTiv_Qk3QGbInC2RSmlXfGXc8Wo,380
@@ -1745,8 +1745,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
1745
1745
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1746
1746
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=fROqff6AZpCIzaSwOKSdtYy4XR0UZQ6ejxL3RJOSJVs,20447
1747
1747
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1748
- vellum_ai-0.14.78.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1749
- vellum_ai-0.14.78.dist-info/METADATA,sha256=xCf9xhdMAXgdPDg2CNV4V4OD2yv1oviC8QRS5hi8IIk,5556
1750
- vellum_ai-0.14.78.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1751
- vellum_ai-0.14.78.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1752
- vellum_ai-0.14.78.dist-info/RECORD,,
1748
+ vellum_ai-0.14.80.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1749
+ vellum_ai-0.14.80.dist-info/METADATA,sha256=AOpjIQ_DNiOeABYs3Y-0JAxvRnzf2jeZLJ0aQZMBcD0,5556
1750
+ vellum_ai-0.14.80.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1751
+ vellum_ai-0.14.80.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1752
+ vellum_ai-0.14.80.dist-info/RECORD,,
vellum_cli/__init__.py CHANGED
@@ -67,7 +67,7 @@ with the provided module or be available for use. The Workflow Sandbox must also
67
67
  @click.option("--deployment-label", type=str, help="Label to use for the Deployment")
68
68
  @click.option("--deployment-name", type=str, help="Unique name for the Deployment")
69
69
  @click.option("--deployment-description", type=str, help="Description for the Deployment")
70
- @click.option("--release-tag", type=list, help="Release Tag for the Deployment", multiple=True)
70
+ @click.option("--release-tag", help="Release Tag for the Deployment", multiple=True)
71
71
  @click.option(
72
72
  "--dry-run",
73
73
  is_flag=True,
@@ -117,7 +117,7 @@ def workflows_push(
117
117
  @click.option("--deployment-label", type=str, help="Label to use for the Deployment")
118
118
  @click.option("--deployment-name", type=str, help="Unique name for the Deployment")
119
119
  @click.option("--deployment-description", type=str, help="Description for the Deployment")
120
- @click.option("--release-tag", type=list, help="Release Tag for the Deployment", multiple=True)
120
+ @click.option("--release-tag", help="Release Tag for the Deployment", multiple=True)
121
121
  @click.option(
122
122
  "--dry-run",
123
123
  is_flag=True,
vellum_cli/push.py CHANGED
@@ -8,6 +8,7 @@ from uuid import UUID
8
8
  from typing import List, Optional
9
9
 
10
10
  from dotenv import load_dotenv
11
+ from pydantic import ValidationError
11
12
 
12
13
  from vellum.client.core.api_error import ApiError
13
14
  from vellum.resources.workflows.client import OMIT
@@ -129,12 +130,26 @@ def push_command(
129
130
  workflow_config.deployments[0] if workflow_config.deployments else WorkflowDeploymentConfig()
130
131
  )
131
132
 
132
- deployment_config = WorkflowPushDeploymentConfigRequest(
133
- label=deployment_label or cli_deployment_config.label,
134
- name=deployment_name or cli_deployment_config.name,
135
- description=deployment_description or cli_deployment_config.description,
136
- release_tags=release_tags or cli_deployment_config.release_tags,
137
- )
133
+ try:
134
+ deployment_config = WorkflowPushDeploymentConfigRequest(
135
+ label=deployment_label or cli_deployment_config.label,
136
+ name=deployment_name or cli_deployment_config.name,
137
+ description=deployment_description or cli_deployment_config.description,
138
+ release_tags=release_tags or cli_deployment_config.release_tags,
139
+ )
140
+ except ValidationError as e:
141
+ for error in e.errors():
142
+ if "release_tags" in str(error.get("loc", [])):
143
+ handle_cli_error(
144
+ logger,
145
+ title="Invalid release tag format",
146
+ message="Release tags must be provided as separate arguments. "
147
+ "Use: --release-tag tag1 --release-tag tag2",
148
+ )
149
+ return
150
+
151
+ # Re-raise if it's not a release_tags validation error
152
+ raise e
138
153
 
139
154
  # We should check with fern if we could auto-serialize typed fields for us
140
155
  # https://app.shortcut.com/vellum/story/5568
@@ -979,3 +979,63 @@ def test_push__use_default_workspace_if_not_specified__multiple_workflows_config
979
979
  config = configs[0]
980
980
  assert config["workflow_sandbox_id"] == workflow_sandbox_id
981
981
  assert config["workspace"] == "default"
982
+
983
+
984
+ def test_push__deploy_with_malformed_release_tags_shows_friendly_validation_error(mock_module, vellum_client):
985
+ # GIVEN a single workflow configured
986
+ temp_dir = mock_module.temp_dir
987
+ module = mock_module.module
988
+
989
+ # AND a workflow exists in the module successfully
990
+ _ensure_workflow_py(temp_dir, module)
991
+
992
+ # AND the push API call would return successfully
993
+ vellum_client.workflows.push.return_value = WorkflowPushResponse(
994
+ workflow_sandbox_id=str(uuid4()),
995
+ )
996
+
997
+ # WHEN calling `vellum workflows push` with --deploy and --release-tag
998
+ runner = CliRunner()
999
+ result = runner.invoke(cli_main, ["workflows", "push", module, "--deploy", "--release-tag", None]) # type: ignore
1000
+
1001
+ # THEN it should show the friendly error message instead of a raw Pydantic traceback
1002
+ assert "Invalid release tag format" in result.output
1003
+ assert "Release tags must be provided as separate arguments" in result.output
1004
+ assert "--release-tag tag1 --release-tag tag2" in result.output
1005
+
1006
+
1007
+ @pytest.mark.usefixtures("info_log_level")
1008
+ def test_push__deploy_with_release_tags_success(mock_module, vellum_client):
1009
+ # GIVEN a single workflow configured
1010
+ temp_dir = mock_module.temp_dir
1011
+ module = mock_module.module
1012
+
1013
+ # AND a workflow exists in the module successfully
1014
+ _ensure_workflow_py(temp_dir, module)
1015
+
1016
+ # AND the push API call returns successfully
1017
+ workflow_deployment_id = str(uuid4())
1018
+ vellum_client.workflows.push.return_value = WorkflowPushResponse(
1019
+ workflow_sandbox_id=str(uuid4()),
1020
+ workflow_deployment_id=workflow_deployment_id,
1021
+ )
1022
+
1023
+ # WHEN calling `vellum workflows push` with --deploy and --release-tag
1024
+ runner = CliRunner()
1025
+ result = runner.invoke(cli_main, ["workflows", "push", module, "--deploy", "--release-tag", "v1.0.0"])
1026
+
1027
+ # THEN it should succeed
1028
+ assert result.exit_code == 0, result.output
1029
+
1030
+ # AND we should have called the push API with the correct deployment config
1031
+ vellum_client.workflows.push.assert_called_once()
1032
+ call_args = vellum_client.workflows.push.call_args.kwargs
1033
+
1034
+ # AND the deployment_config should contain the release tags
1035
+ deployment_config_str = call_args["deployment_config"]
1036
+ deployment_config = json.loads(deployment_config_str)
1037
+ assert deployment_config["release_tags"] == ["v1.0.0"]
1038
+
1039
+ # AND should show success message
1040
+ assert "Successfully pushed" in result.output
1041
+ assert "Updated vellum.lock.json file." in result.output
@@ -6,7 +6,7 @@ from typing import Any, Callable, Dict, Generic, Optional, Type, TypeVar, cast
6
6
  from vellum.workflows.nodes.bases.base import BaseNode
7
7
  from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
8
8
  from vellum.workflows.nodes.utils import get_wrapped_node
9
- from vellum.workflows.types.core import JsonArray, JsonObject
9
+ from vellum.workflows.types.core import JsonObject
10
10
  from vellum.workflows.types.utils import get_original_base
11
11
  from vellum.workflows.utils.uuids import uuid4_from_hash
12
12
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
@@ -94,8 +94,8 @@ class BaseAdornmentNodeDisplay(BaseNodeDisplay[_BaseAdornmentNodeType], Generic[
94
94
  additional_kwargs = get_additional_kwargs(wrapped_node_display.node_id) if get_additional_kwargs else {}
95
95
  serialized_wrapped_node = wrapped_node_display.serialize(display_context, **kwargs, **additional_kwargs)
96
96
 
97
- adornments = cast(JsonArray, serialized_wrapped_node.get("adornments")) or []
98
- serialized_wrapped_node["adornments"] = adornments + [adornment] if adornment else adornments
97
+ adornments = cast(list, serialized_wrapped_node.get("adornments")) or []
98
+ serialized_wrapped_node["adornments"] = [adornment] + adornments if adornment else adornments
99
99
 
100
100
  return serialized_wrapped_node
101
101
 
@@ -139,8 +139,8 @@ def test_serialize_node__function_configs():
139
139
  "foo": {
140
140
  "runtime": "PYTHON_3_11_6",
141
141
  "packages": [
142
- CodeExecutionPackage(name="first_package", version="1.0.0"),
143
- CodeExecutionPackage(name="second_package", version="2.0.0"),
142
+ CodeExecutionPackage(name="first_package", version="1.0.0", repository="test-repo"),
143
+ CodeExecutionPackage(name="second_package", version="2.0.0", repository="test-repo"),
144
144
  ],
145
145
  },
146
146
  "bar": {
@@ -179,11 +179,14 @@ def test_serialize_node__function_configs():
179
179
  "foo": {
180
180
  "runtime": "PYTHON_3_11_6",
181
181
  "packages": [
182
- {"version": "1.0.0", "name": "first_package"},
183
- {"version": "2.0.0", "name": "second_package"},
182
+ {"version": "1.0.0", "name": "first_package", "repository": "test-repo"},
183
+ {"version": "2.0.0", "name": "second_package", "repository": "test-repo"},
184
184
  ],
185
185
  },
186
- "bar": {"runtime": "PYTHON_3_11_6", "packages": [{"version": "3.0.0", "name": "third_package"}]},
186
+ "bar": {
187
+ "runtime": "PYTHON_3_11_6",
188
+ "packages": [{"version": "3.0.0", "name": "third_package", "repository": None}],
189
+ },
187
190
  },
188
191
  },
189
192
  },
@@ -1,4 +1,5 @@
1
1
  from uuid import uuid4
2
+ from typing import Any, Dict, List, cast
2
3
 
3
4
  from deepdiff import DeepDiff
4
5
 
@@ -269,6 +270,21 @@ def test_serialize_node__stacked():
269
270
  },
270
271
  "ports": [{"id": "408cd5fb-3a3e-4eb2-9889-61111bd6a129", "name": "default", "type": "DEFAULT"}],
271
272
  "adornments": [
273
+ {
274
+ "id": "3344083c-a32c-4a32-920b-0fb5093448fa",
275
+ "label": "TryNode",
276
+ "base": {
277
+ "name": "TryNode",
278
+ "module": ["vellum", "workflows", "nodes", "core", "try_node", "node"],
279
+ },
280
+ "attributes": [
281
+ {
282
+ "id": "ab2fbab0-e2a0-419b-b1ef-ce11ecf11e90",
283
+ "name": "on_error_code",
284
+ "value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
285
+ }
286
+ ],
287
+ },
272
288
  {
273
289
  "id": "5be7d260-74f7-4734-b31b-a46a94539586",
274
290
  "label": "RetryNode",
@@ -299,24 +315,41 @@ def test_serialize_node__stacked():
299
315
  },
300
316
  ],
301
317
  },
302
- {
303
- "id": "3344083c-a32c-4a32-920b-0fb5093448fa",
304
- "label": "TryNode",
305
- "base": {
306
- "name": "TryNode",
307
- "module": ["vellum", "workflows", "nodes", "core", "try_node", "node"],
308
- },
309
- "attributes": [
310
- {
311
- "id": "ab2fbab0-e2a0-419b-b1ef-ce11ecf11e90",
312
- "name": "on_error_code",
313
- "value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
314
- }
315
- ],
316
- },
317
318
  ],
318
319
  "attributes": [],
319
320
  "outputs": [],
320
321
  },
321
322
  inner_stacked_generic_node,
322
323
  )
324
+
325
+
326
+ def test_serialize_node__adornment_order_matches_decorator_order():
327
+ """
328
+ Tests that adornments are serialized in the same order as decorators are applied.
329
+ """
330
+
331
+ @TryNode.wrap()
332
+ @RetryNode.wrap(max_attempts=3)
333
+ class MyNode(BaseNode):
334
+ pass
335
+
336
+ # AND a workflow that uses the decorated node
337
+ class MyWorkflow(BaseWorkflow):
338
+ graph = MyNode
339
+
340
+ # WHEN we serialize the workflow
341
+ workflow_display = get_workflow_display(workflow_class=MyWorkflow)
342
+ exec_config = cast(Dict[str, Any], workflow_display.serialize())
343
+
344
+ # THEN the workflow should serialize successfully
345
+ assert isinstance(exec_config["workflow_raw_data"], dict)
346
+ assert isinstance(exec_config["workflow_raw_data"]["nodes"], list)
347
+
348
+ # AND we should find our decorated node
349
+ nodes = cast(List[Dict[str, Any]], exec_config["workflow_raw_data"]["nodes"])
350
+ my_node = [node for node in nodes if isinstance(node, dict) and node["type"] == "GENERIC"][0]
351
+
352
+ adornments = cast(List[Dict[str, Any]], my_node["adornments"])
353
+ assert len(adornments) == 2
354
+ assert adornments[0]["label"] == "TryNode"
355
+ assert adornments[1]["label"] == "RetryNode"
@@ -162,7 +162,7 @@ def test_serialize_workflow():
162
162
  "value": {
163
163
  "get_current_weather": {
164
164
  "runtime": "PYTHON_3_11_6",
165
- "packages": [{"version": "2.26.0", "name": "requests"}],
165
+ "packages": [{"version": "2.26.0", "name": "requests", "repository": None}],
166
166
  }
167
167
  },
168
168
  },