vellum-ai 0.14.29__py3-none-any.whl → 0.14.31__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/client/resources/sandboxes/client.py +10 -0
- vellum/client/resources/workflow_sandboxes/client.py +10 -0
- vellum/workflows/nodes/bases/tests/test_base_node.py +27 -0
- vellum/workflows/nodes/displayable/code_execution_node/node.py +2 -1
- vellum/workflows/nodes/displayable/code_execution_node/utils.py +16 -2
- vellum/workflows/outputs/base.py +1 -1
- vellum/workflows/state/context.py +6 -0
- {vellum_ai-0.14.29.dist-info → vellum_ai-0.14.31.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.29.dist-info → vellum_ai-0.14.31.dist-info}/RECORD +16 -16
- vellum_cli/pull.py +69 -68
- vellum_cli/tests/test_pull.py +72 -1
- vellum_ee/workflows/tests/test_server.py +13 -21
- {vellum_ai-0.14.29.dist-info → vellum_ai-0.14.31.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.29.dist-info → vellum_ai-0.14.31.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.29.dist-info → vellum_ai-0.14.31.dist-info}/entry_points.txt +0 -0
| @@ -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. | 
| 21 | 
            +
                        "X-Fern-SDK-Version": "0.14.31",
         | 
| 22 22 | 
             
                    }
         | 
| 23 23 | 
             
                    headers["X_API_KEY"] = self.api_key
         | 
| 24 24 | 
             
                    return headers
         | 
| @@ -30,6 +30,7 @@ class SandboxesClient: | |
| 30 30 | 
             
                    prompt_deployment_name: typing.Optional[str] = OMIT,
         | 
| 31 31 | 
             
                    label: typing.Optional[str] = OMIT,
         | 
| 32 32 | 
             
                    release_tags: typing.Optional[typing.Sequence[str]] = OMIT,
         | 
| 33 | 
            +
                    release_description: typing.Optional[str] = OMIT,
         | 
| 33 34 | 
             
                    request_options: typing.Optional[RequestOptions] = None,
         | 
| 34 35 | 
             
                ) -> DeploymentRead:
         | 
| 35 36 | 
             
                    """
         | 
| @@ -53,6 +54,9 @@ class SandboxesClient: | |
| 53 54 | 
             
                    release_tags : typing.Optional[typing.Sequence[str]]
         | 
| 54 55 | 
             
                        Optionally provide the release tags that you'd like to be associated with the latest release of the created/updated Prompt Deployment.
         | 
| 55 56 |  | 
| 57 | 
            +
                    release_description : typing.Optional[str]
         | 
| 58 | 
            +
                        Optionally provide a description that details what's new in this Release.
         | 
| 59 | 
            +
             | 
| 56 60 | 
             
                    request_options : typing.Optional[RequestOptions]
         | 
| 57 61 | 
             
                        Request-specific configuration.
         | 
| 58 62 |  | 
| @@ -82,6 +86,7 @@ class SandboxesClient: | |
| 82 86 | 
             
                            "prompt_deployment_name": prompt_deployment_name,
         | 
| 83 87 | 
             
                            "label": label,
         | 
| 84 88 | 
             
                            "release_tags": release_tags,
         | 
| 89 | 
            +
                            "release_description": release_description,
         | 
| 85 90 | 
             
                        },
         | 
| 86 91 | 
             
                        request_options=request_options,
         | 
| 87 92 | 
             
                        omit=OMIT,
         | 
| @@ -250,6 +255,7 @@ class AsyncSandboxesClient: | |
| 250 255 | 
             
                    prompt_deployment_name: typing.Optional[str] = OMIT,
         | 
| 251 256 | 
             
                    label: typing.Optional[str] = OMIT,
         | 
| 252 257 | 
             
                    release_tags: typing.Optional[typing.Sequence[str]] = OMIT,
         | 
| 258 | 
            +
                    release_description: typing.Optional[str] = OMIT,
         | 
| 253 259 | 
             
                    request_options: typing.Optional[RequestOptions] = None,
         | 
| 254 260 | 
             
                ) -> DeploymentRead:
         | 
| 255 261 | 
             
                    """
         | 
| @@ -273,6 +279,9 @@ class AsyncSandboxesClient: | |
| 273 279 | 
             
                    release_tags : typing.Optional[typing.Sequence[str]]
         | 
| 274 280 | 
             
                        Optionally provide the release tags that you'd like to be associated with the latest release of the created/updated Prompt Deployment.
         | 
| 275 281 |  | 
| 282 | 
            +
                    release_description : typing.Optional[str]
         | 
| 283 | 
            +
                        Optionally provide a description that details what's new in this Release.
         | 
| 284 | 
            +
             | 
| 276 285 | 
             
                    request_options : typing.Optional[RequestOptions]
         | 
| 277 286 | 
             
                        Request-specific configuration.
         | 
| 278 287 |  | 
| @@ -310,6 +319,7 @@ class AsyncSandboxesClient: | |
| 310 319 | 
             
                            "prompt_deployment_name": prompt_deployment_name,
         | 
| 311 320 | 
             
                            "label": label,
         | 
| 312 321 | 
             
                            "release_tags": release_tags,
         | 
| 322 | 
            +
                            "release_description": release_description,
         | 
| 313 323 | 
             
                        },
         | 
| 314 324 | 
             
                        request_options=request_options,
         | 
| 315 325 | 
             
                        omit=OMIT,
         | 
| @@ -29,6 +29,7 @@ class WorkflowSandboxesClient: | |
| 29 29 | 
             
                    workflow_deployment_name: typing.Optional[str] = OMIT,
         | 
| 30 30 | 
             
                    label: typing.Optional[str] = OMIT,
         | 
| 31 31 | 
             
                    release_tags: typing.Optional[typing.Sequence[str]] = OMIT,
         | 
| 32 | 
            +
                    release_description: typing.Optional[str] = OMIT,
         | 
| 32 33 | 
             
                    request_options: typing.Optional[RequestOptions] = None,
         | 
| 33 34 | 
             
                ) -> WorkflowDeploymentRead:
         | 
