zenml-nightly 0.70.0.dev20241127__py3-none-any.whl → 0.70.0.dev20241128__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 CHANGED
@@ -1 +1 @@
1
- 0.70.0.dev20241127
1
+ 0.70.0.dev20241128
@@ -48,6 +48,7 @@ from zenml.integrations.lightgbm import LightGBMIntegration # noqa
48
48
 
49
49
  # from zenml.integrations.llama_index import LlamaIndexIntegration # noqa
50
50
  from zenml.integrations.mlflow import MlflowIntegration # noqa
51
+ from zenml.integrations.modal import ModalIntegration # noqa
51
52
  from zenml.integrations.neptune import NeptuneIntegration # noqa
52
53
  from zenml.integrations.neural_prophet import NeuralProphetIntegration # noqa
53
54
  from zenml.integrations.numpy import NumpyIntegration # noqa
@@ -42,6 +42,7 @@ LANGCHAIN = "langchain"
42
42
  LIGHTGBM = "lightgbm"
43
43
  # LLAMA_INDEX = "llama_index"
44
44
  MLFLOW = "mlflow"
45
+ MODAL = "modal"
45
46
  NEPTUNE = "neptune"
46
47
  NEURAL_PROPHET = "neural_prophet"
47
48
  NUMPY = "numpy"
@@ -94,18 +94,62 @@ def load_kube_config(
94
94
  k8s_config.load_kube_config(context=context)
95
95
 
96
96
 
97
- def sanitize_pod_name(pod_name: str) -> str:
97
+ def calculate_max_pod_name_length_for_namespace(namespace: str) -> int:
98
+ """Calculate the max pod length for a certain namespace.
99
+
100
+ Args:
101
+ namespace: The namespace in which the pod will be created.
102
+
103
+ Returns:
104
+ The maximum pod name length.
105
+ """
106
+ # Kubernetes allows Pod names to have 253 characters. However, when
107
+ # creating a pod they try to create a log file which is called
108
+ # <NAMESPACE>_<POD_NAME>_<UUID>, which adds additional characters and
109
+ # runs into filesystem limitations for filename lengths (255). We therefore
110
+ # subtract the length of a UUID (36), the two underscores and the
111
+ # namespace length from the max filename length.
112
+ return 255 - 38 - len(namespace)
113
+
114
+
115
+ def sanitize_pod_name(pod_name: str, namespace: str) -> str:
98
116
  """Sanitize pod names so they conform to Kubernetes pod naming convention.
99
117
 
100
118
  Args:
101
119
  pod_name: Arbitrary input pod name.
120
+ namespace: Namespace in which the Pod will be created.
102
121
 
103
122
  Returns:
104
123
  Sanitized pod name.
105
124
  """
125
+ # https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names
106
126
  pod_name = re.sub(r"[^a-z0-9-]", "-", pod_name.lower())
107
127
  pod_name = re.sub(r"^[-]+", "", pod_name)
108
- return re.sub(r"[-]+", "-", pod_name)
128
+ pod_name = re.sub(r"[-]+$", "", pod_name)
129
+ pod_name = re.sub(r"[-]+", "-", pod_name)
130
+
131
+ allowed_length = calculate_max_pod_name_length_for_namespace(
132
+ namespace=namespace
133
+ )
134
+ return pod_name[:allowed_length]
135
+
136
+
137
+ def sanitize_label(label: str) -> str:
138
+ """Sanitize a label for a Kubernetes resource.
139
+
140
+ Args:
141
+ label: The label to sanitize.
142
+
143
+ Returns:
144
+ The sanitized label.
145
+ """
146
+ # https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names
147
+ label = re.sub(r"[^a-z0-9-]", "-", label.lower())
148
+ label = re.sub(r"^[-]+", "", label)
149
+ label = re.sub(r"[-]+$", "", label)
150
+ label = re.sub(r"[-]+", "-", label)
151
+
152
+ return label[:63]
109
153
 
110
154
 
111
155
  def pod_is_not_pending(pod: k8s_client.V1Pod) -> bool:
@@ -395,8 +395,19 @@ class KubernetesOrchestrator(ContainerizedOrchestrator):
395
395
  )
