zenml-nightly 0.70.0.dev20241122__py3-none-any.whl → 0.70.0.dev20241201__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.
- zenml/VERSION +1 -1
- zenml/artifact_stores/base_artifact_store.py +2 -2
- zenml/artifacts/artifact_config.py +7 -1
- zenml/artifacts/utils.py +56 -31
- zenml/cli/__init__.py +18 -0
- zenml/cli/base.py +4 -4
- zenml/cli/login.py +26 -0
- zenml/cli/pipeline.py +80 -0
- zenml/cli/server.py +1 -1
- zenml/cli/service_connectors.py +3 -3
- zenml/cli/stack.py +0 -3
- zenml/cli/stack_components.py +0 -1
- zenml/cli/utils.py +0 -5
- zenml/client.py +8 -18
- zenml/config/compiler.py +12 -3
- zenml/config/pipeline_configurations.py +20 -0
- zenml/config/pipeline_run_configuration.py +1 -0
- zenml/config/step_configurations.py +21 -0
- zenml/constants.py +1 -0
- zenml/enums.py +1 -0
- zenml/image_builders/local_image_builder.py +13 -3
- zenml/integrations/__init__.py +1 -0
- zenml/integrations/aws/orchestrators/sagemaker_orchestrator.py +14 -6
- zenml/integrations/constants.py +1 -0
- zenml/integrations/feast/__init__.py +1 -1
- zenml/integrations/feast/feature_stores/feast_feature_store.py +13 -9
- zenml/integrations/kubernetes/orchestrators/kube_utils.py +54 -9
- zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator.py +65 -3
- zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator_entrypoint.py +14 -2
- zenml/integrations/kubernetes/orchestrators/manifest_utils.py +6 -5
- zenml/integrations/kubernetes/service_connectors/kubernetes_service_connector.py +2 -1
- zenml/integrations/kubernetes/step_operators/kubernetes_step_operator.py +3 -1
- zenml/integrations/modal/__init__.py +46 -0
- zenml/integrations/modal/flavors/__init__.py +26 -0
- zenml/integrations/modal/flavors/modal_step_operator_flavor.py +125 -0
- zenml/integrations/modal/step_operators/__init__.py +22 -0
- zenml/integrations/modal/step_operators/modal_step_operator.py +242 -0
- zenml/io/filesystem.py +2 -2
- zenml/io/local_filesystem.py +3 -3
- zenml/materializers/built_in_materializer.py +18 -1
- zenml/materializers/structured_string_materializer.py +8 -3
- zenml/model/model.py +11 -85
- zenml/model/utils.py +18 -16
- zenml/models/__init__.py +12 -1
- zenml/models/v2/core/artifact_version.py +6 -3
- zenml/models/v2/core/component.py +0 -22
- zenml/models/v2/core/model_version.py +6 -3
- zenml/models/v2/core/pipeline_run.py +19 -3
- zenml/models/v2/core/run_metadata.py +30 -9
- zenml/models/v2/core/service_connector.py +4 -0
- zenml/models/v2/core/step_run.py +6 -4
- zenml/models/v2/misc/run_metadata.py +38 -0
- zenml/models/v2/misc/server_models.py +23 -0
- zenml/orchestrators/input_utils.py +19 -6
- zenml/orchestrators/publish_utils.py +12 -5
- zenml/orchestrators/step_launcher.py +7 -3
- zenml/orchestrators/step_run_utils.py +26 -9
- zenml/orchestrators/step_runner.py +40 -3
- zenml/orchestrators/utils.py +24 -23
- zenml/pipelines/pipeline_decorator.py +4 -0
- zenml/pipelines/pipeline_definition.py +26 -8
- zenml/pipelines/run_utils.py +9 -5
- zenml/steps/base_step.py +11 -1
- zenml/steps/entrypoint_function_utils.py +7 -3
- zenml/steps/step_decorator.py +4 -0
- zenml/steps/utils.py +23 -7
- zenml/types.py +4 -0
- zenml/utils/metadata_utils.py +186 -153
- zenml/utils/string_utils.py +41 -16
- zenml/utils/visualization_utils.py +4 -1
- zenml/zen_server/cloud_utils.py +3 -1
- zenml/zen_server/deploy/helm/templates/_environment.tpl +117 -0
- zenml/zen_server/deploy/helm/templates/server-db-job.yaml +3 -14
- zenml/zen_server/deploy/helm/templates/server-deployment.yaml +16 -4
- zenml/zen_server/deploy/helm/templates/server-secret.yaml +2 -17
- zenml/zen_server/rbac/endpoint_utils.py +6 -4
- zenml/zen_server/rbac/models.py +3 -2
- zenml/zen_server/rbac/utils.py +4 -7
- zenml/zen_server/routers/server_endpoints.py +47 -0
- zenml/zen_server/routers/users_endpoints.py +35 -37
- zenml/zen_server/routers/workspaces_endpoints.py +44 -55
- zenml/zen_server/template_execution/utils.py +1 -0
- zenml/zen_server/zen_server_api.py +45 -6
- zenml/zen_stores/migrations/utils.py +40 -24
- zenml/zen_stores/migrations/versions/b73bc71f1106_remove_component_spec_path.py +36 -0
- zenml/zen_stores/migrations/versions/cc269488e5a9_separate_run_metadata.py +135 -0
- zenml/zen_stores/migrations/versions/ec6307720f92_simplify_model_version_links.py +7 -6
- zenml/zen_stores/rest_zen_store.py +38 -1
- zenml/zen_stores/schemas/__init__.py +5 -1
- zenml/zen_stores/schemas/artifact_schemas.py +12 -11
- zenml/zen_stores/schemas/component_schemas.py +0 -3
- zenml/zen_stores/schemas/model_schemas.py +13 -11
- zenml/zen_stores/schemas/pipeline_run_schemas.py +44 -16
- zenml/zen_stores/schemas/run_metadata_schemas.py +66 -31
- zenml/zen_stores/schemas/step_run_schemas.py +32 -12
- zenml/zen_stores/schemas/utils.py +47 -3
- zenml/zen_stores/sql_zen_store.py +130 -34
- {zenml_nightly-0.70.0.dev20241122.dist-info → zenml_nightly-0.70.0.dev20241201.dist-info}/METADATA +1 -1
- {zenml_nightly-0.70.0.dev20241122.dist-info → zenml_nightly-0.70.0.dev20241201.dist-info}/RECORD +102 -95
- zenml/utils/cloud_utils.py +0 -40
- {zenml_nightly-0.70.0.dev20241122.dist-info → zenml_nightly-0.70.0.dev20241201.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.70.0.dev20241122.dist-info → zenml_nightly-0.70.0.dev20241201.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.70.0.dev20241122.dist-info → zenml_nightly-0.70.0.dev20241201.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,125 @@
|
|
1
|
+
# Copyright (c) ZenML GmbH 2024. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at:
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
12
|
+
# or implied. See the License for the specific language governing
|
13
|
+
# permissions and limitations under the License.
|
14
|
+
"""Modal step operator flavor."""
|
15
|
+
|
16
|
+
from typing import TYPE_CHECKING, Optional, Type
|
17
|
+
|
18
|
+
from zenml.config.base_settings import BaseSettings
|
19
|
+
from zenml.integrations.modal import MODAL_STEP_OPERATOR_FLAVOR
|
20
|
+
from zenml.step_operators import BaseStepOperatorConfig, BaseStepOperatorFlavor
|
21
|
+
|
22
|
+
if TYPE_CHECKING:
|
23
|
+
from zenml.integrations.modal.step_operators import ModalStepOperator
|
24
|
+
|
25
|
+
|
26
|
+
class ModalStepOperatorSettings(BaseSettings):
|
27
|
+
"""Settings for the Modal step operator.
|
28
|
+
|
29
|
+
Specifying the region and cloud provider is only available for Enterprise
|
30
|
+
and Team plan customers.
|
31
|
+
|
32
|
+
Certain combinations of settings are not available. It is suggested to err
|
33
|
+
on the side of looser settings rather than more restrictive ones to avoid
|
34
|
+
pipeline execution failures. In the case of failures, however, Modal
|
35
|
+
provides detailed error messages that can help identify what is
|
36
|
+
incompatible. See more in the Modal docs at https://modal.com/docs/guide/region-selection.
|
37
|
+
|
38
|
+
Attributes:
|
39
|
+
gpu: The type of GPU to use for the step execution.
|
40
|
+
region: The region to use for the step execution.
|
41
|
+
cloud: The cloud provider to use for the step execution.
|
42
|
+
"""
|
43
|
+
|
44
|
+
gpu: Optional[str] = None
|
45
|
+
region: Optional[str] = None
|
46
|
+
cloud: Optional[str] = None
|
47
|
+
|
48
|
+
|
49
|
+
class ModalStepOperatorConfig(
|
50
|
+
BaseStepOperatorConfig, ModalStepOperatorSettings
|
51
|
+
):
|
52
|
+
"""Configuration for the Modal step operator."""
|
53
|
+
|
54
|
+
@property
|
55
|
+
def is_remote(self) -> bool:
|
56
|
+
"""Checks if this stack component is running remotely.
|
57
|
+
|
58
|
+
This designation is used to determine if the stack component can be
|
59
|
+
used with a local ZenML database or if it requires a remote ZenML
|
60
|
+
server.
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
True if this config is for a remote component, False otherwise.
|
64
|
+
"""
|
65
|
+
return True
|
66
|
+
|
67
|
+
|
68
|
+
class ModalStepOperatorFlavor(BaseStepOperatorFlavor):
|
69
|
+
"""Modal step operator flavor."""
|
70
|
+
|
71
|
+
@property
|
72
|
+
def name(self) -> str:
|
73
|
+
"""Name of the flavor.
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
The name of the flavor.
|
77
|
+
"""
|
78
|
+
return MODAL_STEP_OPERATOR_FLAVOR
|
79
|
+
|
80
|
+
@property
|
81
|
+
def docs_url(self) -> Optional[str]:
|
82
|
+
"""A url to point at docs explaining this flavor.
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
A flavor docs url.
|
86
|
+
"""
|
87
|
+
return self.generate_default_docs_url()
|
88
|
+
|
89
|
+
@property
|
90
|
+
def sdk_docs_url(self) -> Optional[str]:
|
91
|
+
"""A url to point at SDK docs explaining this flavor.
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
A flavor SDK docs url.
|
95
|
+
"""
|
96
|
+
return self.generate_default_sdk_docs_url()
|
97
|
+
|
98
|
+
@property
|
99
|
+
def logo_url(self) -> str:
|
100
|
+
"""A url to represent the flavor in the dashboard.
|
101
|
+
|
102
|
+
Returns:
|
103
|
+
The flavor logo.
|
104
|
+
"""
|
105
|
+
return "https://public-flavor-logos.s3.eu-central-1.amazonaws.com/step_operator/modal.png"
|
106
|
+
|
107
|
+
@property
|
108
|
+
def config_class(self) -> Type[ModalStepOperatorConfig]:
|
109
|
+
"""Returns `ModalStepOperatorConfig` config class.
|
110
|
+
|
111
|
+
Returns:
|
112
|
+
The config class.
|
113
|
+
"""
|
114
|
+
return ModalStepOperatorConfig
|
115
|
+
|
116
|
+
@property
|
117
|
+
def implementation_class(self) -> Type["ModalStepOperator"]:
|
118
|
+
"""Implementation class for this flavor.
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
The implementation class.
|
122
|
+
"""
|
123
|
+
from zenml.integrations.modal.step_operators import ModalStepOperator
|
124
|
+
|
125
|
+
return ModalStepOperator
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Copyright (c) ZenML GmbH 2024. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at:
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
12
|
+
# or implied. See the License for the specific language governing
|
13
|
+
# permissions and limitations under the License.
|
14
|
+
"""Modal step operator."""
|
15
|
+
|
16
|
+
from zenml.integrations.modal.step_operators.modal_step_operator import (
|
17
|
+
ModalStepOperator,
|
18
|
+
)
|
19
|
+
|
20
|
+
__all__ = [
|
21
|
+
"ModalStepOperator",
|
22
|
+
]
|
@@ -0,0 +1,242 @@
|
|
1
|
+
# Copyright (c) ZenML GmbH 2024. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at:
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
12
|
+
# or implied. See the License for the specific language governing
|
13
|
+
# permissions and limitations under the License.
|
14
|
+
"""Modal step operator implementation."""
|
15
|
+
|
16
|
+
import asyncio
|
17
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type, cast
|
18
|
+
|
19
|
+
import modal
|
20
|
+
from modal_proto import api_pb2
|
21
|
+
|
22
|
+
from zenml.client import Client
|
23
|
+
from zenml.config.build_configuration import BuildConfiguration
|
24
|
+
from zenml.config.resource_settings import ByteUnit, ResourceSettings
|
25
|
+
from zenml.enums import StackComponentType
|
26
|
+
from zenml.integrations.modal.flavors import (
|
27
|
+
ModalStepOperatorConfig,
|
28
|
+
ModalStepOperatorSettings,
|
29
|
+
)
|
30
|
+
from zenml.logger import get_logger
|
31
|
+
from zenml.stack import Stack, StackValidator
|
32
|
+
from zenml.step_operators import BaseStepOperator
|
33
|
+
|
34
|
+
if TYPE_CHECKING:
|
35
|
+
from zenml.config.base_settings import BaseSettings
|
36
|
+
from zenml.config.step_run_info import StepRunInfo
|
37
|
+
from zenml.models import PipelineDeploymentBase
|
38
|
+
|
39
|
+
logger = get_logger(__name__)
|
40
|
+
|
41
|
+
MODAL_STEP_OPERATOR_DOCKER_IMAGE_KEY = "modal_step_operator"
|
42
|
+
|
43
|
+
|
44
|
+
def get_gpu_values(
|
45
|
+
settings: ModalStepOperatorSettings, resource_settings: ResourceSettings
|
46
|
+
) -> Optional[str]:
|
47
|
+
"""Get the GPU values for the Modal step operator.
|
48
|
+
|
49
|
+
Args:
|
50
|
+
settings: The Modal step operator settings.
|
51
|
+
resource_settings: The resource settings.
|
52
|
+
|
53
|
+
Returns:
|
54
|
+
The GPU string if a count is specified, otherwise the GPU type.
|
55
|
+
"""
|
56
|
+
if not settings.gpu:
|
57
|
+
return None
|
58
|
+
gpu_count = resource_settings.gpu_count
|
59
|
+
return f"{settings.gpu}:{gpu_count}" if gpu_count else settings.gpu
|
60
|
+
|
61
|
+
|
62
|
+
class ModalStepOperator(BaseStepOperator):
|
63
|
+
"""Step operator to run a step on Modal.
|
64
|
+
|
65
|
+
This class defines code that can set up a Modal environment and run
|
66
|
+
functions in it.
|
67
|
+
"""
|
68
|
+
|
69
|
+
@property
|
70
|
+
def config(self) -> ModalStepOperatorConfig:
|
71
|
+
"""Get the Modal step operator configuration.
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
The Modal step operator configuration.
|
75
|
+
"""
|
76
|
+
return cast(ModalStepOperatorConfig, self._config)
|
77
|
+
|
78
|
+
@property
|
79
|
+
def settings_class(self) -> Optional[Type["BaseSettings"]]:
|
80
|
+
"""Get the settings class for the Modal step operator.
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
The Modal step operator settings class.
|
84
|
+
"""
|
85
|
+
return ModalStepOperatorSettings
|
86
|
+
|
87
|
+
@property
|
88
|
+
def validator(self) -> Optional[StackValidator]:
|
89
|
+
"""Get the stack validator for the Modal step operator.
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
The stack validator.
|
93
|
+
"""
|
94
|
+
|
95
|
+
def _validate_remote_components(stack: "Stack") -> Tuple[bool, str]:
|
96
|
+
if stack.artifact_store.config.is_local:
|
97
|
+
return False, (
|
98
|
+
"The Modal step operator runs code remotely and "
|
99
|
+
"needs to write files into the artifact store, but the "
|
100
|
+
f"artifact store `{stack.artifact_store.name}` of the "
|
101
|
+
"active stack is local. Please ensure that your stack "
|
102
|
+
"contains a remote artifact store when using the Modal "
|
103
|
+
"step operator."
|
104
|
+
)
|
105
|
+
|
106
|
+
container_registry = stack.container_registry
|
107
|
+
assert container_registry is not None
|
108
|
+
|
109
|
+
if container_registry.config.is_local:
|
110
|
+
return False, (
|
111
|
+
"The Modal step operator runs code remotely and "
|
112
|
+
"needs to push/pull Docker images, but the "
|
113
|
+
f"container registry `{container_registry.name}` of the "
|
114
|
+
"active stack is local. Please ensure that your stack "
|
115
|
+
"contains a remote container registry when using the "
|
116
|
+
"Modal step operator."
|
117
|
+
)
|
118
|
+
|
119
|
+
return True, ""
|
120
|
+
|
121
|
+
return StackValidator(
|
122
|
+
required_components={
|
123
|
+
StackComponentType.CONTAINER_REGISTRY,
|
124
|
+
StackComponentType.IMAGE_BUILDER,
|
125
|
+
},
|
126
|
+
custom_validation_function=_validate_remote_components,
|
127
|
+
)
|
128
|
+
|
129
|
+
def get_docker_builds(
|
130
|
+
self, deployment: "PipelineDeploymentBase"
|
131
|
+
) -> List["BuildConfiguration"]:
|
132
|
+
"""Get the Docker build configurations for the Modal step operator.
|
133
|
+
|
134
|
+
Args:
|
135
|
+
deployment: The pipeline deployment.
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
A list of Docker build configurations.
|
139
|
+
"""
|
140
|
+
builds = []
|
141
|
+
for step_name, step in deployment.step_configurations.items():
|
142
|
+
if step.config.step_operator == self.name:
|
143
|
+
build = BuildConfiguration(
|
144
|
+
key=MODAL_STEP_OPERATOR_DOCKER_IMAGE_KEY,
|
145
|
+
settings=step.config.docker_settings,
|
146
|
+
step_name=step_name,
|
147
|
+
)
|
148
|
+
builds.append(build)
|
149
|
+
|
150
|
+
return builds
|
151
|
+
|
152
|
+
def launch(
|
153
|
+
self,
|
154
|
+
info: "StepRunInfo",
|
155
|
+
entrypoint_command: List[str],
|
156
|
+
environment: Dict[str, str],
|
157
|
+
) -> None:
|
158
|
+
"""Launch a step run on Modal.
|
159
|
+
|
160
|
+
Args:
|
161
|
+
info: The step run information.
|
162
|
+
entrypoint_command: The entrypoint command for the step.
|
163
|
+
environment: The environment variables for the step.
|
164
|
+
|
165
|
+
Raises:
|
166
|
+
RuntimeError: If no Docker credentials are found for the container registry.
|
167
|
+
ValueError: If no container registry is found in the stack.
|
168
|
+
"""
|
169
|
+
settings = cast(ModalStepOperatorSettings, self.get_settings(info))
|
170
|
+
image_name = info.get_image(key=MODAL_STEP_OPERATOR_DOCKER_IMAGE_KEY)
|
171
|
+
zc = Client()
|
172
|
+
stack = zc.active_stack
|
173
|
+
|
174
|
+
if not stack.container_registry:
|
175
|
+
raise ValueError(
|
176
|
+
"No Container registry found in the stack. "
|
177
|
+
"Please add a container registry and ensure "
|
178
|
+
"it is correctly configured."
|
179
|
+
)
|
180
|
+
|
181
|
+
if docker_creds := stack.container_registry.credentials:
|
182
|
+
docker_username, docker_password = docker_creds
|
183
|
+
else:
|
184
|
+
raise RuntimeError(
|
185
|
+
"No Docker credentials found for the container registry."
|
186
|
+
)
|
187
|
+
|
188
|
+
my_secret = modal.secret._Secret.from_dict(
|
189
|
+
{
|
190
|
+
"REGISTRY_USERNAME": docker_username,
|
191
|
+
"REGISTRY_PASSWORD": docker_password,
|
192
|
+
}
|
193
|
+
)
|
194
|
+
|
195
|
+
spec = modal.image.DockerfileSpec(
|
196
|
+
commands=[f"FROM {image_name}"], context_files={}
|
197
|
+
)
|
198
|
+
|
199
|
+
zenml_image = modal.Image._from_args(
|
200
|
+
dockerfile_function=lambda *_, **__: spec,
|
201
|
+
force_build=False,
|
202
|
+
image_registry_config=modal.image._ImageRegistryConfig(
|
203
|
+
api_pb2.REGISTRY_AUTH_TYPE_STATIC_CREDS, my_secret
|
204
|
+
),
|
205
|
+
).env(environment)
|
206
|
+
|
207
|
+
resource_settings = info.config.resource_settings
|
208
|
+
gpu_values = get_gpu_values(settings, resource_settings)
|
209
|
+
|
210
|
+
app = modal.App(
|
211
|
+
f"zenml-{info.run_name}-{info.step_run_id}-{info.pipeline_step_name}"
|
212
|
+
)
|
213
|
+
|
214
|
+
async def run_sandbox() -> asyncio.Future[None]:
|
215
|
+
loop = asyncio.get_event_loop()
|
216
|
+
future = loop.create_future()
|
217
|
+
with modal.enable_output():
|
218
|
+
async with app.run():
|
219
|
+
memory_mb = resource_settings.get_memory(ByteUnit.MB)
|
220
|
+
memory_int = (
|
221
|
+
int(memory_mb) if memory_mb is not None else None
|
222
|
+
)
|
223
|
+
sb = await modal.Sandbox.create.aio(
|
224
|
+
"bash",
|
225
|
+
"-c",
|
226
|
+
" ".join(entrypoint_command),
|
227
|
+
image=zenml_image,
|
228
|
+
gpu=gpu_values,
|
229
|
+
cpu=resource_settings.cpu_count,
|
230
|
+
memory=memory_int,
|
231
|
+
cloud=settings.cloud,
|
232
|
+
region=settings.region,
|
233
|
+
app=app,
|
234
|
+
timeout=86400, # 24h, the max Modal allows
|
235
|
+
)
|
236
|
+
|
237
|
+
await sb.wait.aio()
|
238
|
+
|
239
|
+
future.set_result(None)
|
240
|
+
return future
|
241
|
+
|
242
|
+
asyncio.run(run_sandbox())
|
zenml/io/filesystem.py
CHANGED
@@ -54,11 +54,11 @@ class BaseFilesystem(ABC):
|
|
54
54
|
|
55
55
|
@staticmethod
|
56
56
|
@abstractmethod
|
57
|
-
def open(
|
57
|
+
def open(path: PathType, mode: str = "r") -> Any:
|
58
58
|
"""Opens a file.
|
59
59
|
|
60
60
|
Args:
|
61
|
-
|
61
|
+
path: The path to the file.
|
62
62
|
mode: The mode to open the file in.
|
63
63
|
|
64
64
|
Returns:
|
zenml/io/local_filesystem.py
CHANGED
@@ -55,18 +55,18 @@ class LocalFilesystem(BaseFilesystem):
|
|
55
55
|
SUPPORTED_SCHEMES: ClassVar[Set[str]] = {""}
|
56
56
|
|
57
57
|
@staticmethod
|
58
|
-
def open(
|
58
|
+
def open(path: PathType, mode: str = "r") -> Any:
|
59
59
|
"""Open a file at the given path.
|
60
60
|
|
61
61
|
Args:
|
62
|
-
|
62
|
+
path: The path to the file.
|
63
63
|
mode: The mode to open the file.
|
64
64
|
|
65
65
|
Returns:
|
66
66
|
Any: The file object.
|
67
67
|
"""
|
68
68
|
encoding = "utf-8" if "b" not in mode else None
|
69
|
-
return open(
|
69
|
+
return open(path, mode=mode, encoding=encoding)
|
70
70
|
|
71
71
|
@staticmethod
|
72
72
|
def copyfile(
|
@@ -28,7 +28,7 @@ from typing import (
|
|
28
28
|
)
|
29
29
|
|
30
30
|
from zenml.artifact_stores.base_artifact_store import BaseArtifactStore
|
31
|
-
from zenml.enums import ArtifactType
|
31
|
+
from zenml.enums import ArtifactType, VisualizationType
|
32
32
|
from zenml.logger import get_logger
|
33
33
|
from zenml.materializers.base_materializer import BaseMaterializer
|
34
34
|
from zenml.materializers.materializer_registry import materializer_registry
|
@@ -415,6 +415,23 @@ class BuiltInContainerMaterializer(BaseMaterializer):
|
|
415
415
|
self.artifact_store.rmtree(entry["path"])
|
416
416
|
raise e
|
417
417
|
|
418
|
+
# save dict type objects to JSON file with JSON visualization type
|
419
|
+
def save_visualizations(self, data: Any) -> Dict[str, "VisualizationType"]:
|
420
|
+
"""Save visualizations for the given data.
|
421
|
+
|
422
|
+
Args:
|
423
|
+
data: The data to save visualizations for.
|
424
|
+
|
425
|
+
Returns:
|
426
|
+
A dictionary of visualization URIs and their types.
|
427
|
+
"""
|
428
|
+
# dict/list type objects are always saved as JSON files
|
429
|
+
# doesn't work for non-serializable types as they
|
430
|
+
# are saved as list of lists in different files
|
431
|
+
if _is_serializable(data):
|
432
|
+
return {self.data_path: VisualizationType.JSON}
|
433
|
+
return {}
|
434
|
+
|
418
435
|
def extract_metadata(self, data: Any) -> Dict[str, "MetadataType"]:
|
419
436
|
"""Extract metadata from the given built-in container object.
|
420
437
|
|
@@ -19,22 +19,23 @@ from typing import Dict, Type, Union
|
|
19
19
|
from zenml.enums import ArtifactType, VisualizationType
|
20
20
|
from zenml.logger import get_logger
|
21
21
|
from zenml.materializers.base_materializer import BaseMaterializer
|
22
|
-
from zenml.types import CSVString, HTMLString, MarkdownString
|
22
|
+
from zenml.types import CSVString, HTMLString, JSONString, MarkdownString
|
23
23
|
|
24
24
|
logger = get_logger(__name__)
|
25
25
|
|
26
26
|
|
27
|
-
STRUCTURED_STRINGS = Union[CSVString, HTMLString, MarkdownString]
|
27
|
+
STRUCTURED_STRINGS = Union[CSVString, HTMLString, MarkdownString, JSONString]
|
28
28
|
|
29
29
|
HTML_FILENAME = "output.html"
|
30
30
|
MARKDOWN_FILENAME = "output.md"
|
31
31
|
CSV_FILENAME = "output.csv"
|
32
|
+
JSON_FILENAME = "output.json"
|
32
33
|
|
33
34
|
|
34
35
|
class StructuredStringMaterializer(BaseMaterializer):
|
35
36
|
"""Materializer for HTML or Markdown strings."""
|
36
37
|
|
37
|
-
ASSOCIATED_TYPES = (CSVString, HTMLString, MarkdownString)
|
38
|
+
ASSOCIATED_TYPES = (CSVString, HTMLString, MarkdownString, JSONString)
|
38
39
|
ASSOCIATED_ARTIFACT_TYPE = ArtifactType.DATA_ANALYSIS
|
39
40
|
|
40
41
|
def load(self, data_type: Type[STRUCTURED_STRINGS]) -> STRUCTURED_STRINGS:
|
@@ -94,6 +95,8 @@ class StructuredStringMaterializer(BaseMaterializer):
|
|
94
95
|
filename = HTML_FILENAME
|
95
96
|
elif issubclass(data_type, MarkdownString):
|
96
97
|
filename = MARKDOWN_FILENAME
|
98
|
+
elif issubclass(data_type, JSONString):
|
99
|
+
filename = JSON_FILENAME
|
97
100
|
else:
|
98
101
|
raise ValueError(
|
99
102
|
f"Data type {data_type} is not supported by this materializer."
|
@@ -120,6 +123,8 @@ class StructuredStringMaterializer(BaseMaterializer):
|
|
120
123
|
return VisualizationType.HTML
|
121
124
|
elif issubclass(data_type, MarkdownString):
|
122
125
|
return VisualizationType.MARKDOWN
|
126
|
+
elif issubclass(data_type, JSONString):
|
127
|
+
return VisualizationType.JSON
|
123
128
|
else:
|
124
129
|
raise ValueError(
|
125
130
|
f"Data type {data_type} is not supported by this materializer."
|
zenml/model/model.py
CHANGED
@@ -13,14 +13,12 @@
|
|
13
13
|
# permissions and limitations under the License.
|
14
14
|
"""Model user facing interface to pass into pipeline or step."""
|
15
15
|
|
16
|
-
import datetime
|
17
16
|
from typing import (
|
18
17
|
TYPE_CHECKING,
|
19
18
|
Any,
|
20
19
|
Dict,
|
21
20
|
List,
|
22
21
|
Optional,
|
23
|
-
Tuple,
|
24
22
|
Union,
|
25
23
|
)
|
26
24
|
from uuid import UUID
|
@@ -41,7 +39,6 @@ if TYPE_CHECKING:
|
|
41
39
|
ModelResponse,
|
42
40
|
ModelVersionResponse,
|
43
41
|
PipelineRunResponse,
|
44
|
-
StepRunResponse,
|
45
42
|
)
|
46
43
|
|
47
44
|
logger = get_logger(__name__)
|
@@ -60,7 +57,9 @@ class Model(BaseModel):
|
|
60
57
|
ethics: The ethical implications of the model.
|
61
58
|
tags: Tags associated with the model.
|
62
59
|
version: The version name, version number or stage is optional and points model context
|
63
|
-
to a specific version/stage. If skipped new version will be created.
|
60
|
+
to a specific version/stage. If skipped new version will be created. `version`
|
61
|
+
also supports placeholders: standard `{date}` and `{time}` and any custom placeholders
|
62
|
+
that are passed as substitutions in the pipeline or step decorators.
|
64
63
|
save_models_to_registry: Whether to save all ModelArtifacts to Model Registry,
|
65
64
|
if available in active stack.
|
66
65
|
"""
|
@@ -337,12 +336,16 @@ class Model(BaseModel):
|
|
337
336
|
metadata: The metadata to log.
|
338
337
|
"""
|
339
338
|
from zenml.client import Client
|
339
|
+
from zenml.models import RunMetadataResource
|
340
340
|
|
341
341
|
response = self._get_or_create_model_version()
|
342
342
|
Client().create_run_metadata(
|
343
343
|
metadata=metadata,
|
344
|
-
|
345
|
-
|
344
|
+
resources=[
|
345
|
+
RunMetadataResource(
|
346
|
+
id=response.id, type=MetadataResourceTypes.MODEL_VERSION
|
347
|
+
)
|
348
|
+
],
|
346
349
|
)
|
347
350
|
|
348
351
|
@property
|
@@ -537,6 +540,8 @@ class Model(BaseModel):
|
|
537
540
|
from zenml.models import ModelRequest
|
538
541
|
|
539
542
|
zenml_client = Client()
|
543
|
+
# backup logic, if the Model class is used directly from the code
|
544
|
+
self.name = format_name_template(self.name)
|
540
545
|
if self.model_version_id:
|
541
546
|
mv = zenml_client.get_model_version(
|
542
547
|
model_version_name_or_number_or_id=self.model_version_id,
|
@@ -743,85 +748,6 @@ class Model(BaseModel):
|
|
743
748
|
)
|
744
749
|
)
|
745
750
|
|
746
|
-
def _prepare_model_version_before_step_launch(
|
747
|
-
self,
|
748
|
-
pipeline_run: "PipelineRunResponse",
|
749
|
-
step_run: Optional["StepRunResponse"],
|
750
|
-
return_logs: bool,
|
751
|
-
) -> Tuple[str, "PipelineRunResponse", Optional["StepRunResponse"]]:
|
752
|
-
"""Prepares model version inside pipeline run.
|
753
|
-
|
754
|
-
Args:
|
755
|
-
pipeline_run: pipeline run
|
756
|
-
step_run: step run (passed only if model version is defined in a step explicitly)
|
757
|
-
return_logs: whether to return logs or not
|
758
|
-
|
759
|
-
Returns:
|
760
|
-
Logs related to the Dashboard URL to show later.
|
761
|
-
"""
|
762
|
-
from zenml.client import Client
|
763
|
-
from zenml.models import PipelineRunUpdate, StepRunUpdate
|
764
|
-
|
765
|
-
logs = ""
|
766
|
-
|
767
|
-
# copy Model instance to prevent corrupting configs of the
|
768
|
-
# subsequent runs, if they share the same config object
|
769
|
-
self_copy = self.model_copy()
|
770
|
-
|
771
|
-
# in case request is within the step and no self-configuration is provided
|
772
|
-
# try reuse what's in the pipeline run first
|
773
|
-
if step_run is None and pipeline_run.model_version is not None:
|
774
|
-
self_copy.version = pipeline_run.model_version.name
|
775
|
-
self_copy.model_version_id = pipeline_run.model_version.id
|
776
|
-
# otherwise try to fill the templated name, if needed
|
777
|
-
elif isinstance(self_copy.version, str):
|
778
|
-
if pipeline_run.start_time:
|
779
|
-
start_time = pipeline_run.start_time
|
780
|
-
else:
|
781
|
-
start_time = datetime.datetime.now(datetime.timezone.utc)
|
782
|
-
self_copy.version = format_name_template(
|
783
|
-
self_copy.version,
|
784
|
-
date=start_time.strftime("%Y_%m_%d"),
|
785
|
-
time=start_time.strftime("%H_%M_%S_%f"),
|
786
|
-
)
|
787
|
-
|
788
|
-
# if exact model not yet defined - try to get/create and update it
|
789
|
-
# back to the run accordingly
|
790
|
-
if self_copy.model_version_id is None:
|
791
|
-
model_version_response = self_copy._get_or_create_model_version()
|
792
|
-
|
793
|
-
client = Client()
|
794
|
-
# update the configured model version id in runs accordingly
|
795
|
-
if step_run:
|
796
|
-
step_run = client.zen_store.update_run_step(
|
797
|
-
step_run_id=step_run.id,
|
798
|
-
step_run_update=StepRunUpdate(
|
799
|
-
model_version_id=model_version_response.id
|
800
|
-
),
|
801
|
-
)
|
802
|
-
else:
|
803
|
-
pipeline_run = client.zen_store.update_run(
|
804
|
-
run_id=pipeline_run.id,
|
805
|
-
run_update=PipelineRunUpdate(
|
806
|
-
model_version_id=model_version_response.id
|
807
|
-
),
|
808
|
-
)
|
809
|
-
|
810
|
-
if return_logs:
|
811
|
-
from zenml.utils.cloud_utils import try_get_model_version_url
|
812
|
-
|
813
|
-
if logs_to_show := try_get_model_version_url(
|
814
|
-
model_version_response
|
815
|
-
):
|
816
|
-
logs = logs_to_show
|
817
|
-
else:
|
818
|
-
logs = (
|
819
|
-
"Models can be viewed in the dashboard using ZenML Pro. Sign up "
|
820
|
-
"for a free trial at https://www.zenml.io/pro/"
|
821
|
-
)
|
822
|
-
self.model_version_id = self_copy.model_version_id
|
823
|
-
return logs, pipeline_run, step_run
|
824
|
-
|
825
751
|
@property
|
826
752
|
def _lazy_version(self) -> Optional[str]:
|
827
753
|
"""Get version name for lazy loader.
|