vellum-ai 0.14.33__py3-none-any.whl → 0.14.35__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.
vellum/__init__.py CHANGED
@@ -507,6 +507,7 @@ from .types import (
507
507
  WorkflowDeploymentParentContext,
508
508
  WorkflowDeploymentRead,
509
509
  WorkflowDeploymentRelease,
510
+ WorkflowDeploymentReleaseWorkflowDeployment,
510
511
  WorkflowDeploymentReleaseWorkflowVersion,
511
512
  WorkflowError,
512
513
  WorkflowEventDisplayContext,
@@ -1130,6 +1131,7 @@ __all__ = [
1130
1131
  "WorkflowDeploymentParentContext",
1131
1132
  "WorkflowDeploymentRead",
1132
1133
  "WorkflowDeploymentRelease",
1134
+ "WorkflowDeploymentReleaseWorkflowDeployment",
1133
1135
  "WorkflowDeploymentReleaseWorkflowVersion",
1134
1136
  "WorkflowDeploymentsListRequestStatus",
1135
1137
  "WorkflowError",
@@ -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.33",
21
+ "X-Fern-SDK-Version": "0.14.35",
22
22
  }
23
23
  headers["X_API_KEY"] = self.api_key
24
24
  return headers
@@ -531,6 +531,7 @@ from .workflow_deployment_history_item import WorkflowDeploymentHistoryItem
531
531
  from .workflow_deployment_parent_context import WorkflowDeploymentParentContext
532
532
  from .workflow_deployment_read import WorkflowDeploymentRead
533
533
  from .workflow_deployment_release import WorkflowDeploymentRelease
534
+ from .workflow_deployment_release_workflow_deployment import WorkflowDeploymentReleaseWorkflowDeployment
534
535
  from .workflow_deployment_release_workflow_version import WorkflowDeploymentReleaseWorkflowVersion
535
536
  from .workflow_error import WorkflowError
536
537
  from .workflow_event_display_context import WorkflowEventDisplayContext
@@ -1108,6 +1109,7 @@ __all__ = [
1108
1109
  "WorkflowDeploymentParentContext",
1109
1110
  "WorkflowDeploymentRead",
1110
1111
  "WorkflowDeploymentRelease",
1112
+ "WorkflowDeploymentReleaseWorkflowDeployment",
1111
1113
  "WorkflowDeploymentReleaseWorkflowVersion",
1112
1114
  "WorkflowError",
1113
1115
  "WorkflowEventDisplayContext",
@@ -1,15 +1,19 @@
1
1
  # This file was auto-generated by Fern from our API Definition.
2
2
 
3
+ from __future__ import annotations
3
4
  from ..core.pydantic_utilities import UniversalBaseModel
5
+ from .array_vellum_value import ArrayVellumValue
4
6
  import datetime as dt
5
7
  from .release_environment import ReleaseEnvironment
6
8
  import typing
7
9
  from .release_created_by import ReleaseCreatedBy
8
10
  from .workflow_deployment_release_workflow_version import WorkflowDeploymentReleaseWorkflowVersion
11
+ from .workflow_deployment_release_workflow_deployment import WorkflowDeploymentReleaseWorkflowDeployment
9
12
  from .release_release_tag import ReleaseReleaseTag
10
13
  from .slim_release_review import SlimReleaseReview
11
14
  from ..core.pydantic_utilities import IS_PYDANTIC_V2
12
15
  import pydantic
16
+ from ..core.pydantic_utilities import update_forward_refs
13
17
 
14
18
 
15
19
  class WorkflowDeploymentRelease(UniversalBaseModel):
@@ -18,6 +22,7 @@ class WorkflowDeploymentRelease(UniversalBaseModel):
18
22
  environment: ReleaseEnvironment
19
23
  created_by: typing.Optional[ReleaseCreatedBy] = None
20
24
  workflow_version: WorkflowDeploymentReleaseWorkflowVersion
25
+ deployment: WorkflowDeploymentReleaseWorkflowDeployment
21
26
  description: typing.Optional[str] = None
22
27
  release_tags: typing.List[ReleaseReleaseTag]
23
28
  reviews: typing.List[SlimReleaseReview]
@@ -30,3 +35,6 @@ class WorkflowDeploymentRelease(UniversalBaseModel):
30
35
  frozen = True
31
36
  smart_union = True
32
37
  extra = pydantic.Extra.allow
38
+
39
+
40
+ update_forward_refs(ArrayVellumValue, WorkflowDeploymentRelease=WorkflowDeploymentRelease)
@@ -0,0 +1,19 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ from ..core.pydantic_utilities import UniversalBaseModel
4
+ from ..core.pydantic_utilities import IS_PYDANTIC_V2
5
+ import typing
6
+ import pydantic
7
+
8
+
9
+ class WorkflowDeploymentReleaseWorkflowDeployment(UniversalBaseModel):
10
+ name: str
11
+
12
+ if IS_PYDANTIC_V2:
13
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
14
+ else:
15
+
16
+ class Config:
17
+ frozen = True
18
+ smart_union = True
19
+ extra = pydantic.Extra.allow
@@ -1,13 +1,19 @@
1
1
  # This file was auto-generated by Fern from our API Definition.
2
2
 
3
+ from __future__ import annotations
3
4
  from ..core.pydantic_utilities import UniversalBaseModel
4
- from ..core.pydantic_utilities import IS_PYDANTIC_V2
5
+ from .array_vellum_value import ArrayVellumValue
5
6
  import typing
7
+ from .vellum_variable import VellumVariable
8
+ from ..core.pydantic_utilities import IS_PYDANTIC_V2
6
9
  import pydantic
10
+ from ..core.pydantic_utilities import update_forward_refs
7
11
 
8
12
 
9
13
  class WorkflowDeploymentReleaseWorkflowVersion(UniversalBaseModel):
10
14
  id: str
15
+ input_variables: typing.List[VellumVariable]
16
+ output_variables: typing.List[VellumVariable]
11
17
 
12
18
  if IS_PYDANTIC_V2:
13
19
  model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
@@ -17,3 +23,6 @@ class WorkflowDeploymentReleaseWorkflowVersion(UniversalBaseModel):
17
23
  frozen = True
18
24
  smart_union = True
19
25
  extra = pydantic.Extra.allow
26
+
27
+
28
+ update_forward_refs(ArrayVellumValue, WorkflowDeploymentReleaseWorkflowVersion=WorkflowDeploymentReleaseWorkflowVersion)
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.workflow_deployment_release_workflow_deployment import *
@@ -75,9 +75,7 @@ class InlineSubworkflowNode(
75
75
  with execution_context(parent_context=get_parent_context()):
76
76
  subworkflow = self.subworkflow(
77
77
  parent_state=self.state,
78
- context=WorkflowContext(
79
- vellum_client=self._context.vellum_client, generated_files=self._context.generated_files
80
- ),
78
+ context=WorkflowContext.create_from(self._context),
81
79
  )
82
80
  subworkflow_stream = subworkflow.stream(
83
81
  inputs=self._compile_subworkflow_inputs(),
@@ -171,9 +171,7 @@ class MapNode(BaseAdornmentNode[StateType], Generic[StateType, MapNodeItemType])
171
171
  self._run_subworkflow(item=item, index=index)
172
172
 
173
173
  def _run_subworkflow(self, *, item: MapNodeItemType, index: int) -> None:
174
- context = WorkflowContext(
175
- vellum_client=self._context.vellum_client, generated_files=self._context.generated_files
176
- )
174
+ context = WorkflowContext.create_from(self._context)
177
175
  subworkflow = self.subworkflow(
178
176
  parent_state=self.state,
179
177
  context=context,
@@ -45,9 +45,7 @@ class RetryNode(BaseAdornmentNode[StateType], Generic[StateType]):
45
45
  with execution_context(parent_context=parent_context):
46
46
  subworkflow = self.subworkflow(
47
47
  parent_state=self.state,
48
- context=WorkflowContext(
49
- vellum_client=self._context.vellum_client, generated_files=self._context.generated_files
50
- ),
48
+ context=WorkflowContext.create_from(self._context),
51
49
  )
52
50
  subworkflow_stream = subworkflow.stream(
53
51
  inputs=self.SubworkflowInputs(attempt_number=attempt_number),
@@ -32,9 +32,7 @@ class TryNode(BaseAdornmentNode[StateType], Generic[StateType]):
32
32
  with execution_context(parent_context=parent_context):
33
33
  subworkflow = self.subworkflow(
34
34
  parent_state=self.state,
35
- context=WorkflowContext(
36
- vellum_client=self._context.vellum_client, generated_files=self._context.generated_files
37
- ),
35
+ context=WorkflowContext.create_from(self._context),
38
36
  )
39
37
  subworkflow_stream = subworkflow.stream(
40
38
  event_filter=all_workflow_event_filter,
@@ -1,5 +1,6 @@
1
1
  import pytest
2
2
  import os
3
+ import re
3
4
  from typing import Any, List, Union
4
5
 
5
6
  from pydantic import BaseModel
@@ -653,7 +654,9 @@ def main():
653
654
  node.run()
654
655
 
655
656
  # AND the error should contain the execution error details
656
- assert exc_info.value.message == "Expected an output of type 'int | float', but received 'str'"
657
+ assert re.match(
658
+ r"Expected an output of type '(int \| float|float \| int)', but received 'str'", exc_info.value.message
659
+ )
657
660
 
658
661
 
659
662
  def test_run_node__chat_history_output_type():
@@ -74,3 +74,7 @@ class WorkflowContext:
74
74
 
75
75
  def _get_all_node_output_mocks(self) -> List[MockNodeExecution]:
76
76
  return [mock for mocks in self._node_output_mocks_map.values() for mock in mocks]
77
+
78
+ @classmethod
79
+ def create_from(cls, context):
80
+ return cls(vellum_client=context.vellum_client, generated_files=context.generated_files)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 0.14.33
3
+ Version: 0.14.35
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -7,7 +7,7 @@ vellum_cli/image_push.py,sha256=8DDvRDJEZ-FukUCqGW1827bg1ybF4xBbx9WyqWYQE-g,6816
7
7
  vellum_cli/init.py,sha256=WpnMXPItPmh0f0bBGIer3p-e5gu8DUGwSArT_FuoMEw,5093
8
8
  vellum_cli/logger.py,sha256=PuRFa0WCh4sAGFS5aqWB0QIYpS6nBWwPJrIXpWxugV4,1022
9
9
  vellum_cli/ping.py,sha256=p_BCCRjgPhng6JktuECtkDQLbhopt6JpmrtGoLnLJT8,1161
10
- vellum_cli/pull.py,sha256=OfP2FIChfYzi7ksWObnvmkibln0JubMQftr1TZL-Bxk,10061
10
+ vellum_cli/pull.py,sha256=YTo5cVCcp7RjS9lHednOuud4rW4bH7jqV3GMdbic_Uk,12002
11
11
  vellum_cli/push.py,sha256=xjTNbLwOVFNU3kpBrm56Bk5QkSRrJ9z86qceghCzfIA,9655
12
12
  vellum_cli/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  vellum_cli/tests/conftest.py,sha256=AFYZryKA2qnUuCPBxBKmHLFoPiE0WhBFFej9tNwSHdc,1526
@@ -16,7 +16,7 @@ vellum_cli/tests/test_image_push.py,sha256=i3lJuW8nFRwL1M1OF6752IZYvGAFgKmkB2hd_
16
16
  vellum_cli/tests/test_init.py,sha256=8UOc_ThfouR4ja5cCl_URuLk7ohr9JXfCnG4yka1OUQ,18754
17
17
  vellum_cli/tests/test_main.py,sha256=qDZG-aQauPwBwM6A2DIu1494n47v3pL28XakTbLGZ-k,272
18
18
  vellum_cli/tests/test_ping.py,sha256=3ucVRThEmTadlV9LrJdCCrr1Ofj3rOjG6ue0BNR2UC0,2523
19
- vellum_cli/tests/test_pull.py,sha256=UmgQwtv8J7U2zkbTvOPbxi6ozvAec1Fo1b2E5Fc9gWU,34625
19
+ vellum_cli/tests/test_pull.py,sha256=3eZJASQ4UbPXmqnbg-5w1Q3gyasVMFiA2Pr5RFPID1o,46495
20
20
  vellum_cli/tests/test_push.py,sha256=zDv_Q1hbXtLwmTJDPRAvwDjbuHC09uNRYOy4FQujUow,23476
21
21
  vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -121,14 +121,14 @@ vellum_ee/workflows/tests/local_workflow/nodes/final_output.py,sha256=ZX7zBv87zi
121
121
  vellum_ee/workflows/tests/local_workflow/nodes/templating_node.py,sha256=NQwFN61QkHfI3Vssz-B0NKGfupK8PU0FDSAIAhYBLi0,325
122
122
  vellum_ee/workflows/tests/local_workflow/workflow.py,sha256=A4qOzOPNwePYxWbcAgIPLsmrVS_aVEZEc-wULSv787Q,393
123
123
  vellum_ee/workflows/tests/test_display_meta.py,sha256=C25dErwghPNXio49pvSRxyOuc96srH6eYEwTAWdE2zY,2258
124
- vellum_ee/workflows/tests/test_server.py,sha256=Q9XD93jMwYjZOkcwMPNM2F4BWvcY71wxkYp8HgMgGFU,4435
124
+ vellum_ee/workflows/tests/test_server.py,sha256=SsOkS6sGO7uGC4mxvk4iv8AtcXs058P9hgFHzTWmpII,14519
125
125
  vellum_ee/workflows/tests/test_virtual_files.py,sha256=TJEcMR0v2S8CkloXNmCHA0QW0K6pYNGaIjraJz7sFvY,2762
126
- vellum/__init__.py,sha256=Ur02Hk_LtwXW45f-LBBtBhRX1xJRSq05ytaQUfgIGmc,40970
126
+ vellum/__init__.py,sha256=YKN2trB0s1J3jSQSKsT6vQjdVM7RRdSTvKGJLvaQ1PU,41070
127
127
  vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
128
128
  vellum/client/__init__.py,sha256=ki-TDOmYzC0FePN7swDyE6UpHFQV_4dK7lqy4h-3s1Y,118148
129
129
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
130
130
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
131
- vellum/client/core/client_wrapper.py,sha256=jPo42cS6hlGPneWIZtcMfaVmCbt4TEQwCPgoWxxCTUw,1869
131
+ vellum/client/core/client_wrapper.py,sha256=ETlev1Qgt8SDpGgtdiJftsiPBOUFLk93P_WgpT1msN0,1869
132
132
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
133
133
  vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
134
134
  vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
@@ -195,7 +195,7 @@ vellum/client/resources/workspace_secrets/__init__.py,sha256=FTtvy8EDg9nNNg9WCat
195
195
  vellum/client/resources/workspace_secrets/client.py,sha256=h7UzXLyTttPq1t-JZGMg1BWxypxJvBGUdqg7KGT7MK4,8027
196
196
  vellum/client/resources/workspaces/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
197
197
  vellum/client/resources/workspaces/client.py,sha256=RthwzN1o-Jxwg5yyNNodavFyNUSxfLoTv26w3mRR5g8,3595
198
- vellum/client/types/__init__.py,sha256=ZGqDXdE0b5IdJIYVcPEUZ33lgiV5Jy47KM1c5OzyDok,61976
198
+ vellum/client/types/__init__.py,sha256=rvwKofaHwLpRT0shxW3yUXkEorD3QurXnqSicG_2Or4,62132
199
199
  vellum/client/types/ad_hoc_execute_prompt_event.py,sha256=bCjujA2XsOgyF3bRZbcEqV2rOIymRgsLoIRtZpB14xg,607
200
200
  vellum/client/types/ad_hoc_expand_meta.py,sha256=1gv-NCsy_6xBYupLvZH979yf2VMdxAU-l0y0ynMKZaw,1331
201
201
  vellum/client/types/ad_hoc_fulfilled_prompt_execution_meta.py,sha256=Bfvf1d_dkmshxRACVM5vcxbH_7AQY23RmrrnPc0ytYY,939
@@ -698,8 +698,9 @@ vellum/client/types/workflow_deployment_event_executions_response.py,sha256=x7mZ
698
698
  vellum/client/types/workflow_deployment_history_item.py,sha256=4WUPzcthBvEZ7iaisKfEg0soUtHjcTEnL_VUVaKpTyw,1420
699
699
  vellum/client/types/workflow_deployment_parent_context.py,sha256=QNyPj2o-jauaC48KrRjCWan1IKIbDgyuxLxURmhXsXM,2347
700
700
  vellum/client/types/workflow_deployment_read.py,sha256=tp1WaojTVE_dz1oiZ97h8ixMbIWDgy2yRu08A7wPMpw,2363
701
- vellum/client/types/workflow_deployment_release.py,sha256=pLkRtkCIEUUZuYxdfXELddVKkljqt42OG7fUA8HXt4M,1209
702
- vellum/client/types/workflow_deployment_release_workflow_version.py,sha256=kTZbVa6KAtAD_nCfea3QlQrZTH2AYP9tbaOKimHp9jg,581
701
+ vellum/client/types/workflow_deployment_release.py,sha256=1OveujSLRID7B6aGGjqsN5R8MbEHn3SS0sp3dj22DQs,1609
702
+ vellum/client/types/workflow_deployment_release_workflow_deployment.py,sha256=irWt901SImKChLayz7_52C1W7JldKiQHweqkjuMUoNQ,586
703
+ vellum/client/types/workflow_deployment_release_workflow_version.py,sha256=NP3FoxLpgMUIK1OAPf_ei58mxE9F7BLAuw7q55CIcT8,989
703
704
  vellum/client/types/workflow_error.py,sha256=EQajkEmLS64T0wYm0goHQl0rT7Lguurk8pLwkhjsgAI,282
704
705
  vellum/client/types/workflow_event_display_context.py,sha256=tnO9lgIJKnLtuS6xum_QilI83LjOmnWCLtnSHLn1oNo,929
705
706
  vellum/client/types/workflow_event_error.py,sha256=HIewu_kh3KNPpWegAQArvAGHCp-cBIXqlUAAc_dBZhc,687
@@ -1364,6 +1365,7 @@ vellum/types/workflow_deployment_history_item.py,sha256=dp5pwzOVO83KPwAbYeO3NXlK
1364
1365
  vellum/types/workflow_deployment_parent_context.py,sha256=kB0eeRXagHqRnuDVA9B8aDlvBZVOmQ702JYXD8evh24,172
1365
1366
  vellum/types/workflow_deployment_read.py,sha256=dDGG27VP0bvC565JzeSOHJ-5Pvs7eCF4R8F9k8316bo,162
1366
1367
  vellum/types/workflow_deployment_release.py,sha256=lBnOc5Tw2-jLGWmthzkwdaLGvylcDiarO-maZSote0A,165
1368
+ vellum/types/workflow_deployment_release_workflow_deployment.py,sha256=8qT32r--NyJppqBizD9QP6jvM5YdcsdpGEtaKMG1RbE,185
1367
1369
  vellum/types/workflow_deployment_release_workflow_version.py,sha256=l5SJrY9z3lG5K82V2wY2sY50V40CQWKl95nDjnHu4Dc,182
1368
1370
  vellum/types/workflow_error.py,sha256=7rZcYJG5jYr4IbEvgv57G6Lxutrdg43SD8mUerd58_A,152
1369
1371
  vellum/types/workflow_event_display_context.py,sha256=jiH4vHlWYdT_2zM8yxPox3fXjnFStzIO46N90B2TdNA,168
@@ -1523,22 +1525,22 @@ vellum/workflows/nodes/core/__init__.py,sha256=5zDMCmyt1v0HTJzlUBwq3U9L825yZGZhT
1523
1525
  vellum/workflows/nodes/core/error_node/__init__.py,sha256=g7RRnlHhqu4qByfLjBwCunmgGA8dI5gNsjS3h6TwlSI,60
1524
1526
  vellum/workflows/nodes/core/error_node/node.py,sha256=MFHU5vITYSK-L9CuMZ49In2ZeNLWnhZD0f8r5dWvb5Y,1270
1525
1527
  vellum/workflows/nodes/core/inline_subworkflow_node/__init__.py,sha256=nKNEH1QTl-1PcvmYoqSWEl0-t6gAur8GLTXHzklRQfM,84
1526
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py,sha256=gumdZs50XGpnFvKxde-WAu6NqH7ImwRf3gK6c7B46W0,6696
1528
+ vellum/workflows/nodes/core/inline_subworkflow_node/node.py,sha256=rgcjc3gaCEX9uSfkLSErHjSnNQEeqREVZk-7TEr9hUo,6595
1527
1529
  vellum/workflows/nodes/core/inline_subworkflow_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1528
1530
  vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py,sha256=kUqwcRMMxjTHALbwGUXCJT_aJBrHS1bkg49oL8R0JO8,4337
1529
1531
  vellum/workflows/nodes/core/map_node/__init__.py,sha256=MXpZYmGfhsMJHqqlpd64WiJRtbAtAMQz-_3fCU_cLV0,56
1530
- vellum/workflows/nodes/core/map_node/node.py,sha256=8AOHvV1TR8Tr4IyDjdIk6Jzi86KJpg_wS9gp30tDRLQ,9312
1532
+ vellum/workflows/nodes/core/map_node/node.py,sha256=RER4mLOXSe1BSpjEYCoJq83ZeydQ5Q5uKCZAsqhlOW8,9227
1531
1533
  vellum/workflows/nodes/core/map_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1532
1534
  vellum/workflows/nodes/core/map_node/tests/test_node.py,sha256=uMR0AyIFn539LqTKHdwuBswnx1i-PHyqPpgtYrnmYMY,3496
1533
1535
  vellum/workflows/nodes/core/retry_node/__init__.py,sha256=lN2bIy5a3Uzhs_FYCrooADyYU6ZGShtvLKFWpelwPvo,60
1534
- vellum/workflows/nodes/core/retry_node/node.py,sha256=UmTxbxom2VPBoJLk75gXdMa300JJ-1rkW0rh2MeF3jE,5354
1536
+ vellum/workflows/nodes/core/retry_node/node.py,sha256=at7RjwUmlBeUv-tHvqeOhCAxkyuSw47ySmIQKC0fJf8,5245
1535
1537
  vellum/workflows/nodes/core/retry_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1536
1538
  vellum/workflows/nodes/core/retry_node/tests/test_node.py,sha256=RM_OHwxrHwyxvlQQBJPqVBxpedFuWQ9h2-Xa3kP75sc,4399
1537
1539
  vellum/workflows/nodes/core/templating_node/__init__.py,sha256=GmyuYo81_A1_Bz6id69ozVFS6FKiuDsZTiA3I6MaL2U,70
1538
1540
  vellum/workflows/nodes/core/templating_node/node.py,sha256=iqBmr2i-f-BqhisNQJiDfewjol0ur7-XpupLStyMJsg,3731
1539
1541
  vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py,sha256=nXkgGDBg4wP36NwykdMEVWwx_xjv8oGT2rYkwuCB_VU,10075
1540
1542
  vellum/workflows/nodes/core/try_node/__init__.py,sha256=JVD4DrldTIqFQQFrubs9KtWCCc0YCAc7Fzol5ZWIWeM,56
1541
- vellum/workflows/nodes/core/try_node/node.py,sha256=-fHfcgI9RFQobNyiugP2dow4ahqY7iDd1LObJw5aB8k,4530
1543
+ vellum/workflows/nodes/core/try_node/node.py,sha256=2r2I70j30IyZPvn3Q3zP2VgLbx3WQ1DdOr8NUlXjcq0,4429
1542
1544
  vellum/workflows/nodes/core/try_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1543
1545
  vellum/workflows/nodes/core/try_node/tests/test_node.py,sha256=h6eUc3SggvhzBWlOD0PrPUlkoCSQHwjqYn81VkxSIxU,4948
1544
1546
  vellum/workflows/nodes/displayable/__init__.py,sha256=6F_4DlSwvHuilWnIalp8iDjjDXl0Nmz4QzJV2PYe5RI,1023
@@ -1567,7 +1569,7 @@ vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=Ko_Dy17Ajf
1567
1569
  vellum/workflows/nodes/displayable/code_execution_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1568
1570
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1569
1571
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/main.py,sha256=5QsbmkzSlSbcbWTG_JmIqcP-JNJzOPTKxGzdHos19W4,79
1570
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=xAaoOfQHQUlp0iKlig87t0aT2cJM-8PxiTb1QDg8VmY,24641
1572
+ vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=AzgTK2YSvVj7zr6gWZfz0-YGf1cVQ9DiSx9fe5BR4uE,24690
1571
1573
  vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=G-sc7yOL5g6rLk99X8HAbXNcLxRaqpju9IXq1iUwnQI,4470
1572
1574
  vellum/workflows/nodes/displayable/conditional_node/__init__.py,sha256=AS_EIqFdU1F9t8aLmbZU-rLh9ry6LCJ0uj0D8F0L5Uw,72
1573
1575
  vellum/workflows/nodes/displayable/conditional_node/node.py,sha256=Qjfl33gZ3JEgxBA1EgzSUebboGvsARthIxxcQyvx5Gg,1152
@@ -1638,7 +1640,7 @@ vellum/workflows/runner/runner.py,sha256=ww4fjZJBENkB5HJxdj92kTz7k_EyifCeAreupy5
1638
1640
  vellum/workflows/sandbox.py,sha256=GVJzVjMuYzOBnSrboB0_6MMRZWBluAyQ2o7syeaeBd0,2235
1639
1641
  vellum/workflows/state/__init__.py,sha256=yUUdR-_Vl7UiixNDYQZ-GEM_kJI9dnOia75TtuNEsnE,60
1640
1642
  vellum/workflows/state/base.py,sha256=Vkhneko3VlQrPsMLU1PYSzXU_W1u7_AraJsghiv5O-4,15512
1641
- vellum/workflows/state/context.py,sha256=B-sGK9GpZPh7bgCPb8PJCrl_evod3Ru7rHs_RofisrU,2926
1643
+ vellum/workflows/state/context.py,sha256=KOAI1wEGn8dGmhmAemJaf4SZbitP3jpIBcwKfznQaRE,3076
1642
1644
  vellum/workflows/state/encoder.py,sha256=TnOQojc5lTQ83g9QbpA4UCqShJvutmTMxbpKt-9gNe4,1911
1643
1645
  vellum/workflows/state/store.py,sha256=uVe-oN73KwGV6M6YLhwZMMUQhzTQomsVfVnb8V91gVo,1147
1644
1646
  vellum/workflows/state/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1670,8 +1672,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
1670
1672
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1671
1673
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=tCxrV3QBHL8wfdEO3bvKteDdw32xBlUl1_WxkAwaONw,8344
1672
1674
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1673
- vellum_ai-0.14.33.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1674
- vellum_ai-0.14.33.dist-info/METADATA,sha256=8j_YxkXJ0HIBjUZszx0257w_iMhp8KxdW3Y3pUmabMs,5484
1675
- vellum_ai-0.14.33.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1676
- vellum_ai-0.14.33.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1677
- vellum_ai-0.14.33.dist-info/RECORD,,
1675
+ vellum_ai-0.14.35.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1676
+ vellum_ai-0.14.35.dist-info/METADATA,sha256=p35Qzrtnz0-GxWWkLD1zb0nMfyoFki46Og2RE-q9c8E,5484
1677
+ vellum_ai-0.14.35.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1678
+ vellum_ai-0.14.35.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1679
+ vellum_ai-0.14.35.dist-info/RECORD,,
vellum_cli/pull.py CHANGED
@@ -2,6 +2,7 @@ import io
2
2
  import json
3
3
  import os
4
4
  from pathlib import Path
5
+ from uuid import UUID
5
6
  import zipfile
6
7
  from typing import Optional
7
8
 
@@ -10,8 +11,9 @@ from pydash import snake_case
10
11
 
11
12
  from vellum.client.core.api_error import ApiError
12
13
  from vellum.client.core.pydantic_utilities import UniversalBaseModel
14
+ from vellum.utils.uuid import is_valid_uuid
13
15
  from vellum.workflows.vellum_client import create_vellum_client
14
- from vellum_cli.config import VellumCliConfig, WorkflowConfig, load_vellum_cli_config
16
+ from vellum_cli.config import VellumCliConfig, WorkflowConfig, WorkflowDeploymentConfig, load_vellum_cli_config
15
17
  from vellum_cli.logger import load_cli_logger
16
18
 
17
19
  ERROR_LOG_FILE_NAME = "error.log"
@@ -31,6 +33,7 @@ class RunnerConfig(UniversalBaseModel):
31
33
  class PullContentsMetadata(UniversalBaseModel):
32
34
  label: Optional[str] = None
33
35
  runner_config: Optional[RunnerConfig] = None
36
+ deployment_id: Optional[UUID] = None
34
37
  deployment_name: Optional[str] = None
35
38
 
36
39
 
@@ -79,6 +82,29 @@ def _resolve_workflow_config(
79
82
  pk=workflow_config.workflow_sandbox_id,
80
83
  )
81
84
  elif workflow_deployment:
85
+ if is_valid_uuid(workflow_deployment):
86
+ # name may also be a valid UUID
87
+ workflow_config = next(
88
+ (
89
+ w
90
+ for w in config.workflows
91
+ if w.deployments
92
+ and (
93
+ str(w.deployments[0].id) == workflow_deployment or w.deployments[0].name == workflow_deployment
94
+ )
95
+ ),
96
+ None,
97
+ )
98
+ else:
99
+ workflow_config = next(
100
+ (w for w in config.workflows if w.deployments and w.deployments[0].name == workflow_deployment), None
101
+ )
102
+ if workflow_config:
103
+ return WorkflowConfigResolutionResult(
104
+ workflow_config=workflow_config,
105
+ pk=workflow_deployment,
106
+ )
107
+
82
108
  workflow_config = WorkflowConfig(
83
109
  module="",
84
110
  )
@@ -177,6 +203,24 @@ def pull_command(
177
203
  if not workflow_config.module and pull_contents_metadata.label:
178
204
  workflow_config.module = snake_case(pull_contents_metadata.label)
179
205
 
206
+ # Save or update the deployment info when pulling with --workflow-deployment
207
+ if workflow_deployment:
208
+ workflow_deployment_id = pull_contents_metadata.deployment_id
209
+ existing_deployment = next(
210
+ (d for d in workflow_config.deployments if d.id == workflow_deployment_id), None
211
+ )
212
+
213
+ if existing_deployment:
214
+ if pull_contents_metadata.label:
215
+ existing_deployment.label = pull_contents_metadata.label
216
+ else:
217
+ deployment_config = WorkflowDeploymentConfig(
218
+ id=workflow_deployment_id,
219
+ label=pull_contents_metadata.label,
220
+ name=pull_contents_metadata.deployment_name,
221
+ )
222
+ workflow_config.deployments.append(deployment_config)
223
+
180
224
  if not workflow_config.module:
181
225
  raise ValueError(f"Failed to resolve a module name for Workflow {pk}")
182
226
 
@@ -337,6 +337,7 @@ def test_pull__sandbox_id_with_other_workflow_configured(vellum_client, mock_mod
337
337
  def test_pull__workflow_deployment_with_no_config(vellum_client):
338
338
  # GIVEN a workflow deployment
339
339
  workflow_deployment = "my-deployment"
340
+ deployment_id = str(uuid4())
340
341
 
341
342
  # AND the workflow pull API call returns a zip file
342
343
  vellum_client.workflows.pull.return_value = iter(
@@ -344,7 +345,9 @@ def test_pull__workflow_deployment_with_no_config(vellum_client):
344
345
  _zip_file_map(
345
346
  {
346
347
  "workflow.py": "print('hello')",
347
- "metadata.json": json.dumps({"deployment_name": workflow_deployment, "label": "Some Label"}),
348
+ "metadata.json": json.dumps(
349
+ {"deployment_id": deployment_id, "deployment_name": workflow_deployment, "label": "Some Label"}
350
+ ),
348
351
  }
349
352
  )
350
353
  ]
@@ -382,7 +385,15 @@ def test_pull__workflow_deployment_with_no_config(vellum_client):
382
385
  "module": "my_deployment",
383
386
  "workflow_sandbox_id": None,
384
387
  "ignore": None,
385
- "deployments": [],
388
+ "deployments": [
389
+ {
390
+ "id": deployment_id,
391
+ "label": "Some Label",
392
+ "name": "my-deployment",
393
+ "description": None,
394
+ "release_tags": None,
395
+ }
396
+ ],
386
397
  "container_image_tag": None,
387
398
  "container_image_name": None,
388
399
  "workspace": "default",
@@ -937,3 +948,314 @@ def test_pull__unauthorized_error_path(vellum_client):
937
948
  # THEN the command returns an error
938
949
  assert result.exit_code == 1
939
950
  assert str(result.exception) == "Please make sure your `VELLUM_API_KEY` environment variable is set correctly."
951
+
952
+
953
+ @pytest.mark.parametrize(
954
+ "workflow_deployment",
955
+ [
956
+ "test-workflow-deployment-id",
957
+ str(uuid4()),
958
+ ],
959
+ )
960
+ def test_pull__workflow_deployment_adds_deployment_to_config(vellum_client, workflow_deployment):
961
+ # GIVEN a workflow deployment ID
962
+ deployment_id = str(uuid4()) # config will always use the deployment_id return from the API
963
+ deployment_name = "Test Deployment"
964
+ deployment_label = "Test Label"
965
+
966
+ # AND the workflow pull API call returns a zip file with metadata
967
+ vellum_client.workflows.pull.return_value = iter(
968
+ [
969
+ _zip_file_map(
970
+ {
971
+ "workflow.py": "print('hello')",
972
+ "metadata.json": json.dumps(
973
+ {
974
+ "deployment_id": deployment_id,
975
+ "deployment_name": deployment_name,
976
+ "label": deployment_label,
977
+ }
978
+ ),
979
+ }
980
+ )
981
+ ]
982
+ )
983
+
984
+ # AND we are currently in a new directory
985
+ current_dir = os.getcwd()
986
+ temp_dir = tempfile.mkdtemp()
987
+ os.chdir(temp_dir)
988
+
989
+ # WHEN the user runs the pull command with the workflow deployment
990
+ runner = CliRunner()
991
+ result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", workflow_deployment])
992
+
993
+ # THEN the command returns successfully
994
+ assert result.exit_code == 0
995
+
996
+ # AND the deployment is saved in the config
997
+ vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
998
+ assert os.path.exists(vellum_lock_json)
999
+ with open(vellum_lock_json) as f:
1000
+ lock_data = json.loads(f.read())
1001
+ assert len(lock_data["workflows"]) == 1
1002
+ assert len(lock_data["workflows"][0]["deployments"]) == 1
1003
+ deployment = lock_data["workflows"][0]["deployments"][0]
1004
+ assert str(deployment["id"]) == deployment_id
1005
+ assert deployment["name"] == deployment_name
1006
+ assert deployment["label"] == deployment_label
1007
+
1008
+ os.chdir(current_dir)
1009
+
1010
+
1011
+ def test_pull__workflow_deployment_name_is_uuid(vellum_client):
1012
+ # GIVEN a workflow deployment name that is a valid UUID
1013
+ deployment_id = str(uuid4())
1014
+ deployment_name = str(uuid4())
1015
+
1016
+ # AND an existing configuration with this deployment
1017
+ current_dir = os.getcwd()
1018
+ temp_dir = tempfile.mkdtemp()
1019
+ os.chdir(temp_dir)
1020
+
1021
+ # Create initial config with a deployment
1022
+ vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
1023
+ with open(vellum_lock_json, "w") as f:
1024
+ json.dump(
1025
+ {
1026
+ "version": "1.0",
1027
+ "workflows": [
1028
+ {
1029
+ "module": "test_workflow",
1030
+ "workflow_sandbox_id": None,
1031
+ "ignore": None,
1032
+ "deployments": [
1033
+ {
1034
+ "id": deployment_id,
1035
+ "label": "Test Label",
1036
+ "name": deployment_name,
1037
+ "description": None,
1038
+ "release_tags": None,
1039
+ }
1040
+ ],
1041
+ "container_image_name": None,
1042
+ "container_image_tag": None,
1043
+ "workspace": "default",
1044
+ "target_directory": None,
1045
+ }
1046
+ ],
1047
+ "workspaces": [],
1048
+ },
1049
+ f,
1050
+ )
1051
+
1052
+ # AND the workflow pull API call returns a zip file with updated metadata
1053
+ updated_label = "Updated Label"
1054
+ vellum_client.workflows.pull.return_value = iter(
1055
+ [
1056
+ _zip_file_map(
1057
+ {
1058
+ "workflow.py": "print('hello')",
1059
+ "metadata.json": json.dumps(
1060
+ {
1061
+ "deployment_id": deployment_id,
1062
+ "deployment_name": deployment_name,
1063
+ "label": updated_label,
1064
+ }
1065
+ ),
1066
+ }
1067
+ )
1068
+ ]
1069
+ )
1070
+
1071
+ # WHEN the user runs the pull command with the workflow deployment
1072
+ runner = CliRunner()
1073
+ result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", deployment_name])
1074
+
1075
+ # THEN the command returns successfully
1076
+ assert result.exit_code == 0
1077
+
1078
+ # AND the deployment info is updated in the config
1079
+ with open(vellum_lock_json) as f:
1080
+ lock_data = json.loads(f.read())
1081
+ assert len(lock_data["workflows"]) == 1
1082
+ assert len(lock_data["workflows"][0]["deployments"]) == 1
1083
+ deployment = lock_data["workflows"][0]["deployments"][0]
1084
+ assert str(deployment["id"]) == deployment_id
1085
+ assert deployment["name"] == deployment_name
1086
+ assert deployment["label"] == updated_label
1087
+
1088
+ os.chdir(current_dir)
1089
+
1090
+
1091
+ @pytest.mark.parametrize("get_identifier", [(lambda d: d), (lambda d: "Test Name")])
1092
+ def test_pull__workflow_deployment_updates_existing_deployment(vellum_client, get_identifier):
1093
+ """
1094
+ This test is to ensure that the deployment info is updated in the config
1095
+ when the user runs the pull command with the workflow deployment
1096
+
1097
+ get_identifier is a function that returns the identifier to use for the deployment
1098
+ it can be the deployment_id or the deployment_name
1099
+ """
1100
+ # GIVEN a workflow deployment id and name
1101
+ deployment_id = str(uuid4())
1102
+ deployment_name = "Test Name"
1103
+
1104
+ # AND an existing configuration with this deployment
1105
+ current_dir = os.getcwd()
1106
+ temp_dir = tempfile.mkdtemp()
1107
+ os.chdir(temp_dir)
1108
+
1109
+ # Create initial config with a deployment
1110
+ vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
1111
+ with open(vellum_lock_json, "w") as f:
1112
+ json.dump(
1113
+ {
1114
+ "version": "1.0",
1115
+ "workflows": [
1116
+ {
1117
+ "module": "test_workflow",
1118
+ "workflow_sandbox_id": None,
1119
+ "ignore": None,
1120
+ "deployments": [
1121
+ {
1122
+ "id": deployment_id,
1123
+ "label": "Test Label",
1124
+ "name": deployment_name,
1125
+ "description": None,
1126
+ "release_tags": None,
1127
+ }
1128
+ ],
1129
+ "container_image_name": None,
1130
+ "container_image_tag": None,
1131
+ "workspace": "default",
1132
+ "target_directory": None,
1133
+ }
1134
+ ],
1135
+ "workspaces": [],
1136
+ },
1137
+ f,
1138
+ )
1139
+
1140
+ # AND the workflow pull API call returns a zip file with updated metadata
1141
+ updated_label = "Updated Label"
1142
+ vellum_client.workflows.pull.return_value = iter(
1143
+ [
1144
+ _zip_file_map(
1145
+ {
1146
+ "workflow.py": "print('hello')",
1147
+ "metadata.json": json.dumps(
1148
+ {
1149
+ "deployment_id": deployment_id,
1150
+ "deployment_name": deployment_name,
1151
+ "label": updated_label,
1152
+ }
1153
+ ),
1154
+ }
1155
+ )
1156
+ ]
1157
+ )
1158
+
1159
+ # WHEN the user runs the pull command with the workflow deployment
1160
+ runner = CliRunner()
1161
+ identifier_to_use = get_identifier(deployment_id)
1162
+ result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", identifier_to_use])
1163
+
1164
+ # THEN the command returns successfully
1165
+ assert result.exit_code == 0
1166
+
1167
+ # AND the deployment info is updated in the config
1168
+ with open(vellum_lock_json) as f:
1169
+ lock_data = json.loads(f.read())
1170
+ assert len(lock_data["workflows"]) == 1
1171
+ assert len(lock_data["workflows"][0]["deployments"]) == 1
1172
+ deployment = lock_data["workflows"][0]["deployments"][0]
1173
+ assert str(deployment["id"]) == deployment_id
1174
+ assert deployment["name"] == deployment_name
1175
+ assert deployment["label"] == updated_label
1176
+
1177
+ os.chdir(current_dir)
1178
+
1179
+
1180
+ def test_pull__workflow_deployment_with_name_and_id(vellum_client):
1181
+ """
1182
+ This test is to ensure that pulling with id and name will not add a new deployment to the config
1183
+ """
1184
+ # GIVEN a workflow deployment ID
1185
+ deployment_id = str(uuid4()) # config will always use the deployment_id return from the API
1186
+ deployment_name = "Test Deployment"
1187
+ deployment_label = "Test Label"
1188
+
1189
+ # AND the workflow pull API call returns a zip file with metadata
1190
+ vellum_client.workflows.pull.return_value = iter(
1191
+ [
1192
+ _zip_file_map(
1193
+ {
1194
+ "workflow.py": "print('hello')",
1195
+ "metadata.json": json.dumps(
1196
+ {
1197
+ "deployment_id": deployment_id,
1198
+ "deployment_name": deployment_name,
1199
+ "label": deployment_label,
1200
+ }
1201
+ ),
1202
+ }
1203
+ )
1204
+ ]
1205
+ )
1206
+
1207
+ # AND we are currently in a new directory
1208
+ current_dir = os.getcwd()
1209
+ temp_dir = tempfile.mkdtemp()
1210
+ os.chdir(temp_dir)
1211
+
1212
+ # WHEN the user runs the pull command with the workflow deployment
1213
+ runner = CliRunner()
1214
+ result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", deployment_id])
1215
+
1216
+ # THEN the command returns successfully
1217
+ assert result.exit_code == 0
1218
+
1219
+ # AND the deployment is saved in the config
1220
+ vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
1221
+ assert os.path.exists(vellum_lock_json)
1222
+ with open(vellum_lock_json) as f:
1223
+ lock_data = json.loads(f.read())
1224
+ assert len(lock_data["workflows"]) == 1
1225
+ assert len(lock_data["workflows"][0]["deployments"]) == 1
1226
+ deployment = lock_data["workflows"][0]["deployments"][0]
1227
+ assert str(deployment["id"]) == deployment_id
1228
+ assert deployment["name"] == deployment_name
1229
+ assert deployment["label"] == deployment_label
1230
+
1231
+ os.chdir(current_dir)
1232
+
1233
+ # AND pull with name will not add a new deployment to the config
1234
+ vellum_client.workflows.pull.return_value = iter(
1235
+ [
1236
+ _zip_file_map(
1237
+ {
1238
+ "workflow.py": "print('hello')",
1239
+ "metadata.json": json.dumps(
1240
+ {
1241
+ "deployment_id": deployment_id,
1242
+ "deployment_name": deployment_name,
1243
+ "label": deployment_label,
1244
+ }
1245
+ ),
1246
+ }
1247
+ )
1248
+ ]
1249
+ )
1250
+
1251
+ os.chdir(temp_dir)
1252
+ result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", deployment_name])
1253
+ assert result.exit_code == 0
1254
+ with open(vellum_lock_json) as f:
1255
+ lock_data = json.loads(f.read())
1256
+ assert len(lock_data["workflows"][0]["deployments"]) == 1
1257
+ assert lock_data["workflows"][0]["deployments"][0]["id"] == deployment_id
1258
+ assert lock_data["workflows"][0]["deployments"][0]["name"] == deployment_name
1259
+ assert lock_data["workflows"][0]["deployments"][0]["label"] == deployment_label
1260
+
1261
+ os.chdir(current_dir)
@@ -12,8 +12,7 @@ from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
12
12
 
13
13
 
14
14
  def test_load_workflow_event_display_context():
15
- # DEPRECATED: Use `vellum.workflows.events.workflow.WorkflowEventDisplayContext` instead. Will be removed in 0.15.0
16
- from vellum_ee.workflows.display.types import WorkflowEventDisplayContext
15
+ from vellum.workflows.events.workflow import WorkflowEventDisplayContext
17
16
 
18
17
  # We are actually just ensuring there are no circular dependencies when
19
18
  # our Workflow Server imports this class.
@@ -140,3 +139,358 @@ class CodeExecutionNode(BaseCodeExecutionNode[BaseState, int]):
140
139
 
141
140
  # AND we get the code execution result
142
141
  assert event.body.outputs == {"final_output": 5.0}
142
+
143
+
144
+ def test_load_from_module__simple_code_execution_node_with_try(
145
+ vellum_client,
146
+ ):
147
+ # GIVEN a simple Python script
148
+ py_code = """def main() -> int:
149
+ print("Hello")
150
+ return 1
151
+ """
152
+
153
+ # AND a workflow module with only a code execution node
154
+ files = {
155
+ "__init__.py": "",
156
+ "workflow.py": """
157
+ from vellum.workflows import BaseWorkflow
158
+ from vellum.workflows.state import BaseState
159
+
160
+ from .nodes.code_execution_node import CodeExecutionNode
161
+
162
+ class Workflow(BaseWorkflow):
163
+ graph = CodeExecutionNode
164
+
165
+ class Outputs(BaseWorkflow.Outputs):
166
+ final_output = CodeExecutionNode.Outputs.result
167
+ """,
168
+ "nodes/__init__.py": """
169
+ from .code_execution_node import CodeExecutionNode
170
+
171
+ __all__ = ["CodeExecutionNode"]
172
+ """,
173
+ "nodes/code_execution_node/__init__.py": """
174
+ from typing import Union
175
+
176
+ from vellum.workflows.nodes.core import TryNode
177
+ from vellum.workflows.nodes.displayable import CodeExecutionNode as BaseCodeExecutionNode
178
+ from vellum.workflows.state import BaseState
179
+
180
+ @TryNode.wrap()
181
+ class CodeExecutionNode(BaseCodeExecutionNode[BaseState, Union[float, int]]):
182
+ filepath = "./script.py"
183
+ code_inputs = {}
184
+ runtime = "PYTHON_3_11_6"
185
+ packages = []
186
+ """,
187
+ "nodes/code_execution_node/script.py": py_code,
188
+ }
189
+
190
+ namespace = str(uuid4())
191
+
192
+ # AND the virtual file loader is registered
193
+ finder = VirtualFileFinder(files, namespace)
194
+ sys.meta_path.append(finder)
195
+
196
+ # AND we know what the Code Execution Node will respond with
197
+ mock_code_execution = CodeExecutorResponse(
198
+ log="hello",
199
+ output=NumberVellumValue(value=1),
200
+ )
201
+ vellum_client.execute_code.return_value = mock_code_execution
202
+
203
+ # WHEN the workflow is loaded
204
+ Workflow = BaseWorkflow.load_from_module(namespace)
205
+ workflow = Workflow(context=WorkflowContext(generated_files=files))
206
+
207
+ # THEN the workflow is successfully initialized
208
+ assert workflow
209
+
210
+ # WHEN we run the workflow
211
+ event = workflow.run()
212
+
213
+ # THEN the execution is fulfilled
214
+ assert event.name == "workflow.execution.fulfilled"
215
+
216
+ # AND we get the code execution result
217
+ assert event.body.outputs == {"final_output": 1.0}
218
+
219
+
220
+ def test_load_from_module__simple_code_execution_node_with_retry(
221
+ vellum_client,
222
+ ):
223
+ # GIVEN a simple Python script
224
+ py_code = """def main() -> int:
225
+ print("Hello")
226
+ return 1
227
+ """
228
+
229
+ # AND a workflow module with only a code execution node wrapped with RetryNode
230
+ files = {
231
+ "__init__.py": "",
232
+ "workflow.py": """
233
+ from vellum.workflows import BaseWorkflow
234
+ from vellum.workflows.state import BaseState
235
+
236
+ from .nodes.code_execution_node import CodeExecutionNode
237
+
238
+ class Workflow(BaseWorkflow):
239
+ graph = CodeExecutionNode
240
+
241
+ class Outputs(BaseWorkflow.Outputs):
242
+ final_output = CodeExecutionNode.Outputs.result
243
+ """,
244
+ "nodes/__init__.py": """
245
+ from .code_execution_node import CodeExecutionNode
246
+
247
+ __all__ = ["CodeExecutionNode"]
248
+ """,
249
+ "nodes/code_execution_node/__init__.py": """
250
+ from typing import Union
251
+
252
+ from vellum.workflows.nodes.core.retry_node.node import RetryNode
253
+ from vellum.workflows.nodes.displayable import CodeExecutionNode as BaseCodeExecutionNode
254
+ from vellum.workflows.state import BaseState
255
+
256
+ @RetryNode.wrap(max_attempts=3)
257
+ class CodeExecutionNode(BaseCodeExecutionNode[BaseState, Union[float, int]]):
258
+ filepath = "./script.py"
259
+ code_inputs = {}
260
+ runtime = "PYTHON_3_11_6"
261
+ packages = []
262
+ """,
263
+ "nodes/code_execution_node/script.py": py_code,
264
+ }
265
+
266
+ namespace = str(uuid4())
267
+
268
+ # AND the virtual file loader is registered
269
+ finder = VirtualFileFinder(files, namespace)
270
+ sys.meta_path.append(finder)
271
+
272
+ # AND we know what the Code Execution Node will respond with
273
+ mock_code_execution = CodeExecutorResponse(
274
+ log="hello",
275
+ output=NumberVellumValue(value=1),
276
+ )
277
+ vellum_client.execute_code.return_value = mock_code_execution
278
+
279
+ # WHEN the workflow is loaded
280
+ Workflow = BaseWorkflow.load_from_module(namespace)
281
+ workflow = Workflow(context=WorkflowContext(generated_files=files))
282
+
283
+ # THEN the workflow is successfully initialized
284
+ assert workflow
285
+
286
+ # WHEN we run the workflow
287
+ event = workflow.run()
288
+
289
+ # THEN the execution is fulfilled
290
+ assert event.name == "workflow.execution.fulfilled"
291
+
292
+ # AND we get the code execution result
293
+ assert event.body.outputs == {"final_output": 1.0}
294
+
295
+
296
+ def test_load_from_module__code_execution_within_subworkflow(
297
+ vellum_client,
298
+ ):
299
+ # GIVEN a simple Python script
300
+ py_code = """def main() -> int:
301
+ print("Hello")
302
+ return 1
303
+ """
304
+
305
+ # AND a workflow module with a subworkflow containing a code execution node
306
+ files = {
307
+ "__init__.py": "",
308
+ "workflow.py": """
309
+ from vellum.workflows import BaseWorkflow
310
+ from vellum.workflows.state import BaseState
311
+
312
+ from .nodes.subworkflow import Subworkflow
313
+
314
+ class Workflow(BaseWorkflow):
315
+ graph = Subworkflow
316
+
317
+ class Outputs(BaseWorkflow.Outputs):
318
+ final_output = Subworkflow.Outputs.result
319
+ """,
320
+ "nodes/__init__.py": """
321
+ from .subworkflow import Subworkflow
322
+
323
+ __all__ = ["Subworkflow"]
324
+ """,
325
+ "nodes/subworkflow/__init__.py": """
326
+ from vellum.workflows.nodes.displayable import InlineSubworkflowNode
327
+
328
+ from .workflow import SubworkflowWorkflow
329
+
330
+ class Subworkflow(InlineSubworkflowNode):
331
+ subworkflow = SubworkflowWorkflow
332
+ """,
333
+ "nodes/subworkflow/nodes/__init__.py": """
334
+ from .code_execution_node import CodeExecutionNode
335
+
336
+ __all__ = ["CodeExecutionNode"]
337
+ """,
338
+ "nodes/subworkflow/nodes/code_execution_node/__init__.py": """
339
+ from typing import Union
340
+
341
+ from vellum.workflows.nodes.displayable import CodeExecutionNode as BaseCodeExecutionNode
342
+ from vellum.workflows.state import BaseState
343
+
344
+ class CodeExecutionNode(BaseCodeExecutionNode[BaseState, Union[float, int]]):
345
+ filepath = "./script.py"
346
+ code_inputs = {}
347
+ runtime = "PYTHON_3_11_6"
348
+ packages = []
349
+ """,
350
+ "nodes/subworkflow/nodes/code_execution_node/script.py": py_code,
351
+ "nodes/subworkflow/workflow.py": """
352
+ from vellum.workflows import BaseWorkflow
353
+
354
+ from .nodes.code_execution_node import CodeExecutionNode
355
+
356
+ class SubworkflowWorkflow(BaseWorkflow):
357
+ graph = CodeExecutionNode
358
+
359
+ class Outputs(BaseWorkflow.Outputs):
360
+ result = CodeExecutionNode.Outputs.result
361
+ """,
362
+ }
363
+
364
+ namespace = str(uuid4())
365
+
366
+ # AND the virtual file loader is registered
367
+ finder = VirtualFileFinder(files, namespace)
368
+ sys.meta_path.append(finder)
369
+
370
+ # AND we know what the Code Execution Node will respond with
371
+ mock_code_execution = CodeExecutorResponse(
372
+ log="hello",
373
+ output=NumberVellumValue(value=1),
374
+ )
375
+ vellum_client.execute_code.return_value = mock_code_execution
376
+
377
+ # WHEN the workflow is loaded
378
+ Workflow = BaseWorkflow.load_from_module(namespace)
379
+ workflow = Workflow(context=WorkflowContext(generated_files=files))
380
+
381
+ # THEN the workflow is successfully initialized
382
+ assert workflow
383
+
384
+ # WHEN we run the workflow
385
+ event = workflow.run()
386
+
387
+ # THEN the execution is fulfilled
388
+ assert event.name == "workflow.execution.fulfilled"
389
+
390
+ # AND we get the code execution result from the subworkflow
391
+ assert event.body.outputs == {"final_output": 1.0}
392
+
393
+
394
+ def test_load_from_module__code_execution_within_map_node(
395
+ vellum_client,
396
+ ):
397
+ # GIVEN a simple Python script
398
+ py_code = """def main() -> int:
399
+ print("Hello")
400
+ return 1
401
+ """
402
+
403
+ # AND a workflow module with a map node containing a code execution node
404
+ files = {
405
+ "__init__.py": "",
406
+ "workflow.py": """
407
+ from vellum.workflows import BaseWorkflow
408
+ from vellum.workflows.state import BaseState
409
+
410
+ from .nodes.map_node import MapNode
411
+
412
+ class Workflow(BaseWorkflow):
413
+ graph = MapNode
414
+
415
+ class Outputs(BaseWorkflow.Outputs): # noqa: W293
416
+ results = MapNode.Outputs.final_output
417
+ """,
418
+ "nodes/__init__.py": """
419
+ from .map_node import MapNode
420
+
421
+ __all__ = ["MapNode"]
422
+ """,
423
+ "nodes/map_node/__init__.py": """
424
+ from vellum.workflows.nodes.core.map_node import MapNode as BaseMapNode
425
+
426
+ from .workflow import MapNodeWorkflow
427
+
428
+ class MapNode(BaseMapNode):
429
+ items = ["foo", "bar", "baz"]
430
+ subworkflow = MapNodeWorkflow
431
+ max_concurrency = 4
432
+ """,
433
+ "nodes/map_node/nodes/__init__.py": """
434
+ from .code_execution_node import CodeExecutionNode
435
+
436
+ __all__ = ["CodeExecutionNode"]
437
+ """,
438
+ "nodes/map_node/nodes/code_execution_node/__init__.py": """
439
+ from typing import Union
440
+
441
+ from vellum.workflows.nodes.displayable import CodeExecutionNode as BaseCodeExecutionNode
442
+ from vellum.workflows.state import BaseState
443
+
444
+ class CodeExecutionNode(BaseCodeExecutionNode[BaseState, Union[float, int]]):
445
+ filepath = "./script.py"
446
+ code_inputs = {}
447
+ runtime = "PYTHON_3_11_6"
448
+ packages = []
449
+ """,
450
+ "nodes/map_node/nodes/code_execution_node/script.py": py_code,
451
+ "nodes/map_node/workflow.py": """
452
+ from vellum.workflows import BaseWorkflow
453
+ from vellum.workflows.state import BaseState
454
+
455
+ from .nodes.code_execution_node import CodeExecutionNode
456
+
457
+ class MapNodeInputs:
458
+ item: str
459
+ index: int
460
+
461
+ class MapNodeWorkflow(BaseWorkflow):
462
+ graph = CodeExecutionNode
463
+
464
+ class Outputs(BaseWorkflow.Outputs): # noqa: W293
465
+ final_output = CodeExecutionNode.Outputs.result
466
+ """,
467
+ }
468
+
469
+ namespace = str(uuid4())
470
+
471
+ # AND the virtual file loader is registered
472
+ finder = VirtualFileFinder(files, namespace)
473
+ sys.meta_path.append(finder)
474
+
475
+ # AND we know what the Code Execution Node will respond with
476
+ mock_code_execution = CodeExecutorResponse(
477
+ log="hello",
478
+ output=NumberVellumValue(value=3),
479
+ )
480
+ vellum_client.execute_code.return_value = mock_code_execution
481
+
482
+ # WHEN the workflow is loaded
483
+ Workflow = BaseWorkflow.load_from_module(namespace)
484
+ workflow = Workflow(context=WorkflowContext(generated_files=files))
485
+
486
+ # THEN the workflow is successfully initialized
487
+ assert workflow
488
+
489
+ # WHEN we run the workflow
490
+ event = workflow.run()
491
+
492
+ # THEN the execution is fulfilled
493
+ assert event.name == "workflow.execution.fulfilled"
494
+
495
+ # AND we get the map node results as a list
496
+ assert event.body.outputs == {"results": [1.0, 1.0, 1.0]}