396
396
 
397
397
  pipeline_name = deployment.pipeline_configuration.name
398
- orchestrator_run_name = get_orchestrator_run_name(pipeline_name)
399
- pod_name = kube_utils.sanitize_pod_name(orchestrator_run_name)
398
+
399
+ # We already make sure the orchestrator run name has the correct length
400
+ # to make sure we don't cut off the randomized suffix later when
401
+ # sanitizing the pod name. This avoids any pod naming collisions.
402
+ max_length = kube_utils.calculate_max_pod_name_length_for_namespace(
403
+ namespace=self.config.kubernetes_namespace
404
+ )
405
+ orchestrator_run_name = get_orchestrator_run_name(
406
+ pipeline_name, max_length=max_length
407
+ )
408
+ pod_name = kube_utils.sanitize_pod_name(
409
+ orchestrator_run_name, namespace=self.config.kubernetes_namespace
410
+ )
400
411
 
401
412
  assert stack.container_registry
402
413
 
@@ -90,7 +90,9 @@ def main() -> None:
90
90
  """
91
91
  # Define Kubernetes pod name.
92
92
  pod_name = f"{orchestrator_run_id}-{step_name}"
93
- pod_name = kube_utils.sanitize_pod_name(pod_name)
93
+ pod_name = kube_utils.sanitize_pod_name(
94
+ pod_name, namespace=args.kubernetes_namespace
95
+ )
94
96
 
95
97
  image = KubernetesOrchestrator.get_image(
96
98
  deployment=deployment_config, step_name=step_name
@@ -25,6 +25,7 @@ from zenml.constants import ENV_ZENML_ENABLE_REPO_INIT_WARNINGS
25
25
  from zenml.integrations.airflow.orchestrators.dag_generator import (
26
26
  ENV_ZENML_LOCAL_STORES_PATH,
27
27
  )
28
+ from zenml.integrations.kubernetes.orchestrators import kube_utils
28
29
  from zenml.integrations.kubernetes.pod_settings import KubernetesPodSettings
29
30
 
30
31
 
@@ -167,8 +168,8 @@ def build_pod_manifest(
167
168
  # Add run_name and pipeline_name to the labels
168
169
  labels.update(
169
170
  {
170
- "run": run_name,
171
- "pipeline": pipeline_name,
171
+ "run": kube_utils.sanitize_label(run_name),
172
+ "pipeline": kube_utils.sanitize_label(pipeline_name),
172
173
  }
173
174
  )
174
175
 
@@ -197,7 +197,9 @@ class KubernetesStepOperator(BaseStepOperator):
197
197
  )
198
198
 
199
199
  pod_name = f"{info.run_name}_{info.pipeline_step_name}"
200
- pod_name = kube_utils.sanitize_pod_name(pod_name)
200
+ pod_name = kube_utils.sanitize_pod_name(
201
+ pod_name, namespace=self.config.kubernetes_namespace
202
+ )
201
203
 
202
204
  command = entrypoint_command[:3]
203
205
  args = entrypoint_command[3:]
@@ -0,0 +1,46 @@
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 integration for cloud-native step execution.
15
+
16
+ The Modal integration sub-module provides a step operator flavor that allows
17
+ executing steps on Modal's cloud infrastructure.
18
+ """
19
+ from typing import List, Type
20
+
21
+ from zenml.integrations.constants import MODAL
22
+ from zenml.integrations.integration import Integration
23
+ from zenml.stack import Flavor
24
+
25
+ MODAL_STEP_OPERATOR_FLAVOR = "modal"
26
+
27
+
28
+ class ModalIntegration(Integration):
29
+ """Definition of Modal integration for ZenML."""
30
+
31
+ NAME = MODAL
32
+ REQUIREMENTS = ["modal>=0.64.49,<1"]
33
+
34
+ @classmethod
35
+ def flavors(cls) -> List[Type[Flavor]]:
36
+ """Declare the stack component flavors for the Modal integration.
37
+
38
+ Returns:
39
+ List of new stack component flavors.
40
+ """
41
+ from zenml.integrations.modal.flavors import ModalStepOperatorFlavor
42
+
43
+ return [ModalStepOperatorFlavor]
44
+
45
+
46
+ ModalIntegration.check_installation()
@@ -0,0 +1,26 @@
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 integration flavors."""
15
+
16
+ from zenml.integrations.modal.flavors.modal_step_operator_flavor import (
17
+ ModalStepOperatorConfig,
18
+ ModalStepOperatorFlavor,
19
+ ModalStepOperatorSettings,
20
+ )
21
+
22
+ __all__ = [
23
+ "ModalStepOperatorConfig",
24
+ "ModalStepOperatorFlavor",
25
+ "ModalStepOperatorSettings",
26
+ ]
@@ -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())
@@ -400,7 +400,7 @@ class StepRunner:
400
400
  **artifact.get_hydrated_version().model_dump()
401
401
  )
402
402
 
403
- if data_type is Any or is_union(get_origin(data_type)):
403
+ if data_type in (None, Any) or is_union(get_origin(data_type)):
404
404
  # Entrypoint function does not define a specific type for the input,
405
405
  # we use the datatype of the stored artifact
406
406
  data_type = source_utils.load(artifact.data_type)
@@ -40,7 +40,9 @@ if TYPE_CHECKING:
40
40
  from zenml.artifact_stores.base_artifact_store import BaseArtifactStore
41
41
 
42
42
 
43
- def get_orchestrator_run_name(pipeline_name: str) -> str:
43
+ def get_orchestrator_run_name(
44
+ pipeline_name: str, max_length: Optional[int] = None
45
+ ) -> str:
44
46
  """Gets an orchestrator run name.
