zenml-nightly 0.82.0.dev20250512__py3-none-any.whl → 0.82.0.dev20250514__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/cli/login.py +1 -1
- zenml/config/build_configuration.py +9 -0
- zenml/config/docker_settings.py +97 -16
- zenml/constants.py +1 -0
- zenml/integrations/kubernetes/flavors/kubernetes_orchestrator_flavor.py +12 -1
- zenml/integrations/kubernetes/orchestrators/kube_utils.py +24 -1
- zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator.py +3 -0
- zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator_entrypoint.py +21 -5
- zenml/integrations/kubernetes/orchestrators/manifest_utils.py +13 -0
- zenml/orchestrators/step_launcher.py +4 -0
- zenml/orchestrators/step_run_utils.py +30 -1
- zenml/pipelines/build_utils.py +8 -0
- zenml/utils/pipeline_docker_image_builder.py +133 -7
- zenml/utils/tag_utils.py +3 -3
- zenml/zen_server/feature_gate/endpoint_utils.py +9 -12
- zenml/zen_server/feature_gate/feature_gate_interface.py +4 -6
- zenml/zen_server/feature_gate/zenml_cloud_feature_gate.py +8 -9
- zenml/zen_server/rbac/endpoint_utils.py +1 -1
- zenml/zen_server/rbac/rbac_sql_zen_store.py +2 -2
- zenml/zen_server/routers/run_templates_endpoints.py +10 -1
- zenml/zen_server/template_execution/utils.py +12 -1
- zenml/zen_stores/migrations/alembic.py +37 -13
- zenml/zen_stores/rest_zen_store.py +6 -2
- zenml/zen_stores/sql_zen_store.py +3 -6
- {zenml_nightly-0.82.0.dev20250512.dist-info → zenml_nightly-0.82.0.dev20250514.dist-info}/METADATA +2 -2
- {zenml_nightly-0.82.0.dev20250512.dist-info → zenml_nightly-0.82.0.dev20250514.dist-info}/RECORD +30 -30
- {zenml_nightly-0.82.0.dev20250512.dist-info → zenml_nightly-0.82.0.dev20250514.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.82.0.dev20250512.dist-info → zenml_nightly-0.82.0.dev20250514.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.82.0.dev20250512.dist-info → zenml_nightly-0.82.0.dev20250514.dist-info}/entry_points.txt +0 -0
zenml/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.82.0.
|
1
|
+
0.82.0.dev20250514
|
zenml/cli/login.py
CHANGED
@@ -1061,7 +1061,7 @@ def logout(
|
|
1061
1061
|
if clear:
|
1062
1062
|
credentials_store.clear_credentials(server_url=server)
|
1063
1063
|
cli_utils.declare(
|
1064
|
-
f"Logged out from {server}
|
1064
|
+
f"Logged out from {server}.\n"
|
1065
1065
|
f"Hint: You can run `zenml login '{server}'` to log in again "
|
1066
1066
|
"to the same server or 'zenml server list' to view other available "
|
1067
1067
|
"servers that you can connect to with 'zenml login <server-url>'."
|
@@ -135,6 +135,9 @@ class BuildConfiguration(BaseModel):
|
|
135
135
|
Returns:
|
136
136
|
Whether files should be included in the image.
|
137
137
|
"""
|
138
|
+
if self.settings.local_project_install_command:
|
139
|
+
return True
|
140
|
+
|
138
141
|
if self.should_download_files(code_repository=code_repository):
|
139
142
|
return False
|
140
143
|
|
@@ -153,6 +156,9 @@ class BuildConfiguration(BaseModel):
|
|
153
156
|
Returns:
|
154
157
|
Whether files should be downloaded in the image.
|
155
158
|
"""
|
159
|
+
if self.settings.local_project_install_command:
|
160
|
+
return False
|
161
|
+
|
156
162
|
if self.should_download_files_from_code_repository(
|
157
163
|
code_repository=code_repository
|
158
164
|
):
|
@@ -176,6 +182,9 @@ class BuildConfiguration(BaseModel):
|
|
176
182
|
Returns:
|
177
183
|
Whether files should be downloaded from the code repository.
|
178
184
|
"""
|
185
|
+
if self.settings.local_project_install_command:
|
186
|
+
return False
|
187
|
+
|
179
188
|
if (
|
180
189
|
code_repository
|
181
190
|
and self.settings.allow_download_from_code_repository
|
zenml/config/docker_settings.py
CHANGED
@@ -91,12 +91,21 @@ class DockerSettings(BaseSettings):
|
|
91
91
|
--------------------------------
|
92
92
|
Depending on the configuration of this object, requirements will be
|
93
93
|
installed in the following order (each step optional):
|
94
|
-
- The packages installed in your local python environment
|
94
|
+
- The packages installed in your local python environment (extracted using
|
95
|
+
`pip freeze`)
|
95
96
|
- The packages required by the stack unless this is disabled by setting
|
96
|
-
`install_stack_requirements=False
|
97
|
+
`install_stack_requirements=False`
|
97
98
|
- The packages specified via the `required_integrations`
|
99
|
+
- The packages defined inside a pyproject.toml file given by the
|
100
|
+
`pyproject_path` attribute.
|
98
101
|
- The packages specified via the `requirements` attribute
|
99
102
|
|
103
|
+
If neither `replicate_local_python_environment`, `pyproject_path` or
|
104
|
+
`requirements` are specified, ZenML will try to automatically find a
|
105
|
+
requirements.txt or pyproject.toml file in your current source root
|
106
|
+
and installs packages from the first one it finds. You can disable this
|
107
|
+
behavior by setting `disable_automatic_requirements_detection=True`.
|
108
|
+
|
100
109
|
Attributes:
|
101
110
|
parent_image: Full name of the Docker image that should be
|
102
111
|
used as the parent for the image that will be built. Defaults to
|
@@ -137,10 +146,29 @@ class DockerSettings(BaseSettings):
|
|
137
146
|
packages.
|
138
147
|
python_package_installer_args: Arguments to pass to the python package
|
139
148
|
installer.
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
149
|
+
disable_automatic_requirements_detection: If set to True, ZenML will
|
150
|
+
not automatically detect requirements.txt files or pyproject.toml
|
151
|
+
files in your source root.
|
152
|
+
replicate_local_python_environment: If set to True, ZenML will run
|
153
|
+
`pip freeze` to gather the requirements of the local Python
|
154
|
+
environment and then install them in the Docker image.
|
155
|
+
pyproject_path: Path to a pyproject.toml file. If given, the
|
156
|
+
dependencies will be exported to a requirements.txt
|
157
|
+
formatted file using the `pyproject_export_command` and then
|
158
|
+
installed inside the Docker image.
|
159
|
+
pyproject_export_command: Command to export the dependencies inside a
|
160
|
+
pyproject.toml file to a requirements.txt formatted file. If not
|
161
|
+
given and ZenML needs to export the requirements anyway, `uv export`
|
162
|
+
and `poetry export` will be tried to see if one of them works. This
|
163
|
+
command can contain a `{directory}` placeholder which will be
|
164
|
+
replaced with the directory in which the pyproject.toml file is
|
165
|
+
stored.
|
166
|
+
**Note**: This command will be run before any code files are copied
|
167
|
+
into the image. It is therefore not possible to install a local
|
168
|
+
project using this command. This command should exclude any local
|
169
|
+
projects, and you can specify a `local_project_install_command`
|
170
|
+
instead which will be run after the code files are copied into the
|
171
|
+
image.
|
144
172
|
requirements: Path to a requirements file or a list of required pip
|
145
173
|
packages. During the image build, these requirements will be
|
146
174
|
installed using pip. If you need to use a different tool to
|
@@ -149,13 +177,16 @@ class DockerSettings(BaseSettings):
|
|
149
177
|
required_integrations: List of ZenML integrations that should be
|
150
178
|
installed. All requirements for the specified integrations will
|
151
179
|
be installed inside the Docker image.
|
152
|
-
required_hub_plugins: DEPRECATED/UNUSED.
|
153
180
|
install_stack_requirements: If `True`, ZenML will automatically detect
|
154
181
|
if components of your active stack are part of a ZenML integration
|
155
182
|
and install the corresponding requirements and apt packages.
|
156
183
|
If you set this to `False` or use custom components in your stack,
|
157
184
|
you need to make sure these get installed by specifying them in
|
158
185
|
the `requirements` and `apt_packages` attributes.
|
186
|
+
local_project_install_command: Command to install a local project in
|
187
|
+
the Docker image. This is run after the code files are copied into
|
188
|
+
the image, and it is therefore only possible when code is included
|
189
|
+
in the image, not downloaded at runtime.
|
159
190
|
apt_packages: APT packages to install inside the Docker image.
|
160
191
|
environment: Dictionary of environment variables to set inside the
|
161
192
|
Docker image.
|
@@ -170,14 +201,6 @@ class DockerSettings(BaseSettings):
|
|
170
201
|
from a code repository if possible.
|
171
202
|
allow_download_from_artifact_store: If `True`, code can be downloaded
|
172
203
|
from the artifact store.
|
173
|
-
build_options: DEPRECATED, use parent_image_build_config.build_options
|
174
|
-
instead.
|
175
|
-
dockerignore: DEPRECATED, use build_config.dockerignore instead.
|
176
|
-
copy_files: DEPRECATED/UNUSED.
|
177
|
-
copy_global_config: DEPRECATED/UNUSED.
|
178
|
-
source_files: DEPRECATED. Use allow_including_files_in_images,
|
179
|
-
allow_download_from_code_repository and
|
180
|
-
allow_download_from_artifact_store instead.
|
181
204
|
"""
|
182
205
|
|
183
206
|
parent_image: Optional[str] = None
|
@@ -191,14 +214,18 @@ class DockerSettings(BaseSettings):
|
|
191
214
|
PythonPackageInstaller.PIP
|
192
215
|
)
|
193
216
|
python_package_installer_args: Dict[str, Any] = {}
|
217
|
+
disable_automatic_requirements_detection: bool = True
|
194
218
|
replicate_local_python_environment: Optional[
|
195
|
-
Union[List[str], PythonEnvironmentExportMethod]
|
219
|
+
Union[List[str], PythonEnvironmentExportMethod, bool]
|
196
220
|
] = Field(default=None, union_mode="left_to_right")
|
221
|
+
pyproject_path: Optional[str] = None
|
222
|
+
pyproject_export_command: Optional[List[str]] = None
|
197
223
|
requirements: Union[None, str, List[str]] = Field(
|
198
224
|
default=None, union_mode="left_to_right"
|
199
225
|
)
|
200
226
|
required_integrations: List[str] = []
|
201
227
|
install_stack_requirements: bool = True
|
228
|
+
local_project_install_command: Optional[str] = None
|
202
229
|
apt_packages: List[str] = []
|
203
230
|
environment: Dict[str, Any] = {}
|
204
231
|
user: Optional[str] = None
|
@@ -221,6 +248,8 @@ class DockerSettings(BaseSettings):
|
|
221
248
|
"copy_global_config",
|
222
249
|
"source_files",
|
223
250
|
"required_hub_plugins",
|
251
|
+
"build_options",
|
252
|
+
"dockerignore",
|
224
253
|
)
|
225
254
|
|
226
255
|
@model_validator(mode="before")
|
@@ -305,6 +334,58 @@ class DockerSettings(BaseSettings):
|
|
305
334
|
|
306
335
|
return self
|
307
336
|
|
337
|
+
@model_validator(mode="after")
|
338
|
+
def _validate_code_files_included_if_installing_local_project(
|
339
|
+
self,
|
340
|
+
) -> "DockerSettings":
|
341
|
+
"""Ensures that files are included when installing a local package.
|
342
|
+
|
343
|
+
Raises:
|
344
|
+
ValueError: If files are not included in the Docker image
|
345
|
+
when trying to install a local package.
|
346
|
+
|
347
|
+
Returns:
|
348
|
+
The validated settings values.
|
349
|
+
"""
|
350
|
+
if (
|
351
|
+
self.local_project_install_command
|
352
|
+
and not self.allow_including_files_in_images
|
353
|
+
):
|
354
|
+
raise ValueError(
|
355
|
+
"Files must be included in the Docker image when trying to "
|
356
|
+
"install a local python package. You can do so by setting "
|
357
|
+
"the `allow_including_files_in_images` attribute of your "
|
358
|
+
"DockerSettings to `True`."
|
359
|
+
)
|
360
|
+
|
361
|
+
return self
|
362
|
+
|
363
|
+
@model_validator(mode="after")
|
364
|
+
def _deprecate_replicate_local_environment_commands(
|
365
|
+
self,
|
366
|
+
) -> "DockerSettings":
|
367
|
+
"""Deprecates some values for `replicate_local_python_environment`.
|
368
|
+
|
369
|
+
Returns:
|
370
|
+
The validated settings values.
|
371
|
+
"""
|
372
|
+
if isinstance(
|
373
|
+
self.replicate_local_python_environment,
|
374
|
+
(str, list, PythonEnvironmentExportMethod),
|
375
|
+
):
|
376
|
+
logger.warning(
|
377
|
+
"Specifying a command (`%s`) for "
|
378
|
+
"`DockerSettings.replicate_local_python_environment` is "
|
379
|
+
"deprecated. If you want to replicate your exact local "
|
380
|
+
"environment using `pip freeze`, set "
|
381
|
+
"`DockerSettings.replicate_local_python_environment=True`. "
|
382
|
+
"If you want to export requirements from a pyproject.toml "
|
383
|
+
"file, use `DockerSettings.pyproject_path` and "
|
384
|
+
"`DockerSettings.pyproject_export_command` instead."
|
385
|
+
)
|
386
|
+
|
387
|
+
return self
|
388
|
+
|
308
389
|
model_config = ConfigDict(
|
309
390
|
# public attributes are immutable
|
310
391
|
frozen=True,
|
zenml/constants.py
CHANGED
@@ -341,6 +341,7 @@ DEFAULT_ZENML_SERVER_REPORT_USER_ACTIVITY_TO_DB_SECONDS = 30
|
|
341
341
|
DEFAULT_ZENML_SERVER_MAX_REQUEST_BODY_SIZE_IN_BYTES = 256 * 1024 * 1024
|
342
342
|
|
343
343
|
DEFAULT_REPORTABLE_RESOURCES = ["project", "pipeline", "pipeline_run", "model"]
|
344
|
+
RUN_TEMPLATE_TRIGGERS_FEATURE_NAME = "template_run"
|
344
345
|
|
345
346
|
# API Endpoint paths:
|
346
347
|
ACTIVATE = "/activate"
|
@@ -15,7 +15,7 @@
|
|
15
15
|
|
16
16
|
from typing import TYPE_CHECKING, Optional, Type
|
17
17
|
|
18
|
-
from pydantic import PositiveInt
|
18
|
+
from pydantic import NonNegativeInt, PositiveInt
|
19
19
|
|
20
20
|
from zenml.config.base_settings import BaseSettings
|
21
21
|
from zenml.constants import KUBERNETES_CLUSTER_RESOURCE_TYPE
|
@@ -59,6 +59,14 @@ class KubernetesOrchestratorSettings(BaseSettings):
|
|
59
59
|
pod_failure_backoff: The backoff factor for pod failure retries and
|
60
60
|
pod startup retries.
|
61
61
|
max_parallelism: Maximum number of steps to run in parallel.
|
62
|
+
successful_jobs_history_limit: The number of successful jobs
|
63
|
+
to retain. This only applies to jobs created when scheduling a
|
64
|
+
pipeline.
|
65
|
+
failed_jobs_history_limit: The number of failed jobs to retain.
|
66
|
+
This only applies to jobs created when scheduling a pipeline.
|
67
|
+
ttl_seconds_after_finished: The amount of seconds to keep finished jobs
|
68
|
+
before deleting them. This only applies to jobs created when
|
69
|
+
scheduling a pipeline.
|
62
70
|
"""
|
63
71
|
|
64
72
|
synchronous: bool = True
|
@@ -74,6 +82,9 @@ class KubernetesOrchestratorSettings(BaseSettings):
|
|
74
82
|
pod_failure_retry_delay: int = 10
|
75
83
|
pod_failure_backoff: float = 1.0
|
76
84
|
max_parallelism: Optional[PositiveInt] = None
|
85
|
+
successful_jobs_history_limit: Optional[NonNegativeInt] = None
|
86
|
+
failed_jobs_history_limit: Optional[NonNegativeInt] = None
|
87
|
+
ttl_seconds_after_finished: Optional[NonNegativeInt] = None
|
77
88
|
|
78
89
|
|
79
90
|
class KubernetesOrchestratorConfig(
|
@@ -34,7 +34,7 @@ Adjusted from https://github.com/tensorflow/tfx/blob/master/tfx/utils/kube_utils
|
|
34
34
|
import enum
|
35
35
|
import re
|
36
36
|
import time
|
37
|
-
from typing import Any, Callable, Dict, Optional, TypeVar, cast
|
37
|
+
from typing import Any, Callable, Dict, List, Optional, TypeVar, cast
|
38
38
|
|
39
39
|
from kubernetes import client as k8s_client
|
40
40
|
from kubernetes import config as k8s_config
|
@@ -554,3 +554,26 @@ def create_and_wait_for_pod_to_start(
|
|
554
554
|
total_wait += delay
|
555
555
|
time.sleep(delay)
|
556
556
|
delay *= startup_failure_backoff
|
557
|
+
|
558
|
+
|
559
|
+
def get_pod_owner_references(
|
560
|
+
core_api: k8s_client.CoreV1Api, pod_name: str, namespace: str
|
561
|
+
) -> List[k8s_client.V1OwnerReference]:
|
562
|
+
"""Get owner references for a pod.
|
563
|
+
|
564
|
+
Args:
|
565
|
+
core_api: Kubernetes CoreV1Api client.
|
566
|
+
pod_name: Name of the pod.
|
567
|
+
namespace: Kubernetes namespace.
|
568
|
+
|
569
|
+
Returns:
|
570
|
+
List of owner references.
|
571
|
+
"""
|
572
|
+
pod = get_pod(core_api=core_api, pod_name=pod_name, namespace=namespace)
|
573
|
+
|
574
|
+
if not pod or not pod.metadata or not pod.metadata.owner_references:
|
575
|
+
return []
|
576
|
+
|
577
|
+
return cast(
|
578
|
+
List[k8s_client.V1OwnerReference], pod.metadata.owner_references
|
579
|
+
)
|
@@ -523,6 +523,9 @@ class KubernetesOrchestrator(ContainerizedOrchestrator):
|
|
523
523
|
pod_settings=orchestrator_pod_settings,
|
524
524
|
env=environment,
|
525
525
|
mount_local_stores=self.config.is_local,
|
526
|
+
successful_jobs_history_limit=settings.successful_jobs_history_limit,
|
527
|
+
failed_jobs_history_limit=settings.failed_jobs_history_limit,
|
528
|
+
ttl_seconds_after_finished=settings.ttl_seconds_after_finished,
|
526
529
|
)
|
527
530
|
|
528
531
|
self._k8s_batch_api.create_namespaced_cron_job(
|
@@ -71,7 +71,7 @@ def main() -> None:
|
|
71
71
|
# Parse / extract args.
|
72
72
|
args = parse_args()
|
73
73
|
|
74
|
-
|
74
|
+
orchestrator_pod_name = socket.gethostname()
|
75
75
|
|
76
76
|
client = Client()
|
77
77
|
|
@@ -95,7 +95,22 @@ def main() -> None:
|
|
95
95
|
core_api = k8s_client.CoreV1Api(kube_client)
|
96
96
|
|
97
97
|
env = get_config_environment_vars()
|
98
|
-
env[ENV_ZENML_KUBERNETES_RUN_ID] =
|
98
|
+
env[ENV_ZENML_KUBERNETES_RUN_ID] = orchestrator_pod_name
|
99
|
+
|
100
|
+
try:
|
101
|
+
owner_references = kube_utils.get_pod_owner_references(
|
102
|
+
core_api=core_api,
|
103
|
+
pod_name=orchestrator_pod_name,
|
104
|
+
namespace=args.kubernetes_namespace,
|
105
|
+
)
|
106
|
+
except Exception as e:
|
107
|
+
logger.warning(f"Failed to get pod owner references: {str(e)}")
|
108
|
+
owner_references = []
|
109
|
+
else:
|
110
|
+
# Make sure None of the owner references are marked as controllers of
|
111
|
+
# the created pod, which messes with the garbage collection logic.
|
112
|
+
for owner_reference in owner_references:
|
113
|
+
owner_reference.controller = False
|
99
114
|
|
100
115
|
def run_step_on_kubernetes(step_name: str) -> None:
|
101
116
|
"""Run a pipeline step in a separate Kubernetes pod.
|
@@ -112,7 +127,7 @@ def main() -> None:
|
|
112
127
|
settings.model_dump() if settings else {}
|
113
128
|
)
|
114
129
|
|
115
|
-
if settings.pod_name_prefix and not
|
130
|
+
if settings.pod_name_prefix and not orchestrator_pod_name.startswith(
|
116
131
|
settings.pod_name_prefix
|
117
132
|
):
|
118
133
|
max_length = (
|
@@ -125,7 +140,7 @@ def main() -> None:
|
|
125
140
|
)
|
126
141
|
pod_name = f"{pod_name_prefix}-{step_name}"
|
127
142
|
else:
|
128
|
-
pod_name = f"{
|
143
|
+
pod_name = f"{orchestrator_pod_name}-{step_name}"
|
129
144
|
|
130
145
|
pod_name = kube_utils.sanitize_pod_name(
|
131
146
|
pod_name, namespace=args.kubernetes_namespace
|
@@ -179,6 +194,7 @@ def main() -> None:
|
|
179
194
|
service_account_name=settings.step_pod_service_account_name
|
180
195
|
or settings.service_account_name,
|
181
196
|
mount_local_stores=mount_local_stores,
|
197
|
+
owner_references=owner_references,
|
182
198
|
)
|
183
199
|
|
184
200
|
kube_utils.create_and_wait_for_pod_to_start(
|
@@ -231,7 +247,7 @@ def main() -> None:
|
|
231
247
|
else:
|
232
248
|
# For a run triggered by a schedule, we can only use the
|
233
249
|
# orchestrator run ID to find the pipeline run.
|
234
|
-
list_args = dict(orchestrator_run_id=
|
250
|
+
list_args = dict(orchestrator_run_id=orchestrator_pod_name)
|
235
251
|
|
236
252
|
pipeline_runs = client.list_pipeline_runs(
|
237
253
|
hydrate=True,
|
@@ -107,6 +107,7 @@ def build_pod_manifest(
|
|
107
107
|
service_account_name: Optional[str] = None,
|
108
108
|
env: Optional[Dict[str, str]] = None,
|
109
109
|
mount_local_stores: bool = False,
|
110
|
+
owner_references: Optional[List[k8s_client.V1OwnerReference]] = None,
|
110
111
|
) -> k8s_client.V1Pod:
|
111
112
|
"""Build a Kubernetes pod manifest for a ZenML run or step.
|
112
113
|
|
@@ -125,6 +126,7 @@ def build_pod_manifest(
|
|
125
126
|
env: Environment variables to set.
|
126
127
|
mount_local_stores: Whether to mount the local stores path inside the
|
127
128
|
pod.
|
129
|
+
owner_references: List of owner references for the pod.
|
128
130
|
|
129
131
|
Returns:
|
130
132
|
Pod manifest.
|
@@ -180,6 +182,7 @@ def build_pod_manifest(
|
|
180
182
|
pod_metadata = k8s_client.V1ObjectMeta(
|
181
183
|
name=pod_name,
|
182
184
|
labels=labels,
|
185
|
+
owner_references=owner_references,
|
183
186
|
)
|
184
187
|
|
185
188
|
if pod_settings and pod_settings.annotations:
|
@@ -279,6 +282,9 @@ def build_cron_job_manifest(
|
|
279
282
|
service_account_name: Optional[str] = None,
|
280
283
|
env: Optional[Dict[str, str]] = None,
|
281
284
|
mount_local_stores: bool = False,
|
285
|
+
successful_jobs_history_limit: Optional[int] = None,
|
286
|
+
failed_jobs_history_limit: Optional[int] = None,
|
287
|
+
ttl_seconds_after_finished: Optional[int] = None,
|
282
288
|
) -> k8s_client.V1CronJob:
|
283
289
|
"""Create a manifest for launching a pod as scheduled CRON job.
|
284
290
|
|
@@ -298,6 +304,10 @@ def build_cron_job_manifest(
|
|
298
304
|
env: Environment variables to set.
|
299
305
|
mount_local_stores: Whether to mount the local stores path inside the
|
300
306
|
pod.
|
307
|
+
successful_jobs_history_limit: The number of successful jobs to retain.
|
308
|
+
failed_jobs_history_limit: The number of failed jobs to retain.
|
309
|
+
ttl_seconds_after_finished: The amount of seconds to keep finished jobs
|
310
|
+
before deleting them.
|
301
311
|
|
302
312
|
Returns:
|
303
313
|
CRON job manifest.
|
@@ -318,6 +328,8 @@ def build_cron_job_manifest(
|
|
318
328
|
|
319
329
|
job_spec = k8s_client.V1CronJobSpec(
|
320
330
|
schedule=cron_expression,
|
331
|
+
successful_jobs_history_limit=successful_jobs_history_limit,
|
332
|
+
failed_jobs_history_limit=failed_jobs_history_limit,
|
321
333
|
job_template=k8s_client.V1JobTemplateSpec(
|
322
334
|
metadata=pod_manifest.metadata,
|
323
335
|
spec=k8s_client.V1JobSpec(
|
@@ -325,6 +337,7 @@ def build_cron_job_manifest(
|
|
325
337
|
metadata=pod_manifest.metadata,
|
326
338
|
spec=pod_manifest.spec,
|
327
339
|
),
|
340
|
+
ttl_seconds_after_finished=ttl_seconds_after_finished,
|
328
341
|
),
|
329
342
|
),
|
330
343
|
)
|
@@ -292,6 +292,10 @@ class StepLauncher:
|
|
292
292
|
artifacts=step_run.outputs,
|
293
293
|
model_version=model_version,
|
294
294
|
)
|
295
|
+
step_run_utils.cascade_tags_for_output_artifacts(
|
296
|
+
artifacts=step_run.outputs,
|
297
|
+
tags=pipeline_run.config.tags,
|
298
|
+
)
|
295
299
|
|
296
300
|
except: # noqa: E722
|
297
301
|
logger.error(f"Pipeline run `{pipeline_run.name}` failed.")
|
@@ -13,8 +13,9 @@
|
|
13
13
|
# permissions and limitations under the License.
|
14
14
|
"""Utilities for creating step runs."""
|
15
15
|
|
16
|
-
from typing import Dict, List, Optional, Set, Tuple
|
16
|
+
from typing import Dict, List, Optional, Set, Tuple, Union
|
17
17
|
|
18
|
+
from zenml import Tag, add_tags
|
18
19
|
from zenml.client import Client
|
19
20
|
from zenml.config.step_configurations import Step
|
20
21
|
from zenml.constants import CODE_HASH_PARAMETER_NAME, TEXT_FIELD_MAX_LENGTH
|
@@ -333,6 +334,11 @@ def create_cached_step_runs(
|
|
333
334
|
model_version=model_version,
|
334
335
|
)
|
335
336
|
|
337
|
+
cascade_tags_for_output_artifacts(
|
338
|
+
artifacts=step_run.outputs,
|
339
|
+
tags=pipeline_run.config.tags,
|
340
|
+
)
|
341
|
+
|
336
342
|
logger.info("Using cached version of step `%s`.", invocation_id)
|
337
343
|
cached_invocations.add(invocation_id)
|
338
344
|
|
@@ -382,3 +388,26 @@ def link_output_artifacts_to_model_version(
|
|
382
388
|
artifact_version=output_artifact,
|
383
389
|
model_version=model_version,
|
384
390
|
)
|
391
|
+
|
392
|
+
|
393
|
+
def cascade_tags_for_output_artifacts(
|
394
|
+
artifacts: Dict[str, List[ArtifactVersionResponse]],
|
395
|
+
tags: Optional[List[Union[str, Tag]]] = None,
|
396
|
+
) -> None:
|
397
|
+
"""Tag the outputs of a step run.
|
398
|
+
|
399
|
+
Args:
|
400
|
+
artifacts: The step output artifacts.
|
401
|
+
tags: The tags to add to the artifacts.
|
402
|
+
"""
|
403
|
+
if tags is None:
|
404
|
+
return
|
405
|
+
|
406
|
+
cascade_tags = [t for t in tags if isinstance(t, Tag) and t.cascade]
|
407
|
+
|
408
|
+
for output_artifacts in artifacts.values():
|
409
|
+
for output_artifact in output_artifacts:
|
410
|
+
add_tags(
|
411
|
+
tags=[t.name for t in cascade_tags],
|
412
|
+
artifact_version_id=output_artifact.id,
|
413
|
+
)
|
zenml/pipelines/build_utils.py
CHANGED
@@ -80,6 +80,11 @@ def requires_included_code(
|
|
80
80
|
for step in deployment.step_configurations.values():
|
81
81
|
docker_settings = step.config.docker_settings
|
82
82
|
|
83
|
+
if docker_settings.local_project_install_command:
|
84
|
+
# When installing a local package, we need to include the code
|
85
|
+
# files in the container image.
|
86
|
+
return True
|
87
|
+
|
83
88
|
if docker_settings.allow_download_from_artifact_store:
|
84
89
|
return False
|
85
90
|
|
@@ -136,6 +141,9 @@ def code_download_possible(
|
|
136
141
|
Whether code download is possible for the deployment.
|
137
142
|
"""
|
138
143
|
for step in deployment.step_configurations.values():
|
144
|
+
if step.config.docker_settings.local_project_install_command:
|
145
|
+
return False
|
146
|
+
|
139
147
|
if step.config.docker_settings.allow_download_from_artifact_store:
|
140
148
|
continue
|
141
149
|
|