vellum-ai 0.11.8__py3-none-any.whl → 0.11.10__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/client/core/client_wrapper.py +1 -1
- vellum/workflows/nodes/displayable/code_execution_node/node.py +17 -1
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +141 -0
- {vellum_ai-0.11.8.dist-info → vellum_ai-0.11.10.dist-info}/METADATA +1 -1
- {vellum_ai-0.11.8.dist-info → vellum_ai-0.11.10.dist-info}/RECORD +14 -14
- {vellum_ai-0.11.8.dist-info → vellum_ai-0.11.10.dist-info}/WHEEL +1 -1
- vellum_cli/__init__.py +7 -0
- vellum_cli/pull.py +61 -13
- vellum_cli/tests/test_pull.py +80 -0
- vellum_ee/workflows/display/nodes/base_node_vellum_display.py +9 -0
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +4 -1
- vellum_ee/workflows/display/workflows/vellum_workflow_display.py +1 -5
- {vellum_ai-0.11.8.dist-info → vellum_ai-0.11.10.dist-info}/LICENSE +0 -0
- {vellum_ai-0.11.8.dist-info → vellum_ai-0.11.10.dist-info}/entry_points.txt +0 -0
@@ -17,7 +17,7 @@ class BaseClientWrapper:
|
|
17
17
|
headers: typing.Dict[str, str] = {
|
18
18
|
"X-Fern-Language": "Python",
|
19
19
|
"X-Fern-SDK-Name": "vellum-ai",
|
20
|
-
"X-Fern-SDK-Version": "0.11.
|
20
|
+
"X-Fern-SDK-Version": "0.11.10",
|
21
21
|
}
|
22
22
|
headers["X_API_KEY"] = self.api_key
|
23
23
|
return headers
|
@@ -73,7 +73,8 @@ class CodeExecutionNode(BaseNode[StateType], Generic[StateType, _OutputType], me
|
|
73
73
|
request_options: Optional[RequestOptions] = None - The request options to use for the custom script.
|
74
74
|
"""
|
75
75
|
|
76
|
-
filepath: ClassVar[str]
|
76
|
+
filepath: ClassVar[Optional[str]] = None
|
77
|
+
code: ClassVar[Optional[str]] = None
|
77
78
|
|
78
79
|
code_inputs: ClassVar[EntityInputsInterface]
|
79
80
|
runtime: CodeExecutionRuntime = "PYTHON_3_11_6"
|
@@ -190,6 +191,21 @@ class CodeExecutionNode(BaseNode[StateType], Generic[StateType, _OutputType], me
|
|
190
191
|
return compiled_inputs
|
191
192
|
|
192
193
|
def _resolve_code(self) -> str:
|
194
|
+
if self.code and self.filepath:
|
195
|
+
raise NodeException(
|
196
|
+
message="Cannot specify both `code` and `filepath` for a CodeExecutionNode",
|
197
|
+
code=VellumErrorCode.INVALID_INPUTS,
|
198
|
+
)
|
199
|
+
|
200
|
+
if self.code:
|
201
|
+
return self.code
|
202
|
+
|
203
|
+
if not self.filepath:
|
204
|
+
raise NodeException(
|
205
|
+
message="Must specify either `code` or `filepath` for a CodeExecutionNode",
|
206
|
+
code=VellumErrorCode.INVALID_INPUTS,
|
207
|
+
)
|
208
|
+
|
193
209
|
root = inspect.getfile(self.__class__)
|
194
210
|
code = read_file_from_path(node_filepath=root, script_filepath=self.filepath)
|
195
211
|
if not code:
|
@@ -1,6 +1,8 @@
|
|
1
|
+
import pytest
|
1
2
|
import os
|
2
3
|
|
3
4
|
from vellum import CodeExecutorResponse, NumberVellumValue, StringInput
|
5
|
+
from vellum.workflows.exceptions import NodeException
|
4
6
|
from vellum.workflows.inputs.base import BaseInputs
|
5
7
|
from vellum.workflows.nodes.displayable.code_execution_node import CodeExecutionNode
|
6
8
|
from vellum.workflows.references.vellum_secret import VellumSecretReference
|
@@ -62,6 +64,145 @@ def main(word: str) -> int:
|
|
62
64
|
)
|
63
65
|
|
64
66
|
|
67
|
+
def test_run_workflow__code_attribute(vellum_client):
|
68
|
+
"""Confirm that CodeExecutionNodes can use the `code` attribute to specify the code to execute."""
|
69
|
+
|
70
|
+
# GIVEN a node that subclasses CodeExecutionNode
|
71
|
+
class Inputs(BaseInputs):
|
72
|
+
word: str
|
73
|
+
|
74
|
+
class State(BaseState):
|
75
|
+
pass
|
76
|
+
|
77
|
+
class ExampleCodeExecutionNode(CodeExecutionNode[State, int]):
|
78
|
+
code = """\
|
79
|
+
def main(word: str) -> int:
|
80
|
+
print(word) # noqa: T201
|
81
|
+
return len(word)
|
82
|
+
"""
|
83
|
+
runtime = "PYTHON_3_11_6"
|
84
|
+
|
85
|
+
code_inputs = {
|
86
|
+
"word": Inputs.word,
|
87
|
+
}
|
88
|
+
|
89
|
+
# AND we know what the Code Execution Node will respond with
|
90
|
+
mock_code_execution = CodeExecutorResponse(
|
91
|
+
log="hello",
|
92
|
+
output=NumberVellumValue(value=5),
|
93
|
+
)
|
94
|
+
vellum_client.execute_code.return_value = mock_code_execution
|
95
|
+
|
96
|
+
# WHEN we run the node
|
97
|
+
node = ExampleCodeExecutionNode(
|
98
|
+
state=State(
|
99
|
+
meta=StateMeta(workflow_inputs=Inputs(word="hello")),
|
100
|
+
)
|
101
|
+
)
|
102
|
+
outputs = node.run()
|
103
|
+
|
104
|
+
# THEN the node should have produced the outputs we expect
|
105
|
+
assert outputs == {"result": 5, "log": "hello"}
|
106
|
+
|
107
|
+
# AND we should have invoked the Code with the expected inputs
|
108
|
+
vellum_client.execute_code.assert_called_once_with(
|
109
|
+
input_values=[
|
110
|
+
StringInput(name="word", value="hello"),
|
111
|
+
],
|
112
|
+
code="""\
|
113
|
+
def main(word: str) -> int:
|
114
|
+
print(word) # noqa: T201
|
115
|
+
return len(word)
|
116
|
+
""",
|
117
|
+
runtime="PYTHON_3_11_6",
|
118
|
+
output_type="NUMBER",
|
119
|
+
packages=[],
|
120
|
+
request_options=None,
|
121
|
+
)
|
122
|
+
|
123
|
+
|
124
|
+
def test_run_workflow__code_and_filepath_defined(vellum_client):
|
125
|
+
"""Confirm that CodeExecutionNodes raise an error if both `code` and `filepath` are defined."""
|
126
|
+
|
127
|
+
# GIVEN a node that subclasses CodeExecutionNode
|
128
|
+
class Inputs(BaseInputs):
|
129
|
+
word: str
|
130
|
+
|
131
|
+
class State(BaseState):
|
132
|
+
pass
|
133
|
+
|
134
|
+
fixture = os.path.abspath(os.path.join(__file__, "../fixtures/main.py"))
|
135
|
+
|
136
|
+
class ExampleCodeExecutionNode(CodeExecutionNode[State, int]):
|
137
|
+
filepath = fixture
|
138
|
+
code = """\
|
139
|
+
def main(word: str) -> int:
|
140
|
+
print(word) # noqa: T201
|
141
|
+
return len(word)
|
142
|
+
"""
|
143
|
+
runtime = "PYTHON_3_11_6"
|
144
|
+
|
145
|
+
code_inputs = {
|
146
|
+
"word": Inputs.word,
|
147
|
+
}
|
148
|
+
|
149
|
+
# AND we know what the Code Execution Node will respond with
|
150
|
+
mock_code_execution = CodeExecutorResponse(
|
151
|
+
log="hello",
|
152
|
+
output=NumberVellumValue(value=5),
|
153
|
+
)
|
154
|
+
vellum_client.execute_code.return_value = mock_code_execution
|
155
|
+
|
156
|
+
# WHEN we run the node
|
157
|
+
node = ExampleCodeExecutionNode(
|
158
|
+
state=State(
|
159
|
+
meta=StateMeta(workflow_inputs=Inputs(word="hello")),
|
160
|
+
)
|
161
|
+
)
|
162
|
+
with pytest.raises(NodeException) as exc_info:
|
163
|
+
node.run()
|
164
|
+
|
165
|
+
# THEN the node should have produced the exception we expected
|
166
|
+
assert exc_info.value.message == "Cannot specify both `code` and `filepath` for a CodeExecutionNode"
|
167
|
+
|
168
|
+
|
169
|
+
def test_run_workflow__code_and_filepath_not_defined(vellum_client):
|
170
|
+
"""Confirm that CodeExecutionNodes raise an error if neither `code` nor `filepath` are defined."""
|
171
|
+
|
172
|
+
# GIVEN a node that subclasses CodeExecutionNode
|
173
|
+
class Inputs(BaseInputs):
|
174
|
+
word: str
|
175
|
+
|
176
|
+
class State(BaseState):
|
177
|
+
pass
|
178
|
+
|
179
|
+
class ExampleCodeExecutionNode(CodeExecutionNode[State, int]):
|
180
|
+
runtime = "PYTHON_3_11_6"
|
181
|
+
|
182
|
+
code_inputs = {
|
183
|
+
"word": Inputs.word,
|
184
|
+
}
|
185
|
+
|
186
|
+
# AND we know what the Code Execution Node will respond with
|
187
|
+
mock_code_execution = CodeExecutorResponse(
|
188
|
+
log="hello",
|
189
|
+
output=NumberVellumValue(value=5),
|
190
|
+
)
|
191
|
+
vellum_client.execute_code.return_value = mock_code_execution
|
192
|
+
|
193
|
+
# WHEN we run the node
|
194
|
+
node = ExampleCodeExecutionNode(
|
195
|
+
state=State(
|
196
|
+
meta=StateMeta(workflow_inputs=Inputs(word="hello")),
|
197
|
+
)
|
198
|
+
)
|
199
|
+
with pytest.raises(NodeException) as exc_info:
|
200
|
+
node.run()
|
201
|
+
|
202
|
+
# THEN the node should have produced the exception we expected
|
203
|
+
assert exc_info.value.message == "Must specify either `code` or `filepath` for a CodeExecutionNode"
|
204
|
+
|
205
|
+
|
65
206
|
def test_run_workflow__vellum_secret(vellum_client):
|
66
207
|
"""Confirm that CodeExecutionNodes can use Vellum Secrets"""
|
67
208
|
|
@@ -1,17 +1,17 @@
|
|
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=
|
3
|
+
vellum_cli/__init__.py,sha256=XG6aC1NSPfBjNFzqiy9Lbk4DnELCKO8lpIZ3jeJR0r0,6990
|
4
4
|
vellum_cli/aliased_group.py,sha256=ugW498j0yv4ALJ8vS9MsO7ctDW7Jlir9j6nE_uHAP8c,3363
|
5
5
|
vellum_cli/config.py,sha256=wJQnv3tCgu1BOugg0AOP94yQ-x1yAg8juX_QoFN9Y7w,5223
|
6
6
|
vellum_cli/image_push.py,sha256=SJwhwWJsLjwGNezNVd_oCVpFMfPsAB3dfLWmriZZUtw,4419
|
7
7
|
vellum_cli/logger.py,sha256=PuRFa0WCh4sAGFS5aqWB0QIYpS6nBWwPJrIXpWxugV4,1022
|
8
|
-
vellum_cli/pull.py,sha256=
|
8
|
+
vellum_cli/pull.py,sha256=EkQirsTe0KCpJ8oHTE3S9_M2XHSrb2wrXNq4hTkMZng,5770
|
9
9
|
vellum_cli/push.py,sha256=kbvlzZ9KnkS5DxxKHQP5ZvHHk1-CbCDg9LqnIRAWyt4,5258
|
10
10
|
vellum_cli/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
11
|
vellum_cli/tests/conftest.py,sha256=eFGwBxib3Nki830lIFintB0b6r4x8T_KMnmzhlTY5x0,1337
|
12
12
|
vellum_cli/tests/test_config.py,sha256=uvKGDc8BoVyT9_H0Z-g8469zVxomn6Oi3Zj-vK7O_wU,2631
|
13
13
|
vellum_cli/tests/test_main.py,sha256=qDZG-aQauPwBwM6A2DIu1494n47v3pL28XakTbLGZ-k,272
|
14
|
-
vellum_cli/tests/test_pull.py,sha256=
|
14
|
+
vellum_cli/tests/test_pull.py,sha256=pGqpBkahC360UITDE8BlcJ7LPd9DyRPn4NrCNdkQmAQ,15907
|
15
15
|
vellum_cli/tests/test_push.py,sha256=V2iGcskh2X3OHj2uV5Vx_BhmtyfmUkyx0lrp8DDOExc,5824
|
16
16
|
vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
17
|
vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -20,7 +20,7 @@ vellum_ee/workflows/display/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
|
|
20
20
|
vellum_ee/workflows/display/base.py,sha256=3ZFUYRNKL24fBqXhKpa_Dq2W1a-a86J20dmJYA3H2eY,1755
|
21
21
|
vellum_ee/workflows/display/nodes/__init__.py,sha256=5XOcZJXYUgaLS55QgRJzyQ_W1tpeprjnYAeYVezqoGw,160
|
22
22
|
vellum_ee/workflows/display/nodes/base_node_display.py,sha256=3W7X1V2Lv0k6djYp60LDu-0lYVMNsEjPXmNmIQ4UW6s,5961
|
23
|
-
vellum_ee/workflows/display/nodes/base_node_vellum_display.py,sha256=
|
23
|
+
vellum_ee/workflows/display/nodes/base_node_vellum_display.py,sha256=F30B7L3HJLdeYzQmn17KVduWWXPb3KFvFFEVltYWxvY,2124
|
24
24
|
vellum_ee/workflows/display/nodes/get_node_display_class.py,sha256=xg6DWEm8CDiOn_fX74fT2XtSEDCnq06dHKj7HX9JnJw,907
|
25
25
|
vellum_ee/workflows/display/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
26
26
|
vellum_ee/workflows/display/nodes/tests/test_base_node_display.py,sha256=0y20AAqDivg58hQjHbiCPjEdEghfWmWr-NdYw3u-AwM,1054
|
@@ -28,7 +28,7 @@ vellum_ee/workflows/display/nodes/types.py,sha256=St1BB6no528OyELGiyRabWao0GGw6m
|
|
28
28
|
vellum_ee/workflows/display/nodes/utils.py,sha256=sloya5TpXsnot1HURc9L51INwflRqUzHxRVnCS9Cd-4,973
|
29
29
|
vellum_ee/workflows/display/nodes/vellum/__init__.py,sha256=nmPLj8vkbVCS46XQqmHq8Xj8Mr36wCK_vWf26A9KIkw,1505
|
30
30
|
vellum_ee/workflows/display/nodes/vellum/api_node.py,sha256=4SSQGecKWHuoGy5YIGJeOZVHGKwTs_8Y-gf3GvsHb0M,8506
|
31
|
-
vellum_ee/workflows/display/nodes/vellum/code_execution_node.py,sha256=
|
31
|
+
vellum_ee/workflows/display/nodes/vellum/code_execution_node.py,sha256=qJXUTVGfL88Katxu6HQb4yCPUzBFTBFDXcf8pdId3U8,4076
|
32
32
|
vellum_ee/workflows/display/nodes/vellum/conditional_node.py,sha256=gUbSP8_oSAMNIb0CGiefd2FMYgoO6wMoG6iA1FakMjk,13293
|
33
33
|
vellum_ee/workflows/display/nodes/vellum/error_node.py,sha256=ygTjSjYDI4DtkxADWub5rhBnRWItMKWF6fezBrgpOKA,1979
|
34
34
|
vellum_ee/workflows/display/nodes/vellum/final_output_node.py,sha256=UezalObmZ3mcg7Nou2RgiI_0cmc7_tSdZLNB591iCcI,2772
|
@@ -73,13 +73,13 @@ vellum_ee/workflows/display/vellum.py,sha256=OSv0ZS50h1zJbunJ9TH7VEWFw-exXdK_Zsd
|
|
73
73
|
vellum_ee/workflows/display/workflows/__init__.py,sha256=kapXsC67VJcgSuiBMa86FdePG5A9kMB5Pi4Uy1O2ob4,207
|
74
74
|
vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=HkakkrNgVFoHlUP7yHlQjHOvii3CZ90iyU1062PfoW4,12819
|
75
75
|
vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py,sha256=AMxNnTm2z3LIR5rqxoCAfuy37F2FTuSRDVtKUoezO8M,1184
|
76
|
-
vellum_ee/workflows/display/workflows/vellum_workflow_display.py,sha256=
|
76
|
+
vellum_ee/workflows/display/workflows/vellum_workflow_display.py,sha256=DWaiYdP138kN3fbQvloh6VEBPlxzd6zSLydc6NpYu3s,17145
|
77
77
|
vellum/__init__.py,sha256=QmGeEPXeFxgkZa849KKK3wH3Y641wyt00Rytfay6KiM,35520
|
78
78
|
vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
|
79
79
|
vellum/client/__init__.py,sha256=o4m7iRZWEV8rP3GkdaztHAjNmjxjWERlarviFoHzuKI,110927
|
80
80
|
vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
|
81
81
|
vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
|
82
|
-
vellum/client/core/client_wrapper.py,sha256=
|
82
|
+
vellum/client/core/client_wrapper.py,sha256=6zb4goHdzGRX1BY9bkyHFFaNstiWKUbnXvOxT_2cidM,1891
|
83
83
|
vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
|
84
84
|
vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
|
85
85
|
vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
|
@@ -1302,11 +1302,11 @@ vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py,sha256=1_OXD
|
|
1302
1302
|
vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py,sha256=MdrAKN8QGPk_JnNjbEBaVVKwVLPE2judbBcWuYJgbkY,4964
|
1303
1303
|
vellum/workflows/nodes/displayable/bases/search_node.py,sha256=S7J8tTW681O4wcWYerGOfH6h-_BlE8-JMJHpW8eCVG0,3564
|
1304
1304
|
vellum/workflows/nodes/displayable/code_execution_node/__init__.py,sha256=0FLWMMktpzSnmBMizQglBpcPrP80fzVsoJwJgf822Cg,76
|
1305
|
-
vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=
|
1305
|
+
vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=OHU9SnB0JDTTSFIp7io-9Jckelqtq84AP-QOcZBRNXY,8640
|
1306
1306
|
vellum/workflows/nodes/displayable/code_execution_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1307
1307
|
vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1308
1308
|
vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/main.py,sha256=5QsbmkzSlSbcbWTG_JmIqcP-JNJzOPTKxGzdHos19W4,79
|
1309
|
-
vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=
|
1309
|
+
vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=s93M_EnU-4n60iSKv3FCf0kppwzFH5FNi9o9E58fQ3I,7510
|
1310
1310
|
vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=LfI3kj2zQz6UGMld_uA9z2LjZobqRcgxQO4jdUWkg7o,376
|
1311
1311
|
vellum/workflows/nodes/displayable/conditional_node/__init__.py,sha256=AS_EIqFdU1F9t8aLmbZU-rLh9ry6LCJ0uj0D8F0L5Uw,72
|
1312
1312
|
vellum/workflows/nodes/displayable/conditional_node/node.py,sha256=REFZdEVetXGyOK1RbIN1T6yRblrP0hfyZUls2KfjTKg,1016
|
@@ -1378,8 +1378,8 @@ vellum/workflows/vellum_client.py,sha256=ODrq_TSl-drX2aezXegf7pizpWDVJuTXH-j6528
|
|
1378
1378
|
vellum/workflows/workflows/__init__.py,sha256=KY45TqvavCCvXIkyCFMEc0dc6jTMOUci93U2DUrlZYc,66
|
1379
1379
|
vellum/workflows/workflows/base.py,sha256=mnI-kZ78yt7u6NFSTUo-tYjDnarP-RJ7uZjwjCn6PCQ,16795
|
1380
1380
|
vellum/workflows/workflows/event_filters.py,sha256=-uQcMB7IpPd-idMku8f2QNVhPXPFWo6FZLlGjRf8rCo,1996
|
1381
|
-
vellum_ai-0.11.
|
1382
|
-
vellum_ai-0.11.
|
1383
|
-
vellum_ai-0.11.
|
1384
|
-
vellum_ai-0.11.
|
1385
|
-
vellum_ai-0.11.
|
1381
|
+
vellum_ai-0.11.10.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
|
1382
|
+
vellum_ai-0.11.10.dist-info/METADATA,sha256=mPMns1QolQ74Dw64M1w0SbtZXWFMMpEfK7VBYRrAmj8,5129
|
1383
|
+
vellum_ai-0.11.10.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
1384
|
+
vellum_ai-0.11.10.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
|
1385
|
+
vellum_ai-0.11.10.dist-info/RECORD,,
|
vellum_cli/__init__.py
CHANGED
@@ -148,6 +148,11 @@ def pull(
|
|
148
148
|
Should only be used for debugging purposes.""",
|
149
149
|
)
|
150
150
|
@click.option("--workflow-sandbox-id", type=str, help="Pull the Workflow from a specific Sandbox ID")
|
151
|
+
@click.option(
|
152
|
+
"--workflow-deployment",
|
153
|
+
type=str,
|
154
|
+
help="""Pull the Workflow from a specific Deployment. Can use the name or the ID of the Deployment.""",
|
155
|
+
)
|
151
156
|
@click.option(
|
152
157
|
"--exclude-code",
|
153
158
|
is_flag=True,
|
@@ -158,6 +163,7 @@ def workflows_pull(
|
|
158
163
|
module: Optional[str],
|
159
164
|
include_json: Optional[bool],
|
160
165
|
workflow_sandbox_id: Optional[str],
|
166
|
+
workflow_deployment: Optional[str],
|
161
167
|
exclude_code: Optional[bool],
|
162
168
|
) -> None:
|
163
169
|
"""
|
@@ -169,6 +175,7 @@ def workflows_pull(
|
|
169
175
|
module=module,
|
170
176
|
include_json=include_json,
|
171
177
|
workflow_sandbox_id=workflow_sandbox_id,
|
178
|
+
workflow_deployment=workflow_deployment,
|
172
179
|
exclude_code=exclude_code,
|
173
180
|
)
|
174
181
|
|
vellum_cli/pull.py
CHANGED
@@ -1,39 +1,84 @@
|
|
1
1
|
import io
|
2
2
|
import os
|
3
3
|
from pathlib import Path
|
4
|
+
from uuid import UUID
|
4
5
|
import zipfile
|
5
|
-
from typing import Optional
|
6
|
+
from typing import Optional, Union
|
6
7
|
|
7
8
|
from dotenv import load_dotenv
|
9
|
+
from pydash import snake_case
|
8
10
|
|
11
|
+
from vellum.client.core.pydantic_utilities import UniversalBaseModel
|
9
12
|
from vellum.workflows.vellum_client import create_vellum_client
|
10
13
|
from vellum_cli.config import VellumCliConfig, WorkflowConfig, load_vellum_cli_config
|
11
14
|
from vellum_cli.logger import load_cli_logger
|
12
15
|
|
13
16
|
|
14
|
-
def
|
17
|
+
def _is_valid_uuid(val: Union[str, UUID, None]) -> bool:
|
18
|
+
try:
|
19
|
+
UUID(str(val))
|
20
|
+
return True
|
21
|
+
except (ValueError, TypeError):
|
22
|
+
return False
|
23
|
+
|
24
|
+
|
25
|
+
class WorkflowConfigResolutionResult(UniversalBaseModel):
|
26
|
+
workflow_config: Optional[WorkflowConfig] = None
|
27
|
+
pk: Optional[str] = None
|
28
|
+
|
29
|
+
|
30
|
+
def _resolve_workflow_config(
|
15
31
|
config: VellumCliConfig,
|
16
32
|
module: Optional[str] = None,
|
17
33
|
workflow_sandbox_id: Optional[str] = None,
|
18
|
-
|
34
|
+
workflow_deployment: Optional[str] = None,
|
35
|
+
) -> WorkflowConfigResolutionResult:
|
36
|
+
if workflow_sandbox_id and workflow_deployment:
|
37
|
+
raise ValueError("Cannot specify both workflow_sandbox_id and workflow_deployment")
|
38
|
+
|
19
39
|
if module:
|
20
|
-
|
40
|
+
workflow_config = next((w for w in config.workflows if w.module == module), None)
|
41
|
+
return WorkflowConfigResolutionResult(
|
42
|
+
workflow_config=workflow_config,
|
43
|
+
pk=workflow_config.workflow_sandbox_id if workflow_config else None,
|
44
|
+
)
|
21
45
|
elif workflow_sandbox_id:
|
22
46
|
workflow_config = WorkflowConfig(
|
23
47
|
workflow_sandbox_id=workflow_sandbox_id,
|
24
48
|
module=f"workflow_{workflow_sandbox_id.split('-')[0]}",
|
25
49
|
)
|
26
50
|
config.workflows.append(workflow_config)
|
27
|
-
return
|
51
|
+
return WorkflowConfigResolutionResult(
|
52
|
+
workflow_config=workflow_config,
|
53
|
+
pk=workflow_config.workflow_sandbox_id,
|
54
|
+
)
|
55
|
+
elif workflow_deployment:
|
56
|
+
module = (
|
57
|
+
f"workflow_{workflow_deployment.split('-')[0]}"
|
58
|
+
if _is_valid_uuid(workflow_deployment)
|
59
|
+
else snake_case(workflow_deployment)
|
60
|
+
)
|
61
|
+
workflow_config = WorkflowConfig(
|
62
|
+
module=module,
|
63
|
+
)
|
64
|
+
config.workflows.append(workflow_config)
|
65
|
+
return WorkflowConfigResolutionResult(
|
66
|
+
workflow_config=workflow_config,
|
67
|
+
pk=workflow_deployment,
|
68
|
+
)
|
28
69
|
elif config.workflows:
|
29
|
-
return
|
70
|
+
return WorkflowConfigResolutionResult(
|
71
|
+
workflow_config=config.workflows[0],
|
72
|
+
pk=config.workflows[0].workflow_sandbox_id,
|
73
|
+
)
|
30
74
|
|
31
|
-
return
|
75
|
+
return WorkflowConfigResolutionResult()
|
32
76
|
|
33
77
|
|
34
78
|
def pull_command(
|
35
79
|
module: Optional[str] = None,
|
36
80
|
workflow_sandbox_id: Optional[str] = None,
|
81
|
+
workflow_deployment: Optional[str] = None,
|
37
82
|
include_json: Optional[bool] = None,
|
38
83
|
exclude_code: Optional[bool] = None,
|
39
84
|
) -> None:
|
@@ -41,17 +86,20 @@ def pull_command(
|
|
41
86
|
logger = load_cli_logger()
|
42
87
|
config = load_vellum_cli_config()
|
43
88
|
|
44
|
-
|
45
|
-
config,
|
46
|
-
module,
|
47
|
-
workflow_sandbox_id,
|
89
|
+
workflow_config_result = _resolve_workflow_config(
|
90
|
+
config=config,
|
91
|
+
module=module,
|
92
|
+
workflow_sandbox_id=workflow_sandbox_id,
|
93
|
+
workflow_deployment=workflow_deployment,
|
48
94
|
)
|
49
95
|
save_lock_file = not module
|
50
96
|
|
97
|
+
workflow_config = workflow_config_result.workflow_config
|
51
98
|
if not workflow_config:
|
52
99
|
raise ValueError("No workflow config found in project to pull from.")
|
53
100
|
|
54
|
-
|
101
|
+
pk = workflow_config_result.pk
|
102
|
+
if not pk:
|
55
103
|
raise ValueError("No workflow sandbox ID found in project to pull from.")
|
56
104
|
|
57
105
|
logger.info(f"Pulling workflow into {workflow_config.module}")
|
@@ -63,7 +111,7 @@ def pull_command(
|
|
63
111
|
query_parameters["exclude_code"] = exclude_code
|
64
112
|
|
65
113
|
response = client.workflows.pull(
|
66
|
-
|
114
|
+
pk,
|
67
115
|
request_options={"additional_query_parameters": query_parameters},
|
68
116
|
)
|
69
117
|
|
vellum_cli/tests/test_pull.py
CHANGED
@@ -164,6 +164,86 @@ def test_pull__sandbox_id_with_other_workflow_configured(vellum_client, mock_mod
|
|
164
164
|
assert f.read() == "print('hello')"
|
165
165
|
|
166
166
|
|
167
|
+
def test_pull__workflow_deployment_with_no_config(vellum_client):
|
168
|
+
# GIVEN a workflow deployment
|
169
|
+
workflow_deployment = "my-deployment"
|
170
|
+
|
171
|
+
# AND the workflow pull API call returns a zip file
|
172
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
173
|
+
|
174
|
+
# AND we are currently in a new directory
|
175
|
+
current_dir = os.getcwd()
|
176
|
+
temp_dir = tempfile.mkdtemp()
|
177
|
+
os.chdir(temp_dir)
|
178
|
+
|
179
|
+
# WHEN the user runs the pull command with the workflow deployment
|
180
|
+
runner = CliRunner()
|
181
|
+
result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", workflow_deployment])
|
182
|
+
os.chdir(current_dir)
|
183
|
+
|
184
|
+
# THEN the command returns successfully
|
185
|
+
assert result.exit_code == 0
|
186
|
+
|
187
|
+
# AND the pull api is called with the workflow deployment
|
188
|
+
vellum_client.workflows.pull.assert_called_once()
|
189
|
+
workflow_py = os.path.join(temp_dir, "my_deployment", "workflow.py")
|
190
|
+
assert os.path.exists(workflow_py)
|
191
|
+
with open(workflow_py) as f:
|
192
|
+
assert f.read() == "print('hello')"
|
193
|
+
|
194
|
+
# AND the vellum.lock.json file is created
|
195
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
196
|
+
assert os.path.exists(vellum_lock_json)
|
197
|
+
with open(vellum_lock_json) as f:
|
198
|
+
lock_data = json.loads(f.read())
|
199
|
+
assert lock_data == {
|
200
|
+
"version": "1.0",
|
201
|
+
"workflows": [
|
202
|
+
{
|
203
|
+
"module": "my_deployment",
|
204
|
+
"workflow_sandbox_id": None,
|
205
|
+
"ignore": None,
|
206
|
+
"deployments": [],
|
207
|
+
}
|
208
|
+
],
|
209
|
+
}
|
210
|
+
|
211
|
+
|
212
|
+
def test_pull__both_workflow_sandbox_id_and_deployment(vellum_client):
|
213
|
+
# GIVEN a workflow sandbox id
|
214
|
+
workflow_sandbox_id = "87654321-0000-0000-0000-000000000000"
|
215
|
+
|
216
|
+
# AND a workflow deployment
|
217
|
+
workflow_deployment = "my-deployment"
|
218
|
+
|
219
|
+
# AND the workflow pull API call returns a zip file
|
220
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
221
|
+
|
222
|
+
# AND we are currently in a new directory
|
223
|
+
current_dir = os.getcwd()
|
224
|
+
temp_dir = tempfile.mkdtemp()
|
225
|
+
os.chdir(temp_dir)
|
226
|
+
|
227
|
+
# WHEN the user runs the pull command with the workflow deployment
|
228
|
+
runner = CliRunner()
|
229
|
+
result = runner.invoke(
|
230
|
+
cli_main,
|
231
|
+
[
|
232
|
+
"workflows",
|
233
|
+
"pull",
|
234
|
+
"--workflow-sandbox-id",
|
235
|
+
workflow_sandbox_id,
|
236
|
+
"--workflow-deployment",
|
237
|
+
workflow_deployment,
|
238
|
+
],
|
239
|
+
)
|
240
|
+
os.chdir(current_dir)
|
241
|
+
|
242
|
+
# THEN the command returns successfully
|
243
|
+
assert result.exit_code == 1
|
244
|
+
assert "Cannot specify both workflow_sandbox_id and workflow_deployment" == str(result.exception)
|
245
|
+
|
246
|
+
|
167
247
|
def test_pull__remove_missing_files(vellum_client, mock_module):
|
168
248
|
# GIVEN a module on the user's filesystem
|
169
249
|
temp_dir = mock_module.temp_dir
|
@@ -30,6 +30,15 @@ class BaseNodeVellumDisplay(BaseNodeDisplay[NodeType]):
|
|
30
30
|
def get_target_handle_id(self) -> UUID:
|
31
31
|
return self._get_node_display_uuid("target_handle_id")
|
32
32
|
|
33
|
+
def get_target_handle_id_by_source_node_id(self, source_node_id: UUID) -> UUID:
|
34
|
+
"""
|
35
|
+
In the vast majority of cases, nodes will only have a single target handle and can be retrieved independently
|
36
|
+
of the source node. However, in rare cases (such as legacy Merge nodes), this method can be overridden to
|
37
|
+
account for the case of retrieving one amongst multiple target handles on a node.
|
38
|
+
"""
|
39
|
+
|
40
|
+
return self.get_target_handle_id()
|
41
|
+
|
33
42
|
def get_source_handle_id(self, port_displays: Dict[Port, PortDisplay]) -> UUID:
|
34
43
|
default_port = self._node.Ports.default
|
35
44
|
default_port_display = port_displays[default_port]
|
@@ -30,7 +30,10 @@ class BaseCodeExecutionNodeDisplay(BaseNodeVellumDisplay[_CodeExecutionNodeType]
|
|
30
30
|
node_id = self.node_id
|
31
31
|
|
32
32
|
node_file_path = inspect.getfile(node)
|
33
|
-
code = read_file_from_path(
|
33
|
+
code = read_file_from_path(
|
34
|
+
node_filepath=node_file_path,
|
35
|
+
script_filepath=(raise_if_descriptor(node.filepath)), # type: ignore
|
36
|
+
)
|
34
37
|
code_inputs = raise_if_descriptor(node.code_inputs)
|
35
38
|
|
36
39
|
inputs = [
|
@@ -12,7 +12,6 @@ from vellum.workflows.references import WorkflowInputReference
|
|
12
12
|
from vellum.workflows.references.output import OutputReference
|
13
13
|
from vellum.workflows.types.core import JsonArray, JsonObject
|
14
14
|
from vellum.workflows.types.generics import WorkflowType
|
15
|
-
from vellum_ee.workflows.display.nodes import BaseMergeNodeDisplay
|
16
15
|
from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
|
17
16
|
from vellum_ee.workflows.display.nodes.types import PortDisplay
|
18
17
|
from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input
|
@@ -369,10 +368,7 @@ class VellumWorkflowDisplay(
|
|
369
368
|
target_node_id = target_node_display.node_id
|
370
369
|
|
371
370
|
target_handle_id: UUID
|
372
|
-
|
373
|
-
target_handle_id = target_node_display.get_target_handle_id_by_source_node_id(source_node_id)
|
374
|
-
else:
|
375
|
-
target_handle_id = target_node_display.get_target_handle_id()
|
371
|
+
target_handle_id = target_node_display.get_target_handle_id_by_source_node_id(source_node_id)
|
376
372
|
|
377
373
|
return self._generate_edge_display_from_source(
|
378
374
|
source_node_id, source_handle_id, target_node_id, target_handle_id, overrides
|
File without changes
|
File without changes
|