| 34 35 | 
             
                    """
         | 
| @@ -52,6 +53,9 @@ class WorkflowSandboxesClient: | |
| 52 53 | 
             
                    release_tags : typing.Optional[typing.Sequence[str]]
         | 
| 53 54 | 
             
                        Optionally provide the release tags that you'd like to be associated with the latest release of the created/updated Prompt Deployment.
         | 
| 54 55 |  | 
| 56 | 
            +
                    release_description : typing.Optional[str]
         | 
| 57 | 
            +
                        Optionally provide a description that details what's new in this Release.
         | 
| 58 | 
            +
             | 
| 55 59 | 
             
                    request_options : typing.Optional[RequestOptions]
         | 
| 56 60 | 
             
                        Request-specific configuration.
         | 
| 57 61 |  | 
| @@ -81,6 +85,7 @@ class WorkflowSandboxesClient: | |
| 81 85 | 
             
                            "workflow_deployment_name": workflow_deployment_name,
         | 
| 82 86 | 
             
                            "label": label,
         | 
| 83 87 | 
             
                            "release_tags": release_tags,
         | 
| 88 | 
            +
                            "release_description": release_description,
         | 
| 84 89 | 
             
                        },
         | 
| 85 90 | 
             
                        request_options=request_options,
         | 
| 86 91 | 
             
                        omit=OMIT,
         | 
| @@ -181,6 +186,7 @@ class AsyncWorkflowSandboxesClient: | |
| 181 186 | 
             
                    workflow_deployment_name: typing.Optional[str] = OMIT,
         | 
| 182 187 | 
             
                    label: typing.Optional[str] = OMIT,
         | 
| 183 188 | 
             
                    release_tags: typing.Optional[typing.Sequence[str]] = OMIT,
         | 
| 189 | 
            +
                    release_description: typing.Optional[str] = OMIT,
         | 
| 184 190 | 
             
                    request_options: typing.Optional[RequestOptions] = None,
         | 
| 185 191 | 
             
                ) -> WorkflowDeploymentRead:
         | 
| 186 192 | 
             
                    """
         | 
| @@ -204,6 +210,9 @@ class AsyncWorkflowSandboxesClient: | |
| 204 210 | 
             
                    release_tags : typing.Optional[typing.Sequence[str]]
         | 
| 205 211 | 
             
                        Optionally provide the release tags that you'd like to be associated with the latest release of the created/updated Prompt Deployment.
         | 
| 206 212 |  | 
| 213 | 
            +
                    release_description : typing.Optional[str]
         | 
| 214 | 
            +
                        Optionally provide a description that details what's new in this Release.
         | 
| 215 | 
            +
             | 
| 207 216 | 
             
                    request_options : typing.Optional[RequestOptions]
         | 
| 208 217 | 
             
                        Request-specific configuration.
         | 
| 209 218 |  | 
| @@ -241,6 +250,7 @@ class AsyncWorkflowSandboxesClient: | |
| 241 250 | 
             
                            "workflow_deployment_name": workflow_deployment_name,
         | 
| 242 251 | 
             
                            "label": label,
         | 
| 243 252 | 
             
                            "release_tags": release_tags,
         | 
| 253 | 
            +
                            "release_description": release_description,
         | 
| 244 254 | 
             
                        },
         | 
| 245 255 | 
             
                        request_options=request_options,
         | 
| 246 256 | 
             
                        omit=OMIT,
         | 
| @@ -1,9 +1,12 @@ | |
| 1 | 
            +
            import pytest
         | 
| 1 2 | 
             
            from uuid import UUID
         | 
| 2 3 | 
             
            from typing import Optional
         | 
| 3 4 |  | 
| 4 5 | 
             
            from vellum.client.types.string_vellum_value_request import StringVellumValueRequest
         | 
| 5 6 | 
             
            from vellum.core.pydantic_utilities import UniversalBaseModel
         | 
| 7 | 
            +
            from vellum.workflows.descriptors.tests.test_utils import FixtureState
         | 
| 6 8 | 
             
            from vellum.workflows.inputs.base import BaseInputs
         | 
| 9 | 
            +
            from vellum.workflows.nodes import FinalOutputNode
         | 
| 7 10 | 
             
            from vellum.workflows.nodes.bases.base import BaseNode
         | 
| 8 11 | 
             
            from vellum.workflows.outputs.base import BaseOutputs
         | 
| 9 12 | 
             
            from vellum.workflows.state.base import BaseState, StateMeta
         | 
| @@ -232,3 +235,27 @@ def test_outputs_preserves_non_object_bases(): | |
| 232 235 | 
             
                # THEN the output values should be correct
         | 
| 233 236 | 
             
                assert outputs.foo == "bar"
         | 
| 234 237 | 
             
                assert outputs.bar == "baz"
         | 
| 238 | 
            +
             | 
| 239 | 
            +
             | 
| 240 | 
            +
            @pytest.mark.parametrize(
         | 
| 241 | 
            +
                "falsy_value,expected_type",
         | 
| 242 | 
            +
                [
         | 
| 243 | 
            +
                    ("", str),
         | 
| 244 | 
            +
                    (0, int),
         | 
| 245 | 
            +
                ],
         | 
| 246 | 
            +
            )
         | 
| 247 | 
            +
            def test_resolve_value__for_falsy_values(falsy_value, expected_type):
         | 
| 248 | 
            +
                """Test that falsy values in FinalOutputNode are handled correctly."""
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                class FalsyOutput(FinalOutputNode[FixtureState, expected_type]):  # type: ignore
         | 
| 251 | 
            +
                    class Outputs(FinalOutputNode.Outputs):
         | 
| 252 | 
            +
                        value = falsy_value
         | 
| 253 | 
            +
             | 
| 254 | 
            +
                # GIVEN a node with a falsy value
         | 
| 255 | 
            +
                falsy_node = FalsyOutput(state=FixtureState())
         | 
| 256 | 
            +
             | 
| 257 | 
            +
                # WHEN we run the node
         | 
| 258 | 
            +
                falsy_output = falsy_node.run()
         | 
| 259 | 
            +
             | 
| 260 | 
            +
                # THEN the output has the correct value
         | 
| 261 | 
            +
                assert falsy_output.value == falsy_value
         | 
| @@ -227,7 +227,8 @@ class CodeExecutionNode(BaseNode[StateType], Generic[StateType, _OutputType], me | |
| 227 227 | 
             
                        )
         | 
| 228 228 |  | 
| 229 229 | 
             
                    root = inspect.getfile(self.__class__)
         | 
| 230 | 
            -
             | 
| 230 | 
            +
             | 
| 231 | 
            +
                    code = read_file_from_path(node_filepath=root, script_filepath=self.filepath, context=self._context)
         | 
| 231 232 | 
             
                    if not code:
         | 