45
47
 
46
48
  This run name is not the same as the ZenML run name but can instead be
@@ -48,11 +50,31 @@ def get_orchestrator_run_name(pipeline_name: str) -> str:
48
50
 
49
51
  Args:
50
52
  pipeline_name: Name of the pipeline that will run.
53
+ max_length: Maximum length of the generated name.
54
+
55
+ Raises:
56
+ ValueError: If the max length is below 8 characters.
51
57
 
52
58
  Returns:
53
59
  The orchestrator run name.
54
60
  """
55
- return f"{pipeline_name}_{random.Random().getrandbits(128):032x}"
61
+ suffix_length = 32
62
+ pipeline_name = f"{pipeline_name}_"
63
+
64
+ if max_length:
65
+ if max_length < 8:
66
+ raise ValueError(
67
+ "Maximum length for orchestrator run name must be 8 or above."
68
+ )
69
+
70
+ # Make sure we always have a certain suffix to guarantee no overlap
71
+ # with other runs
72
+ suffix_length = min(32, max(8, max_length - len(pipeline_name)))
73
+ pipeline_name = pipeline_name[: (max_length - suffix_length)]
74
+
75
+ suffix = "".join(random.choices("0123456789abcdef", k=suffix_length))
76
+
77
+ return f"{pipeline_name}{suffix}"
56
78
 
57
79
 
58
80
  def is_setting_enabled(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zenml-nightly
3
- Version: 0.70.0.dev20241127
3
+ Version: 0.70.0.dev20241128
4
4
  Summary: ZenML: Write production-ready ML code.
5
5
  Home-page: https://zenml.io
6
6
  License: Apache-2.0
@@ -6,7 +6,7 @@ RELEASE_NOTES.md,sha256=DleauURHESDrTrcVzCVLqPiSM9NIAk5vldvEFMc7qlk,389375
6
6
  ROADMAP.md,sha256=hiLSmr16BH8Dfx7SaQM4JcXCGCVl6mFZPFAwJeDTrJU,407
7
7
  SECURITY.md,sha256=9DepA8y03yvCZLHEfcXLTDH4lUyKHquAdukBsccNN7c,682
8
8
  zenml/README.md,sha256=827dekbOWAs1BpW7VF1a4d7EbwPbjwccX-2zdXBENZo,1777
9
- zenml/VERSION,sha256=ABVQIWEjcsl8ujX_CSg3mt4aGfIhe3dLr9X3GJHNxrU,19
9
+ zenml/VERSION,sha256=NNMdj0FLYeBDdXqt2fW55JGAJCyPz22NxDtWUZCXycg,19
10
10
  zenml/__init__.py,sha256=SkMObQA41ajqdZqGErN00S1Vf3KAxpLvbZ-OBy5uYoo,2130
11
11
  zenml/actions/__init__.py,sha256=mrt6wPo73iKRxK754_NqsGyJ3buW7RnVeIGXr1xEw8Y,681
12
12
  zenml/actions/base_action.py,sha256=UcaHev6BTuLDwuswnyaPjdA8AgUqB5xPZ-lRtuvf2FU,25553
@@ -130,7 +130,7 @@ zenml/image_builders/base_image_builder.py,sha256=-Y5N3zFZsMJvVuzm1M3tU-r38fT9KC
130
130
  zenml/image_builders/build_context.py,sha256=TTY5T8aG4epeKOOpLItr8PDjmDijfcGaY3zFzmGV1II,6157
131
131
  zenml/image_builders/local_image_builder.py,sha256=R_zMpERtUCWLLDZg9kXuAQSWLtin1ve_rxpbUBiym7s,6448
132
132
  zenml/integrations/README.md,sha256=hFIZwjsAItHjvDWVBqGSF-ZAeMsFR2GKX1Axl2g1Bz0,6190
133
- zenml/integrations/__init__.py,sha256=ciJbNsqNPTHpWeMbFfLNa8fJ0jg8AxJUjOPnqrYPl9M,4843
133
+ zenml/integrations/__init__.py,sha256=GiLRzSCu1O9Jg5NSCRKVDWLIXtKuc90ozntqYjUHo08,4905
134
134
  zenml/integrations/airflow/__init__.py,sha256=7ffV98vlrdH1RfWHkv8TXNd3hjtXSx4z2U7MZin-87I,1483
135
135
  zenml/integrations/airflow/flavors/__init__.py,sha256=Y48mn5OxERPPaXDBd5CFAIn6yhLPsgN5ZMk26hLXiNM,800
136
136
  zenml/integrations/airflow/flavors/airflow_orchestrator_flavor.py,sha256=VfZQD2H-WwIgVD1Fi7uewdnkvRoSykY0YCfROFDadXg,6189
@@ -198,7 +198,7 @@ zenml/integrations/comet/experiment_trackers/__init__.py,sha256=reGygyAEgMrlc-9Q
198
198
  zenml/integrations/comet/experiment_trackers/comet_experiment_tracker.py,sha256=JnB_TqiCD8t9t6cVxWoomxvBuhA4jIJHYFZ-gKdGXf8,5767
199
199
  zenml/integrations/comet/flavors/__init__.py,sha256=x-XK-YwHMxz3zZPoIXo3X5vq_5VYUJAnsIoEX_ZooOU,883
200
200
  zenml/integrations/comet/flavors/comet_experiment_tracker_flavor.py,sha256=Rkk1UtEVY2MQBKbUHKxYQpDTWndkOYF8KuKuMGZAb24,3706
201
- zenml/integrations/constants.py,sha256=Qi3uwS9jIxGY1v4nES-5npWuQTS2uOj6IEUKyOzLehM,2055
201
+ zenml/integrations/constants.py,sha256=hbRRrkXz4qBFFZOl81G_2u7O-gWLU8DTSy43HlyUDUY,2071
202
202
  zenml/integrations/databricks/__init__.py,sha256=dkyTxfwIete7mRBlDzIfsTmllYgrd4DB2P4brXHPMUs,2414
203
203
  zenml/integrations/databricks/flavors/__init__.py,sha256=S-BZ3R9iKGOw-aUltR8I0ULEe2-LKGTIZhQv9TlnXfk,1122
204
204
  zenml/integrations/databricks/flavors/databricks_model_deployer_flavor.py,sha256=eDyYVqO2x1A9qgGICKJx5Z3qiUuTMfW9R3NZUO8OiRk,3591
@@ -337,17 +337,17 @@ zenml/integrations/kubernetes/flavors/__init__.py,sha256=a5gU45qCj3FkLwl_uVjlIkL
337
337
  zenml/integrations/kubernetes/flavors/kubernetes_orchestrator_flavor.py,sha256=HkCyDWqv1lDd8W6GeXE6PeHQiUrHPfSkfw3sB0B2xuA,7911
338
338
  zenml/integrations/kubernetes/flavors/kubernetes_step_operator_flavor.py,sha256=ILN-H4cl7z3i4ltb4UBs55wbtIo871b4ib28pYkQoyQ,5605
339
339
  zenml/integrations/kubernetes/orchestrators/__init__.py,sha256=TJID3OTieZBox36WpQpzD0jdVRA_aZVcs_bNtfXS8ik,811
340
- zenml/integrations/kubernetes/orchestrators/kube_utils.py,sha256=9PSr1wAR7qGjrtsSrIT00P-5c3eFcVV2tzXzTbH9WOc,10615
341
- zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator.py,sha256=ElvTfgDEKnhFKLy1TL7v-34Paq9v5rXkXN4Otzonvig,22418
342
- zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator_entrypoint.py,sha256=hGvBUnUPhpjudDfumiLLSDsNU2fJR1C-BEPJ671h-VY,6383
340
+ zenml/integrations/kubernetes/orchestrators/kube_utils.py,sha256=0Cj1RoiuXL4oACnfyzEpOiCTnHt_YoF5ml3bhobyOEg,12195
341
+ zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator.py,sha256=W6YpTQXXzQcpfskJcuYwN1LMiN8eUJQHWJ4QXMmADFs,22899
342
+ zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator_entrypoint.py,sha256=4BBy8-0xJahRjpkFc9fYy90uIRxOoy-s-GmXZeFE2tQ,6442
343
343
  zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator_entrypoint_configuration.py,sha256=Y7uGU8eksMluGyXYsf688CwpiXwI_W6WYLscYwRZXRY,2494
344
- zenml/integrations/kubernetes/orchestrators/manifest_utils.py,sha256=HS8FDu8m9bDFpnmJ5RCe0ZH57F__xqIuo6Fo7wJkL7E,11365
344
+ zenml/integrations/kubernetes/orchestrators/manifest_utils.py,sha256=urcPGUm51vRA1a5eZk2jEGjFrDLPHqD4jTVJDpxngnU,11486
345
345
  zenml/integrations/kubernetes/pod_settings.py,sha256=NMp4aHKRG29mh1Nq5uvV78Hzj1cMZj93poWCBiwov-M,4898
346
346
  zenml/integrations/kubernetes/serialization_utils.py,sha256=cPSe4szdBLzDnUZT9nQc2CCA8h84aj5oTA8vsUE36ig,7000
347
347
  zenml/integrations/kubernetes/service_connectors/__init__.py,sha256=Uf6zlHIapYrRDl3xOPWQ2jA7jt85SXx1U7DmSxzxTvQ,818
348
348
  zenml/integrations/kubernetes/service_connectors/kubernetes_service_connector.py,sha256=kgdh25dOBNTxLAFft_cknwHoWRAGrdzUu9fLsm4ZlfY,19579
349
349
  zenml/integrations/kubernetes/step_operators/__init__.py,sha256=40utDPYAezxHsFgO0UUIT_6XpCDzDapje6OH951XsTs,806
350
- zenml/integrations/kubernetes/step_operators/kubernetes_step_operator.py,sha256=sEz8IZkzo0qarnPCr8zarBPHR0T0i6HL8r9nSNHQBxI,8310
350
+ zenml/integrations/kubernetes/step_operators/kubernetes_step_operator.py,sha256=x7-ZO1r85eqOE_RS4IM0H7NGYZcB9okTj6qFrh9N5HM,8376
351
351
  zenml/integrations/label_studio/__init__.py,sha256=tXmK0Wu_bFgtL7CqPPubSK99PaBZSyAu90aghHlXAek,1520
352
352
  zenml/integrations/label_studio/annotators/__init__.py,sha256=YtOtSfS1_NBoLoXIygEerElBP1-B98UU0HOAEfzdRY0,821
353
353
  zenml/integrations/label_studio/annotators/label_studio_annotator.py,sha256=VkuW4zsZhHz8__P9WTTLRTF-FOmoYB-_cFqBdu-PUyA,30498
@@ -396,6 +396,11 @@ zenml/integrations/mlflow/services/mlflow_deployment.py,sha256=iu8u0jQLKNf5oueGP
396
396
  zenml/integrations/mlflow/steps/__init__.py,sha256=5IXeipGRfBjtqr0ZdbQLliuQNr5GXsm7xkhpqfOg6qI,770
397
397
  zenml/integrations/mlflow/steps/mlflow_deployer.py,sha256=E4A-tiKsVG-GDy1vaiOw37mpa4SzJR3T-HZ6crzONyo,12238
398
398
  zenml/integrations/mlflow/steps/mlflow_registry.py,sha256=BeOhuo72ghuJWEIdr4YNqit_ImSljW4suUE9XguFjeQ,6425
399
+ zenml/integrations/modal/__init__.py,sha256=jyIhsLVjxhepKEpO-uyNJ5NSuSqzBIJnhb_lHdHFFFM,1525
400
+ zenml/integrations/modal/flavors/__init__.py,sha256=169Oirq-NUVl-2oiByrMcL3HBM0R971BrS6xVBk0DB4,922
401
+ zenml/integrations/modal/flavors/modal_step_operator_flavor.py,sha256=r7xkzER0hvNY6N0CEZMiF1vRk7A7CfkuYOeGDJ517N4,3978
402
+ zenml/integrations/modal/step_operators/__init__.py,sha256=8NYCXe7aNPnldCI9IjcHzJC_B5e4gtfGzpHNZ-ToKnE,780
403
+ zenml/integrations/modal/step_operators/modal_step_operator.py,sha256=J2HiNaGnlP6kDLBnQKz8mQKaoaQLitFkjHhOP_XRWgI,8543
399
404
  zenml/integrations/neptune/__init__.py,sha256=FUaMxVC9uPryIY7reWdzfMcMdsf5T3HjBZ1QjpjEX4Q,1717
400
405
  zenml/integrations/neptune/experiment_trackers/__init__.py,sha256=DJi7lk7NXYrRg3VGPPSEsMycKECDfXL-h2V0A0r7z8Y,833
401
406
  zenml/integrations/neptune/experiment_trackers/neptune_experiment_tracker.py,sha256=c5CigxkeYGBadcchTOSjZPaDAtAkSzNM9EnaE2dOgc8,3730
@@ -681,9 +686,9 @@ zenml/orchestrators/output_utils.py,sha256=Gz7SX2cbQ3w4eyfU0XuhKEKGtalQGBoc6moDR
681
686
  zenml/orchestrators/publish_utils.py,sha256=aNwgTDmVSq9qCDP3Ldk77YNXnWx_YHjYNTEJwYeZo9s,4579
682
687
  zenml/orchestrators/step_launcher.py,sha256=HGdshxQAwYHLHa_nJyIkMgrSTbxTQKqI63mKaSQ3ZVg,17758
683
688
  zenml/orchestrators/step_run_utils.py,sha256=FjnNGYz8NF-F_WP_8w7Zny0zDd6JanCp3IAFMjKFU9g,19996
684
- zenml/orchestrators/step_runner.py,sha256=rInaBHdU-43xkho2xW_OlMsAZRVCanry8jRCBveVBpA,25076
689
+ zenml/orchestrators/step_runner.py,sha256=5bRN_0kAj8Jnk0cKt-xGnLrywmbSsqqkcBaJ3jYgDHc,25084
685
690
  zenml/orchestrators/topsort.py,sha256=D8evz3X47zwpXd90NMLsJD-_uCeXtV6ClzNfDUrq7cM,5784
686
- zenml/orchestrators/utils.py,sha256=mW-abfIeLRsprE1Y-wGvURfJzNNtFHMyRrkgX_LEpkw,11446
691
+ zenml/orchestrators/utils.py,sha256=DCKQuAzv9Ba2BxAGeexgCc9Q_1nzbqzhr4B4jI9NbeA,12141
687
692
  zenml/orchestrators/wheeled_orchestrator.py,sha256=eOnMcnd3sCzfhA2l6qRAzF0rOXzaojbjvvYvTkqixQo,4791
688
693
  zenml/pipelines/__init__.py,sha256=hpIX7hN8jsQRHT5R-xSXZL88qrHwkmrvGLQeu1rWt4o,873
689
694
  zenml/pipelines/build_utils.py,sha256=Az-zz56WQqD9dUrZ0iQDDRok-GNeglmXQxv40LoiyQk,25440
@@ -1268,8 +1273,8 @@ zenml/zen_stores/secrets_stores/sql_secrets_store.py,sha256=Bq1djrUP9saoD7vECjS7
1268
1273
  zenml/zen_stores/sql_zen_store.py,sha256=rJT3Uth4J6D1iVfBdNHipgI54jcqIItNVc-IMNEU8Zc,404787
1269
1274
  zenml/zen_stores/template_utils.py,sha256=EKYBgmDLTS_PSMWaIO5yvHPLiQvMqHcsAe6NUCrv-i4,9068
1270
1275
  zenml/zen_stores/zen_store_interface.py,sha256=vf2gKBWfUUPtcGZC35oQB6pPNVzWVyQC8nWxVLjfrxM,92692
1271
- zenml_nightly-0.70.0.dev20241127.dist-info/LICENSE,sha256=wbnfEnXnafPbqwANHkV6LUsPKOtdpsd-SNw37rogLtc,11359
1272
- zenml_nightly-0.70.0.dev20241127.dist-info/METADATA,sha256=KeeoaNmNjtSScsuRqF9RjmRVytjnekiiKvTDU1XaAJY,21208
1273
- zenml_nightly-0.70.0.dev20241127.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
1274
- zenml_nightly-0.70.0.dev20241127.dist-info/entry_points.txt,sha256=QK3ETQE0YswAM2mWypNMOv8TLtr7EjnqAFq1br_jEFE,43
1275
- zenml_nightly-0.70.0.dev20241127.dist-info/RECORD,,
1276
+ zenml_nightly-0.70.0.dev20241128.dist-info/LICENSE,sha256=wbnfEnXnafPbqwANHkV6LUsPKOtdpsd-SNw37rogLtc,11359
1277
+ zenml_nightly-0.70.0.dev20241128.dist-info/METADATA,sha256=pq6OfIPB-gg_Y1RQMZbo2H-G2BX9FLotTfJqywDuYpw,21208
1278
+ zenml_nightly-0.70.0.dev20241128.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
1279
+ zenml_nightly-0.70.0.dev20241128.dist-info/entry_points.txt,sha256=QK3ETQE0YswAM2mWypNMOv8TLtr7EjnqAFq1br_jEFE,43
1280
+ zenml_nightly-0.70.0.dev20241128.dist-info/RECORD,,