| 232 233 | 
             
                        raise NodeException(
         | 
| 233 234 | 
             
                            message=f"Filepath '{self.filepath}' does not exist",
         | 
| @@ -1,19 +1,33 @@ | |
| 1 1 | 
             
            import io
         | 
| 2 2 | 
             
            import os
         | 
| 3 | 
            -
            from typing import Any, Tuple, Union
         | 
| 3 | 
            +
            from typing import Any, Optional, Tuple, Union
         | 
| 4 4 |  | 
| 5 5 | 
             
            from pydantic import BaseModel
         | 
| 6 6 |  | 
| 7 7 | 
             
            from vellum.workflows.errors.types import WorkflowErrorCode
         | 
| 8 8 | 
             
            from vellum.workflows.exceptions import NodeException
         | 
| 9 9 | 
             
            from vellum.workflows.nodes.utils import cast_to_output_type
         | 
| 10 | 
            +
            from vellum.workflows.state.context import WorkflowContext
         | 
| 10 11 | 
             
            from vellum.workflows.types.core import EntityInputsInterface
         | 
| 11 12 |  | 
| 12 13 |  | 
| 13 | 
            -
            def read_file_from_path( | 
| 14 | 
            +
            def read_file_from_path(
         | 
| 15 | 
            +
                node_filepath: str, script_filepath: str, context: Optional[WorkflowContext] = None
         | 
| 16 | 
            +
            ) -> Union[str, None]:
         | 
| 14 17 | 
             
                node_filepath_dir = os.path.dirname(node_filepath)
         | 
| 15 18 | 
             
                full_filepath = os.path.join(node_filepath_dir, script_filepath)
         | 
| 16 19 |  | 
| 20 | 
            +
                # If dynamic file loader is present, try and read the code from there
         | 
| 21 | 
            +
                if context and context.generated_files:
         | 
| 22 | 
            +
                    # Strip out namespace
         | 
| 23 | 
            +
                    normalized_path = os.path.normpath(full_filepath)
         | 
| 24 | 
            +
                    stripped_node_filepath = "/".join(normalized_path.split("/")[1:])
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    code = context.generated_files.get(stripped_node_filepath, None)
         | 
| 27 | 
            +
                    if code is not None:
         | 
| 28 | 
            +
                        return code
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                # Default logic for reading from filesystem
         | 
| 17 31 | 
             
                try:
         | 
| 18 32 | 
             
                    with open(full_filepath) as file:
         | 
| 19 33 | 
             
                        return file.read()
         | 
    
        vellum/workflows/outputs/base.py
    CHANGED
    
    | @@ -145,7 +145,7 @@ class _BaseOutputsMeta(type): | |
| 145 145 | 
             
                        # we iterate through its inheritance hierarchy to find the first base class that has this attribute
         | 
| 146 146 | 
             
                        # and use its mapping.
         | 
| 147 147 | 
             
                        instance = vars(cls).get(name, undefined)
         | 
| 148 | 
            -
                        if  | 
| 148 | 
            +
                        if instance is undefined:
         | 
| 149 149 | 
             
                            for base in cls.__mro__[1:]:
         | 
| 150 150 | 
             
                                if hasattr(base, name):
         | 
| 151 151 | 
             
                                    instance = getattr(base, name)
         | 
| @@ -19,6 +19,7 @@ class WorkflowContext: | |
| 19 19 | 
             
                    *,
         | 
| 20 20 | 
             
                    vellum_client: Optional[Vellum] = None,
         | 
| 21 21 | 
             
                    execution_context: Optional[ExecutionContext] = None,
         | 
| 22 | 
            +
                    generated_files: Optional[dict[str, str]] = None,
         | 
| 22 23 | 
             
                ):
         | 
| 23 24 | 
             
                    self._vellum_client = vellum_client
         | 
| 24 25 | 
             
                    self._event_queue: Optional[Queue["WorkflowEvent"]] = None
         | 
| @@ -26,6 +27,7 @@ class WorkflowContext: | |
| 26 27 | 
             
                    self._execution_context = get_execution_context()
         | 
| 27 28 | 
             
                    if not self._execution_context.parent_context and execution_context:
         | 
| 28 29 | 
             
                        self._execution_context = execution_context
         | 
| 30 | 
            +
                    self._generated_files = generated_files
         | 
| 29 31 |  | 
| 30 32 | 
             
                @cached_property
         | 
| 31 33 | 
             
                def vellum_client(self) -> Vellum:
         | 
| @@ -38,6 +40,10 @@ class WorkflowContext: | |
| 38 40 | 
             
                def execution_context(self) -> ExecutionContext:
         | 
| 39 41 | 
             
                    return self._execution_context
         | 
| 40 42 |  | 
| 43 | 
            +
                @cached_property
         | 
| 44 | 
            +
                def generated_files(self) -> Optional[dict[str, str]]:
         | 
| 45 | 
            +
                    return self._generated_files
         | 
| 46 | 
            +
             | 
| 41 47 | 
             
                @cached_property
         | 
| 42 48 | 
             
                def node_output_mocks_map(self) -> Dict[Type[BaseOutputs], List[MockNodeExecution]]:
         | 
| 43 49 | 
             
                    return self._node_output_mocks_map
         | 
| @@ -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=lWyJw6sziXjyTopTYRdFF5hV-sYPVDdX0yVbG5fzcY4,585
         | 
| 10 | 
            -
            vellum_cli/pull.py,sha256= | 
| 10 | 
            +
            vellum_cli/pull.py,sha256=Cz4_LWeaMNRGw4qvKpUBFnOpY6PXzJJ-9d7NU90S9Jc,9629
         | 
| 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=QtbhYKMYn1DFnDyBij2mkQO32j9KOpZ5Pf0yek7k_Ao,1284
         | 
| 19 | 
            -
            vellum_cli/tests/test_pull.py,sha256= | 
| 19 | 
            +
            vellum_cli/tests/test_pull.py,sha256=asWPuOOeGY1Tj_JGu574A3-pnNKCDY0HqjaSWI0BXss,32824
         | 
| 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= | 
| 124 | 
            +
            vellum_ee/workflows/tests/test_server.py,sha256=Q9XD93jMwYjZOkcwMPNM2F4BWvcY71wxkYp8HgMgGFU,4435
         | 
| 125 125 | 
             
            vellum_ee/workflows/tests/test_virtual_files.py,sha256=TJEcMR0v2S8CkloXNmCHA0QW0K6pYNGaIjraJz7sFvY,2762
         | 
| 126 126 | 
             
            vellum/__init__.py,sha256=88-79I29hBTQvR1uH_BOCGMWuj2a4Nx82R_8KIESg28,40470
         | 
| 127 127 | 
             
            vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
         | 
| 128 128 | 
             
            vellum/client/__init__.py,sha256=Jv9sI5BNFo2OYA9px_aREFSIp655ryC3eaZSRI6yH1k,117826
         | 
| 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= | 
| 131 | 
            +
            vellum/client/core/client_wrapper.py,sha256=PGwx4aPEP3yRHu1laYbSTTdEDpTOYg6DZU5rt8u-QZo,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
         | 
| @@ -173,7 +173,7 @@ vellum/client/resources/organizations/client.py,sha256=Uye92moqjAcOCs4astmuFpT92 | |
| 173 173 | 
             
            vellum/client/resources/prompts/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
         | 
| 174 174 | 
             
            vellum/client/resources/prompts/client.py,sha256=_rNTUjhl_ZF3vyQa_M1BSTrX4DlFXU_SXkwwCEYKD2s,6598
         | 
| 175 175 | 
             
            vellum/client/resources/sandboxes/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
         | 
| 176 | 
            -
            vellum/client/resources/sandboxes/client.py,sha256= | 
| 176 | 
            +
            vellum/client/resources/sandboxes/client.py,sha256=SG4BV0NG1Ow10mXSu52ybj-c6hR7wcNxfGJK4eiHu_8,17670
         | 
| 177 177 | 
             
            vellum/client/resources/test_suite_runs/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
         | 
| 178 178 | 
             
            vellum/client/resources/test_suite_runs/client.py,sha256=tU-N1fEfXQPomt2f058PUNIhnGoMV4vo471PlQanTAs,15128
         | 
| 179 179 | 
             
            vellum/client/resources/test_suites/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
         | 
| @@ -184,7 +184,7 @@ vellum/client/resources/workflow_deployments/types/__init__.py,sha256=W7DKJ1nduw | |
| 184 184 | 
             
            vellum/client/resources/workflow_deployments/types/list_workflow_release_tags_request_source.py,sha256=LPETHLX9Ygha_JRT9oWZAZR6clv-W1tTelXzktkTBX8,178
         | 
| 185 185 | 
             
            vellum/client/resources/workflow_deployments/types/workflow_deployments_list_request_status.py,sha256=FXVkVmGM6DZ2RpTGnZXWJYiVlLQ-K5fDtX3WMaBPaWk,182
         | 
| 186 186 | 
             
            vellum/client/resources/workflow_sandboxes/__init__.py,sha256=OR3wE3pTgsZlTS-0ukeMWzSuEZF8PszuQTCHDh6JybI,175
         | 
| 187 | 
            -
            vellum/client/resources/workflow_sandboxes/client.py,sha256= | 
| 187 | 
            +
            vellum/client/resources/workflow_sandboxes/client.py,sha256=H6GP3yitYKSSb5D0fSF_dKG467d2NYMQf5bnFo0nSZM,12916
         | 
| 188 188 | 
             
            vellum/client/resources/workflow_sandboxes/types/__init__.py,sha256=EaGVRU1w6kJiiHrbZOeEa0c3ggjfgv_jBqsyOkCRWOI,212
         | 
| 189 189 | 
             
            vellum/client/resources/workflow_sandboxes/types/list_workflow_sandbox_examples_request_tag.py,sha256=TEwWit20W3X-zWPPLAhmUG05UudG9gaBSJ4Q4-rNJws,188
         | 
| 190 190 | 
             
            vellum/client/resources/workflows/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
         | 
| @@ -1498,7 +1498,7 @@ vellum/workflows/nodes/bases/__init__.py,sha256=cniHuz_RXdJ4TQgD8CBzoiKDiPxg62Er | |
| 1498 1498 | 
             
            vellum/workflows/nodes/bases/base.py,sha256=eW-3RSkBgtuGY8x2nmbHYiUg_HXS5U57n3k6Fh-dJ9s,15330
         | 
| 1499 1499 | 
             
            vellum/workflows/nodes/bases/base_adornment_node.py,sha256=afMwJLHK2Ke7sBpceVLnNdZMlU2O-6UgyG7lBt9SAQ8,3039
         | 
| 1500 1500 | 
             
            vellum/workflows/nodes/bases/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 1501 | 
            -
            vellum/workflows/nodes/bases/tests/test_base_node.py,sha256= | 
| 1501 | 
            +
            vellum/workflows/nodes/bases/tests/test_base_node.py,sha256=4PGjzFh4ZkvffOn-7xhqzsqfdLsYe_2IGqn002TL4LE,7099
         | 
| 1502 1502 | 
             
            vellum/workflows/nodes/core/__init__.py,sha256=5zDMCmyt1v0HTJzlUBwq3U9L825yZGZhT9JL18-mRR4,455
         | 
| 1503 1503 | 
             
            vellum/workflows/nodes/core/error_node/__init__.py,sha256=g7RRnlHhqu4qByfLjBwCunmgGA8dI5gNsjS3h6TwlSI,60
         | 
| 1504 1504 | 
             
            vellum/workflows/nodes/core/error_node/node.py,sha256=MFHU5vITYSK-L9CuMZ49In2ZeNLWnhZD0f8r5dWvb5Y,1270
         | 
| @@ -1543,12 +1543,12 @@ vellum/workflows/nodes/displayable/bases/tests/test_utils.py,sha256=eqdqbKNRWVMD | |
| 1543 1543 | 
             
            vellum/workflows/nodes/displayable/bases/types.py,sha256=C37B2Qh2YP7s7pUjd-EYKc2Zl1TbnCgI_mENuUSb8bo,1706
         | 
| 1544 1544 | 
             
            vellum/workflows/nodes/displayable/bases/utils.py,sha256=ckMUenSsNkiYmSw6FmjSMHYaCk8Y8_sUjL6lkFFEqts,5412
         | 
| 1545 1545 | 
             
            vellum/workflows/nodes/displayable/code_execution_node/__init__.py,sha256=0FLWMMktpzSnmBMizQglBpcPrP80fzVsoJwJgf822Cg,76
         | 
| 1546 | 
            -
            vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256= | 
| 1546 | 
            +
            vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=Ko_Dy17AjfSx2A4u5Xno5R0KH2p5akEHq8L0rQkySGs,9576
         | 
| 1547 1547 | 
             
            vellum/workflows/nodes/displayable/code_execution_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 1548 1548 | 
             
            vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 1549 1549 | 
             
            vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/main.py,sha256=5QsbmkzSlSbcbWTG_JmIqcP-JNJzOPTKxGzdHos19W4,79
         | 
| 1550 1550 | 
             
            vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=xAaoOfQHQUlp0iKlig87t0aT2cJM-8PxiTb1QDg8VmY,24641
         | 
| 1551 | 
            -
            vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256= | 
| 1551 | 
            +
            vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=G-sc7yOL5g6rLk99X8HAbXNcLxRaqpju9IXq1iUwnQI,4470
         | 
| 1552 1552 | 
             
            vellum/workflows/nodes/displayable/conditional_node/__init__.py,sha256=AS_EIqFdU1F9t8aLmbZU-rLh9ry6LCJ0uj0D8F0L5Uw,72
         | 
| 1553 1553 | 
             
            vellum/workflows/nodes/displayable/conditional_node/node.py,sha256=Qjfl33gZ3JEgxBA1EgzSUebboGvsARthIxxcQyvx5Gg,1152
         | 
| 1554 1554 | 
             
            vellum/workflows/nodes/displayable/conftest.py,sha256=tD_WIiw5WjFqnzgnGLtEZDaMj2XhQ1DptnBTKYeBbI0,5705
         | 
| @@ -1593,7 +1593,7 @@ vellum/workflows/nodes/tests/test_mocks.py,sha256=mfPvrs75PKcsNsbJLQAN6PDFoVqs9T | |
| 1593 1593 | 
             
            vellum/workflows/nodes/tests/test_utils.py,sha256=qNB6ApIsnFtE_9HDaEah9KD-zX8e10FhDixewS1uRcc,5199
         | 
| 1594 1594 | 
             
            vellum/workflows/nodes/utils.py,sha256=Tc3TAmAytb-gi30BAyzGY7DG0uS1_7KTGYdjrvKUSt0,8362
         | 
| 1595 1595 | 
             
            vellum/workflows/outputs/__init__.py,sha256=AyZ4pRh_ACQIGvkf0byJO46EDnSix1ZCAXfvh-ms1QE,94
         | 
| 1596 | 
            -
            vellum/workflows/outputs/base.py,sha256= | 
| 1596 | 
            +
            vellum/workflows/outputs/base.py,sha256=jOvSr44GEmtldits-bysJoxme7js6aN_LkdV-PpA7Dk,8631
         | 
| 1597 1597 | 
             
            vellum/workflows/ports/__init__.py,sha256=bZuMt-R7z5bKwpu4uPW7LlJeePOQWmCcDSXe5frUY5g,101
         | 
| 1598 1598 | 
             
            vellum/workflows/ports/node_ports.py,sha256=g4A-8iUAvEJSkaWppbvzAR8XU02R9U-qLN4rP2Kq4Aw,2743
         | 
| 1599 1599 | 
             
            vellum/workflows/ports/port.py,sha256=eI2SOZPZ5rsC3jMsxW6Rbn0NpaYQsrR7AapiIbbiy8Q,3635
         | 
| @@ -1618,7 +1618,7 @@ vellum/workflows/runner/runner.py,sha256=ww4fjZJBENkB5HJxdj92kTz7k_EyifCeAreupy5 | |
| 1618 1618 | 
             
            vellum/workflows/sandbox.py,sha256=GVJzVjMuYzOBnSrboB0_6MMRZWBluAyQ2o7syeaeBd0,2235
         | 
| 1619 1619 | 
             
            vellum/workflows/state/__init__.py,sha256=yUUdR-_Vl7UiixNDYQZ-GEM_kJI9dnOia75TtuNEsnE,60
         | 
| 1620 1620 | 
             
            vellum/workflows/state/base.py,sha256=Vkhneko3VlQrPsMLU1PYSzXU_W1u7_AraJsghiv5O-4,15512
         | 
| 1621 | 
            -
            vellum/workflows/state/context.py,sha256= | 
| 1621 | 
            +
            vellum/workflows/state/context.py,sha256=B-sGK9GpZPh7bgCPb8PJCrl_evod3Ru7rHs_RofisrU,2926
         | 
| 1622 1622 | 
             
            vellum/workflows/state/encoder.py,sha256=TnOQojc5lTQ83g9QbpA4UCqShJvutmTMxbpKt-9gNe4,1911
         | 
| 1623 1623 | 
             
            vellum/workflows/state/store.py,sha256=uVe-oN73KwGV6M6YLhwZMMUQhzTQomsVfVnb8V91gVo,1147
         | 
| 1624 1624 | 
             
            vellum/workflows/state/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| @@ -1650,8 +1650,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad | |
| 1650 1650 | 
             
            vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 1651 1651 | 
             
            vellum/workflows/workflows/tests/test_base_workflow.py,sha256=tCxrV3QBHL8wfdEO3bvKteDdw32xBlUl1_WxkAwaONw,8344
         | 
| 1652 1652 | 
             
            vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
         | 
| 1653 | 
            -
            vellum_ai-0.14. | 
| 1654 | 
            -
            vellum_ai-0.14. | 
| 1655 | 
            -
            vellum_ai-0.14. | 
| 1656 | 
            -
            vellum_ai-0.14. | 
| 1657 | 
            -
            vellum_ai-0.14. | 
| 1653 | 
            +
            vellum_ai-0.14.31.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
         | 
| 1654 | 
            +
            vellum_ai-0.14.31.dist-info/METADATA,sha256=BDTX1i0ZWziXvzVjwWKd3C6uEiD3QMQol9_a636y0-s,5484
         | 
| 1655 | 
            +
            vellum_ai-0.14.31.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
         | 
| 1656 | 
            +
            vellum_ai-0.14.31.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
         | 
| 1657 | 
            +
            vellum_ai-0.14.31.dist-info/RECORD,,
         | 
    
        vellum_cli/pull.py
    CHANGED
    
    | @@ -9,7 +9,6 @@ from dotenv import load_dotenv | |
| 9 9 | 
             
            from pydash import snake_case
         | 
| 10 10 |  | 
| 11 11 | 
             
            from vellum.client.core.pydantic_utilities import UniversalBaseModel
         | 
| 12 | 
            -
            from vellum.utils.uuid import is_valid_uuid
         | 
| 13 12 | 
             
            from vellum.workflows.vellum_client import create_vellum_client
         | 
| 14 13 | 
             
            from vellum_cli.config import VellumCliConfig, WorkflowConfig, load_vellum_cli_config
         | 
| 15 14 | 
             
            from vellum_cli.logger import load_cli_logger
         | 
| @@ -31,6 +30,7 @@ class RunnerConfig(UniversalBaseModel): | |
| 31 30 | 
             
            class PullContentsMetadata(UniversalBaseModel):
         | 
| 32 31 | 
             
                label: Optional[str] = None
         | 
| 33 32 | 
             
                runner_config: Optional[RunnerConfig] = None
         | 
| 33 | 
            +
                deployment_name: Optional[str] = None
         | 
| 34 34 |  | 
| 35 35 |  | 
| 36 36 | 
             
            def _resolve_workflow_config(
         | 
| @@ -78,13 +78,8 @@ def _resolve_workflow_config( | |
| 78 78 | 
             
                        pk=workflow_config.workflow_sandbox_id,
         | 
| 79 79 | 
             
                    )
         | 
| 80 80 | 
             
                elif workflow_deployment:
         | 
| 81 | 
            -
                    module = (
         | 
| 82 | 
            -
                        f"workflow_{workflow_deployment.split('-')[0]}"
         | 
| 83 | 
            -
                        if is_valid_uuid(workflow_deployment)
         | 
| 84 | 
            -
                        else snake_case(workflow_deployment)
         | 
| 85 | 
            -
                    )
         | 
| 86 81 | 
             
                    workflow_config = WorkflowConfig(
         | 
| 87 | 
            -
                        module= | 
| 82 | 
            +
                        module="",
         | 
| 88 83 | 
             
                    )
         | 
| 89 84 | 
             
                    config.workflows.append(workflow_config)
         | 
| 90 85 | 
             
                    return WorkflowConfigResolutionResult(
         | 
| @@ -156,67 +151,73 @@ def pull_command( | |
| 156 151 |  | 
| 157 152 | 
             
                error_content = ""
         | 
| 158 153 |  | 
| 159 | 
            -
                 | 
| 160 | 
            -
                     | 
| 161 | 
            -
                         | 
| 162 | 
            -
             | 
| 163 | 
            -
                             | 
| 164 | 
            -
             | 
| 165 | 
            -
             | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 168 | 
            -
                             | 
| 169 | 
            -
             | 
| 170 | 
            -
             | 
| 171 | 
            -
                                workflow_config. | 
| 172 | 
            -
             | 
| 173 | 
            -
             | 
| 174 | 
            -
             | 
| 175 | 
            -
             | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 178 | 
            -
             | 
| 179 | 
            -
             | 
| 180 | 
            -
             | 
| 181 | 
            -
             | 
| 182 | 
            -
             | 
| 183 | 
            -
             | 
| 184 | 
            -
             | 
| 185 | 
            -
             | 
| 186 | 
            -
                         | 
| 187 | 
            -
             | 
| 188 | 
            -
                             | 
| 189 | 
            -
             | 
| 190 | 
            -
             | 
| 191 | 
            -
             | 
| 192 | 
            -
             | 
| 193 | 
            -
                             | 
| 194 | 
            -
             | 
| 195 | 
            -
                                 | 
| 196 | 
            -
             | 
| 197 | 
            -
             | 
| 198 | 
            -
             | 
| 199 | 
            -
             | 
| 200 | 
            -
             | 
| 201 | 
            -
             | 
| 202 | 
            -
             | 
| 203 | 
            -
                                 | 
| 204 | 
            -
             | 
| 205 | 
            -
             | 
| 206 | 
            -
             | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
                             | 
| 210 | 
            -
                                 | 
| 211 | 
            -
                                 | 
| 212 | 
            -
             | 
| 213 | 
            -
             | 
| 214 | 
            -
             | 
| 215 | 
            -
             | 
| 216 | 
            -
             | 
| 217 | 
            -
             | 
| 218 | 
            -
                                 | 
| 219 | 
            -
                                 | 
| 154 | 
            +
                try:
         | 
| 155 | 
            +
                    with zipfile.ZipFile(zip_buffer) as zip_file:
         | 
| 156 | 
            +
                        if METADATA_FILE_NAME in zip_file.namelist():
         | 
| 157 | 
            +
                            metadata_json: Optional[dict] = None
         | 
| 158 | 
            +
                            with zip_file.open(METADATA_FILE_NAME) as source:
         | 
| 159 | 
            +
                                metadata_json = json.load(source)
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                            pull_contents_metadata = PullContentsMetadata.model_validate(metadata_json)
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                            if pull_contents_metadata.runner_config:
         | 
| 164 | 
            +
                                workflow_config.container_image_name = pull_contents_metadata.runner_config.container_image_name
         | 
| 165 | 
            +
                                workflow_config.container_image_tag = pull_contents_metadata.runner_config.container_image_tag
         | 
| 166 | 
            +
                                if workflow_config.container_image_name and not workflow_config.container_image_tag:
         | 
| 167 | 
            +
                                    workflow_config.container_image_tag = "latest"
         | 
| 168 | 
            +
                            if not workflow_config.module and workflow_deployment and pull_contents_metadata.deployment_name:
         | 
| 169 | 
            +
                                workflow_config.module = snake_case(pull_contents_metadata.deployment_name)
         | 
| 170 | 
            +
                            if not workflow_config.module and pull_contents_metadata.label:
         | 
| 171 | 
            +
                                workflow_config.module = snake_case(pull_contents_metadata.label)
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                        if not workflow_config.module:
         | 
| 174 | 
            +
                            raise ValueError(f"Failed to resolve a module name for Workflow {pk}")
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                        # Use target_directory if provided, otherwise use current working directory
         | 
| 177 | 
            +
                        base_dir = os.path.join(os.getcwd(), target_directory) if target_directory else os.getcwd()
         | 
| 178 | 
            +
                        target_dir = os.path.join(base_dir, *workflow_config.module.split("."))
         | 
| 179 | 
            +
                        workflow_config.target_directory = target_dir if target_directory else None
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                        # Delete files in target_dir that aren't in the zip file
         | 
| 182 | 
            +
                        if os.path.exists(target_dir):
         | 
| 183 | 
            +
                            ignore_patterns = (
         | 
| 184 | 
            +
                                workflow_config.ignore
         | 
| 185 | 
            +
                                if isinstance(workflow_config.ignore, list)
         | 
| 186 | 
            +
                                else [workflow_config.ignore] if isinstance(workflow_config.ignore, str) else []
         | 
| 187 | 
            +
                            )
         | 
| 188 | 
            +
                            existing_files = []
         | 
| 189 | 
            +
                            for root, _, files in os.walk(target_dir):
         | 
| 190 | 
            +
                                for file in files:
         | 
| 191 | 
            +
                                    rel_path = os.path.relpath(os.path.join(root, file), target_dir)
         | 
| 192 | 
            +
                                    existing_files.append(rel_path)
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                            for file in existing_files:
         | 
| 195 | 
            +
                                if any(Path(file).match(ignore_pattern) for ignore_pattern in ignore_patterns):
         | 
| 196 | 
            +
                                    continue
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                                if file not in zip_file.namelist():
         | 
| 199 | 
            +
                                    file_path = os.path.join(target_dir, file)
         | 
| 200 | 
            +
                                    logger.info(f"Deleting {file_path}...")
         | 
| 201 | 
            +
                                    os.remove(file_path)
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                        for file_name in zip_file.namelist():
         | 
| 204 | 
            +
                            with zip_file.open(file_name) as source:
         | 
| 205 | 
            +
                                content = source.read().decode("utf-8")
         | 
| 206 | 
            +
                                if file_name == ERROR_LOG_FILE_NAME:
         | 
| 207 | 
            +
                                    error_content = content
         | 
| 208 | 
            +
                                    continue
         | 
| 209 | 
            +
                                if file_name == METADATA_FILE_NAME:
         | 
| 210 | 
            +
                                    continue
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                                target_file = os.path.join(target_dir, file_name)
         | 
| 213 | 
            +
                                os.makedirs(os.path.dirname(target_file), exist_ok=True)
         | 
| 214 | 
            +
                                with open(target_file, "w") as target:
         | 
| 215 | 
            +
                                    logger.info(f"Writing to {target_file}...")
         | 
| 216 | 
            +
                                    target.write(content)
         | 
| 217 | 
            +
                except zipfile.BadZipFile:
         | 
| 218 | 
            +
                    raise Exception(
         | 
| 219 | 
            +
                        "The API we tried to pull from returned an invalid zip file. Please make sure your `VELLUM_API_URL` environment variable is set correctly."  # noqa: E501
         | 
| 220 | 
            +
                    )
         | 
| 220 221 |  | 
| 221 222 | 
             
                if include_json:
         | 
| 222 223 | 
             
                    logger.warning(
         | 
    
        vellum_cli/tests/test_pull.py
    CHANGED
    
    | @@ -338,7 +338,16 @@ def test_pull__workflow_deployment_with_no_config(vellum_client): | |
| 338 338 | 
             
                workflow_deployment = "my-deployment"
         | 
| 339 339 |  | 
| 340 340 | 
             
                # AND the workflow pull API call returns a zip file
         | 
| 341 | 
            -
                vellum_client.workflows.pull.return_value = iter( | 
| 341 | 
            +
                vellum_client.workflows.pull.return_value = iter(
         | 
| 342 | 
            +
                    [
         | 
| 343 | 
            +
                        _zip_file_map(
         | 
| 344 | 
            +
                            {
         | 
| 345 | 
            +
                                "workflow.py": "print('hello')",
         | 
| 346 | 
            +
                                "metadata.json": json.dumps({"deployment_name": workflow_deployment, "label": "Some Label"}),
         | 
| 347 | 
            +
                            }
         | 
| 348 | 
            +
                        )
         | 
| 349 | 
            +
                    ]
         | 
| 350 | 
            +
                )
         | 
| 342 351 |  | 
| 343 352 | 
             
                # AND we are currently in a new directory
         | 
| 344 353 | 
             
                current_dir = os.getcwd()
         | 
| @@ -822,3 +831,65 @@ def test_pull__multiple_instances_of_same_module__keep_when_pulling_another_modu | |
| 822 831 | 
             
                with open(lock_json) as f:
         | 
| 823 832 | 
             
                    lock_data = json.load(f)
         | 
| 824 833 | 
             
                    assert len(lock_data["workflows"]) == 3
         | 
| 834 | 
            +
             | 
| 835 | 
            +
             | 
| 836 | 
            +
            def test_pull__module_name_from_deployment_name(vellum_client):
         | 
| 837 | 
            +
                # GIVEN a workflow deployment
         | 
| 838 | 
            +
                workflow_deployment = "test-workflow-deployment-id"
         | 
| 839 | 
            +
             | 
| 840 | 
            +
                # AND the workflow pull API call returns a zip file with metadata containing a deployment_name
         | 
| 841 | 
            +
                deployment_name = "Test Deployment"
         | 
| 842 | 
            +
                vellum_client.workflows.pull.return_value = iter(
         | 
| 843 | 
            +
                    [
         | 
| 844 | 
            +
                        _zip_file_map(
         | 
| 845 | 
            +
                            {
         | 
| 846 | 
            +
                                "workflow.py": "print('hello')",
         | 
| 847 | 
            +
                                "metadata.json": json.dumps({"deployment_name": deployment_name, "label": "Some Label"}),
         | 
| 848 | 
            +
                            }
         | 
| 849 | 
            +
                        )
         | 
| 850 | 
            +
                    ]
         | 
| 851 | 
            +
                )
         | 
| 852 | 
            +
             | 
| 853 | 
            +
                # AND we are currently in a new directory
         | 
| 854 | 
            +
                current_dir = os.getcwd()
         | 
| 855 | 
            +
                temp_dir = tempfile.mkdtemp()
         | 
| 856 | 
            +
                os.chdir(temp_dir)
         | 
| 857 | 
            +
             | 
| 858 | 
            +
                # WHEN the user runs the pull command with the workflow deployment
         | 
| 859 | 
            +
                runner = CliRunner()
         | 
| 860 | 
            +
                result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", workflow_deployment])
         | 
| 861 | 
            +
                os.chdir(current_dir)
         | 
| 862 | 
            +
             | 
| 863 | 
            +
                # THEN the command returns successfully
         | 
| 864 | 
            +
                assert result.exit_code == 0
         | 
| 865 | 
            +
             | 
| 866 | 
            +
                # AND the module name is derived from the deployment_name in metadata.json (snake_cased)
         | 
| 867 | 
            +
                vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
         | 
| 868 | 
            +
                assert os.path.exists(vellum_lock_json)
         | 
| 869 | 
            +
                with open(vellum_lock_json) as f:
         | 
| 870 | 
            +
                    lock_data = json.loads(f.read())
         | 
| 871 | 
            +
                    assert lock_data["workflows"][0]["module"] == "test_deployment"
         | 
| 872 | 
            +
             | 
| 873 | 
            +
                # AND the workflow.py file is written to the module directory with the correct name
         | 
| 874 | 
            +
                workflow_py = os.path.join(temp_dir, "test_deployment", "workflow.py")
         | 
| 875 | 
            +
                assert os.path.exists(workflow_py)
         | 
| 876 | 
            +
                with open(workflow_py) as f:
         | 
| 877 | 
            +
                    assert f.read() == "print('hello')"
         | 
| 878 | 
            +
             | 
| 879 | 
            +
             | 
| 880 | 
            +
            def test_pull__invalid_zip_file(vellum_client):
         | 
| 881 | 
            +
                workflow_deployment = "test-workflow-deployment-id"
         | 
| 882 | 
            +
             | 
| 883 | 
            +
                # GIVEN a workflow pull API call returns an invalid zip file
         | 
| 884 | 
            +
                vellum_client.workflows.pull.return_value = iter([b"invalid"])  # Return bytes instead of string
         | 
| 885 | 
            +
             | 
| 886 | 
            +
                # WHEN the user runs the pull command
         | 
| 887 | 
            +
                runner = CliRunner()
         | 
| 888 | 
            +
                result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", workflow_deployment])
         | 
| 889 | 
            +
             | 
| 890 | 
            +
                # THEN the command returns an error
         | 
| 891 | 
            +
                assert result.exit_code == 1
         | 
| 892 | 
            +
                assert (
         | 
| 893 | 
            +
                    str(result.exception)
         | 
| 894 | 
            +
                    == "The API we tried to pull from returned an invalid zip file. Please make sure your `VELLUM_API_URL` environment variable is set correctly."  # noqa: E501
         | 
| 895 | 
            +
                )
         | 
| @@ -1,4 +1,3 @@ | |
| 1 | 
            -
            import pytest
         | 
| 2 1 | 
             
            import sys
         | 
| 3 2 | 
             
            from uuid import uuid4
         | 
| 4 3 | 
             
            from typing import Type, cast
         | 
| @@ -8,14 +7,10 @@ from vellum.client.types.code_executor_response import CodeExecutorResponse | |
| 8 7 | 
             
            from vellum.client.types.number_vellum_value import NumberVellumValue
         | 
| 9 8 | 
             
            from vellum.workflows import BaseWorkflow
         | 
| 10 9 | 
             
            from vellum.workflows.nodes import BaseNode
         | 
| 10 | 
            +
            from vellum.workflows.state.context import WorkflowContext
         | 
| 11 11 | 
             
            from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
         | 
| 12 12 |  | 
| 13 13 |  | 
| 14 | 
            -
            @pytest.fixture
         | 
| 15 | 
            -
            def mock_open(mocker):
         | 
| 16 | 
            -
                return mocker.patch("vellum.workflows.nodes.displayable.code_execution_node.utils.open")
         | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 14 | 
             
            def test_load_workflow_event_display_context():
         | 
| 20 15 | 
             
                # DEPRECATED: Use `vellum.workflows.events.workflow.WorkflowEventDisplayContext` instead. Will be removed in 0.15.0
         | 
| 21 16 | 
             
                from vellum_ee.workflows.display.types import WorkflowEventDisplayContext
         | 
| @@ -80,7 +75,6 @@ class StartNode(BaseNode): | |
| 80 75 |  | 
| 81 76 |  | 
| 82 77 | 
             
            def test_load_from_module__ts_code_in_file_loader(
         | 
| 83 | 
            -
                mock_open,
         | 
| 84 78 | 
             
                vellum_client,
         | 
| 85 79 | 
             
            ):
         | 
| 86 80 | 
             
                # GIVEN typescript code
         | 
| @@ -91,19 +85,22 @@ def test_load_from_module__ts_code_in_file_loader( | |
| 91 85 | 
             
                # AND a workflow module with only a code execution node
         | 
| 92 86 | 
             
                files = {
         | 
| 93 87 | 
             
                    "__init__.py": "",
         | 
| 94 | 
            -
                    "workflow.py": """ | 
| 88 | 
            +
                    "workflow.py": """
         | 
| 95 89 | 
             
            from vellum.workflows import BaseWorkflow
         | 
| 96 90 | 
             
            from .nodes.code_execution_node import CodeExecutionNode
         | 
| 97 91 |  | 
| 98 92 | 
             
            class Workflow(BaseWorkflow):
         | 
| 99 93 | 
             
                graph = CodeExecutionNode
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                class Outputs(BaseWorkflow.Outputs):
         | 
| 96 | 
            +
                   final_output = CodeExecutionNode.Outputs.result
         | 
| 100 97 | 
             
            """,
         | 
| 101 | 
            -
                    "nodes/__init__.py": """ | 
| 98 | 
            +
                    "nodes/__init__.py": """
         | 
| 102 99 | 
             
            from .code_execution_node import CodeExecutionNode
         | 
| 103 100 |  | 
| 104 101 | 
             
            __all__ = ["CodeExecutionNode"]
         | 
| 105 102 | 
             
            """,
         | 
| 106 | 
            -
                    "nodes/code_execution_node.py": """ | 
| 103 | 
            +
                    "nodes/code_execution_node/__init__.py": """
         | 
| 107 104 | 
             
            from typing import Any
         | 
| 108 105 |  | 
| 109 106 | 
             
            from vellum.workflows.nodes.displayable import CodeExecutionNode as BaseCodeExecutionNode
         | 
| @@ -121,10 +118,8 @@ class CodeExecutionNode(BaseCodeExecutionNode[BaseState, int]): | |
| 121 118 | 
             
                namespace = str(uuid4())
         | 
| 122 119 |  | 
| 123 120 | 
             
                # AND the virtual file loader is registered
         | 
| 124 | 
            -
                 | 
| 125 | 
            -
             | 
| 126 | 
            -
                # AND the open function returns our file content
         | 
| 127 | 
            -
                mock_open.return_value.__enter__.return_value.read.return_value = ts_code
         | 
| 121 | 
            +
                finder = VirtualFileFinder(files, namespace)
         | 
| 122 | 
            +
                sys.meta_path.append(finder)
         | 
| 128 123 |  | 
| 129 124 | 
             
                # AND we know what the Code Execution Node will respond with
         | 
| 130 125 | 
             
                mock_code_execution = CodeExecutorResponse(
         | 
| @@ -135,16 +130,13 @@ class CodeExecutionNode(BaseCodeExecutionNode[BaseState, int]): | |
| 135 130 |  | 
| 136 131 | 
             
                # WHEN the workflow is loaded
         | 
| 137 132 | 
             
                Workflow = BaseWorkflow.load_from_module(namespace)
         | 
| 138 | 
            -
                workflow = Workflow()
         | 
| 133 | 
            +
                workflow = Workflow(context=WorkflowContext(generated_files=files))
         | 
| 139 134 |  | 
| 140 135 | 
             
                # THEN the workflow is successfully initialized
         | 
| 141 136 | 
             
                assert workflow
         | 
| 142 137 |  | 
| 143 138 | 
             
                event = workflow.run()
         | 
| 144 | 
            -
                assert event.name == "workflow.execution.fulfilled" | 
| 145 | 
            -
             | 
| 146 | 
            -
                # AND we pass in the correct file path to the open function
         | 
| 147 | 
            -
                assert mock_open.call_args[0][0] == f"{namespace}/nodes/./script.ts"
         | 
| 139 | 
            +
                assert event.name == "workflow.execution.fulfilled"
         | 
| 148 140 |  | 
| 149 | 
            -
                # AND we  | 
| 150 | 
            -
                assert  | 
| 141 | 
            +
                # AND we get the code execution result
         | 
| 142 | 
            +
                assert event.body.outputs == {"final_output": 5.0}
         | 
| 
            File without changes
         | 
| 
            File without changes
         | 
| 
            File without changes
         |