zenml-nightly 0.80.2.dev20250414__py3-none-any.whl → 0.80.2.dev20250415__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/artifacts/utils.py +7 -2
- zenml/config/server_config.py +7 -0
- zenml/constants.py +5 -0
- zenml/integrations/kubernetes/flavors/kubernetes_step_operator_flavor.py +12 -0
- zenml/integrations/kubernetes/orchestrators/kube_utils.py +92 -0
- zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator.py +13 -3
- zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator_entrypoint.py +12 -65
- zenml/integrations/kubernetes/step_operators/kubernetes_step_operator.py +14 -3
- zenml/materializers/path_materializer.py +17 -2
- zenml/utils/io_utils.py +23 -0
- zenml/zen_server/auth.py +52 -0
- zenml/zen_server/cloud_utils.py +7 -1
- zenml/zen_server/download_utils.py +126 -0
- zenml/zen_server/rbac/rbac_interface.py +10 -3
- zenml/zen_server/rbac/utils.py +13 -3
- zenml/zen_server/rbac/zenml_cloud_rbac.py +14 -8
- zenml/zen_server/routers/artifact_version_endpoints.py +86 -3
- zenml/zen_server/routers/users_endpoints.py +13 -8
- zenml/zen_server/template_execution/utils.py +2 -2
- zenml/zen_stores/migrations/versions/ff538a321a92_migrate_onboarding_state.py +123 -0
- zenml/zen_stores/schemas/server_settings_schemas.py +4 -1
- {zenml_nightly-0.80.2.dev20250414.dist-info → zenml_nightly-0.80.2.dev20250415.dist-info}/METADATA +1 -1
- {zenml_nightly-0.80.2.dev20250414.dist-info → zenml_nightly-0.80.2.dev20250415.dist-info}/RECORD +27 -25
- {zenml_nightly-0.80.2.dev20250414.dist-info → zenml_nightly-0.80.2.dev20250415.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.80.2.dev20250414.dist-info → zenml_nightly-0.80.2.dev20250415.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.80.2.dev20250414.dist-info → zenml_nightly-0.80.2.dev20250415.dist-info}/entry_points.txt +0 -0
zenml/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.80.2.
|
1
|
+
0.80.2.dev20250415
|
zenml/artifacts/utils.py
CHANGED
@@ -35,7 +35,9 @@ from zenml.artifacts.preexisting_data_materializer import (
|
|
35
35
|
PreexistingDataMaterializer,
|
36
36
|
)
|
37
37
|
from zenml.client import Client
|
38
|
-
from zenml.constants import
|
38
|
+
from zenml.constants import (
|
39
|
+
MODEL_METADATA_YAML_FILE_NAME,
|
40
|
+
)
|
39
41
|
from zenml.enums import (
|
40
42
|
ArtifactSaveType,
|
41
43
|
ArtifactType,
|
@@ -43,7 +45,10 @@ from zenml.enums import (
|
|
43
45
|
StackComponentType,
|
44
46
|
VisualizationType,
|
45
47
|
)
|
46
|
-
from zenml.exceptions import
|
48
|
+
from zenml.exceptions import (
|
49
|
+
DoesNotExistException,
|
50
|
+
StepContextError,
|
51
|
+
)
|
47
52
|
from zenml.io import fileio
|
48
53
|
from zenml.logger import get_logger
|
49
54
|
from zenml.metadata.metadata_types import validate_metadata
|
zenml/config/server_config.py
CHANGED
@@ -34,6 +34,7 @@ from zenml.constants import (
|
|
34
34
|
DEFAULT_ZENML_JWT_TOKEN_LEEWAY,
|
35
35
|
DEFAULT_ZENML_SERVER_DEVICE_AUTH_POLLING,
|
36
36
|
DEFAULT_ZENML_SERVER_DEVICE_AUTH_TIMEOUT,
|
37
|
+
DEFAULT_ZENML_SERVER_FILE_DOWNLOAD_SIZE_LIMIT,
|
37
38
|
DEFAULT_ZENML_SERVER_GENERIC_API_TOKEN_LIFETIME,
|
38
39
|
DEFAULT_ZENML_SERVER_GENERIC_API_TOKEN_MAX_LIFETIME,
|
39
40
|
DEFAULT_ZENML_SERVER_LOGIN_RATE_LIMIT_DAY,
|
@@ -245,6 +246,8 @@ class ServerConfiguration(BaseModel):
|
|
245
246
|
memcache_default_expiry: The default expiry time in seconds for cache
|
246
247
|
entries. If not specified, the default value of 30 seconds will be
|
247
248
|
used.
|
249
|
+
file_download_size_limit: The maximum size of the file download in
|
250
|
+
bytes. If not specified, the default value of 2GB will be used.
|
248
251
|
"""
|
249
252
|
|
250
253
|
deployment_type: ServerDeploymentType = ServerDeploymentType.OTHER
|
@@ -346,6 +349,10 @@ class ServerConfiguration(BaseModel):
|
|
346
349
|
memcache_max_capacity: int = 1000
|
347
350
|
memcache_default_expiry: int = 30
|
348
351
|
|
352
|
+
file_download_size_limit: int = (
|
353
|
+
DEFAULT_ZENML_SERVER_FILE_DOWNLOAD_SIZE_LIMIT
|
354
|
+
)
|
355
|
+
|
349
356
|
_deployment_id: Optional[UUID] = None
|
350
357
|
|
351
358
|
@model_validator(mode="before")
|
zenml/constants.py
CHANGED
@@ -192,12 +192,14 @@ ENV_ZENML_SERVER_PRO_PREFIX = "ZENML_SERVER_PRO_"
|
|
192
192
|
ENV_ZENML_SERVER_DEPLOYMENT_TYPE = f"{ENV_ZENML_SERVER_PREFIX}DEPLOYMENT_TYPE"
|
193
193
|
ENV_ZENML_SERVER_AUTH_SCHEME = f"{ENV_ZENML_SERVER_PREFIX}AUTH_SCHEME"
|
194
194
|
ENV_ZENML_SERVER_AUTO_ACTIVATE = f"{ENV_ZENML_SERVER_PREFIX}AUTO_ACTIVATE"
|
195
|
+
|
195
196
|
ENV_ZENML_RUN_SINGLE_STEPS_WITHOUT_STACK = (
|
196
197
|
"ZENML_RUN_SINGLE_STEPS_WITHOUT_STACK"
|
197
198
|
)
|
198
199
|
ENV_ZENML_PREVENT_CLIENT_SIDE_CACHING = "ZENML_PREVENT_CLIENT_SIDE_CACHING"
|
199
200
|
ENV_ZENML_DISABLE_CREDENTIALS_DISK_CACHING = "DISABLE_CREDENTIALS_DISK_CACHING"
|
200
201
|
ENV_ZENML_RUNNER_IMAGE_DISABLE_UV = "ZENML_RUNNER_IMAGE_DISABLE_UV"
|
202
|
+
|
201
203
|
# Logging variables
|
202
204
|
IS_DEBUG_ENV: bool = handle_bool_env_var(ENV_ZENML_DEBUG, default=False)
|
203
205
|
|
@@ -284,6 +286,7 @@ DEFAULT_ZENML_SERVER_GENERIC_API_TOKEN_LIFETIME = 60 * 60 # 1 hour
|
|
284
286
|
DEFAULT_ZENML_SERVER_GENERIC_API_TOKEN_MAX_LIFETIME = (
|
285
287
|
60 * 60 * 24 * 7
|
286
288
|
) # 7 days
|
289
|
+
DEFAULT_ZENML_SERVER_FILE_DOWNLOAD_SIZE_LIMIT = 2 * 1024 * 1024 * 1024 # 20 GB
|
287
290
|
|
288
291
|
DEFAULT_ZENML_SERVER_SECURE_HEADERS_HSTS = (
|
289
292
|
"max-age=63072000; includeSubdomains"
|
@@ -350,10 +353,12 @@ CODE_REPOSITORIES = "/code_repositories"
|
|
350
353
|
COMPONENT_TYPES = "/component-types"
|
351
354
|
CONFIG = "/config"
|
352
355
|
CURRENT_USER = "/current-user"
|
356
|
+
DATA = "/data"
|
353
357
|
DEACTIVATE = "/deactivate"
|
354
358
|
DEVICES = "/devices"
|
355
359
|
DEVICE_AUTHORIZATION = "/device_authorization"
|
356
360
|
DEVICE_VERIFY = "/verify"
|
361
|
+
DOWNLOAD_TOKEN = "/download-token"
|
357
362
|
EMAIL_ANALYTICS = "/email-opt-in"
|
358
363
|
EVENT_FLAVORS = "/event-flavors"
|
359
364
|
EVENT_SOURCES = "/event-sources"
|
@@ -35,11 +35,23 @@ class KubernetesStepOperatorSettings(BaseSettings):
|
|
35
35
|
pod_settings: Pod settings to apply to pods executing the steps.
|
36
36
|
service_account_name: Name of the service account to use for the pod.
|
37
37
|
privileged: If the container should be run in privileged mode.
|
38
|
+
pod_startup_timeout: The maximum time to wait for a pending step pod to
|
39
|
+
start (in seconds).
|
40
|
+
pod_failure_max_retries: The maximum number of times to retry a step
|
41
|
+
pod if the step Kubernetes pod fails to start
|
42
|
+
pod_failure_retry_delay: The delay in seconds between pod
|
43
|
+
failure retries and pod startup retries (in seconds)
|
44
|
+
pod_failure_backoff: The backoff factor for pod failure retries and
|
45
|
+
pod startup retries.
|
38
46
|
"""
|
39
47
|
|
40
48
|
pod_settings: Optional[KubernetesPodSettings] = None
|
41
49
|
service_account_name: Optional[str] = None
|
42
50
|
privileged: bool = False
|
51
|
+
pod_startup_timeout: int = 60 * 10 # Default 10 minutes
|
52
|
+
pod_failure_max_retries: int = 3
|
53
|
+
pod_failure_retry_delay: int = 10
|
54
|
+
pod_failure_backoff: float = 1.0
|
43
55
|
|
44
56
|
|
45
57
|
class KubernetesStepOperatorConfig(
|
@@ -462,3 +462,95 @@ def delete_secret(
|
|
462
462
|
name=secret_name,
|
463
463
|
namespace=namespace,
|
464
464
|
)
|
465
|
+
|
466
|
+
|
467
|
+
def create_and_wait_for_pod_to_start(
|
468
|
+
core_api: k8s_client.CoreV1Api,
|
469
|
+
pod_display_name: str,
|
470
|
+
pod_name: str,
|
471
|
+
pod_manifest: k8s_client.V1Pod,
|
472
|
+
namespace: str,
|
473
|
+
startup_max_retries: int,
|
474
|
+
startup_failure_delay: float,
|
475
|
+
startup_failure_backoff: float,
|
476
|
+
startup_timeout: float,
|
477
|
+
) -> None:
|
478
|
+
"""Create a pod and wait for it to reach a desired state.
|
479
|
+
|
480
|
+
Args:
|
481
|
+
core_api: Client of Core V1 API of Kubernetes API.
|
482
|
+
pod_display_name: The display name of the pod to use in logs.
|
483
|
+
pod_name: The name of the pod to create.
|
484
|
+
pod_manifest: The manifest of the pod to create.
|
485
|
+
namespace: The namespace in which to create the pod.
|
486
|
+
startup_max_retries: The maximum number of retries for the pod startup.
|
487
|
+
startup_failure_delay: The delay between retries for the pod startup.
|
488
|
+
startup_failure_backoff: The backoff factor for the pod startup.
|
489
|
+
startup_timeout: The maximum time to wait for the pod to start.
|
490
|
+
|
491
|
+
Raises:
|
492
|
+
TimeoutError: If the pod is still in a pending state after the maximum
|
493
|
+
wait time has elapsed.
|
494
|
+
Exception: If the pod fails to start after the maximum number of
|
495
|
+
retries.
|
496
|
+
"""
|
497
|
+
retries = 0
|
498
|
+
|
499
|
+
while retries < startup_max_retries:
|
500
|
+
try:
|
501
|
+
# Create and run pod.
|
502
|
+
core_api.create_namespaced_pod(
|
503
|
+
namespace=namespace,
|
504
|
+
body=pod_manifest,
|
505
|
+
)
|
506
|
+
break
|
507
|
+
except Exception as e:
|
508
|
+
retries += 1
|
509
|
+
if retries < startup_max_retries:
|
510
|
+
logger.debug(f"The {pod_display_name} failed to start: {e}")
|
511
|
+
logger.error(
|
512
|
+
f"Failed to create {pod_display_name}. "
|
513
|
+
f"Retrying in {startup_failure_delay} seconds..."
|
514
|
+
)
|
515
|
+
time.sleep(startup_failure_delay)
|
516
|
+
startup_failure_delay *= startup_failure_backoff
|
517
|
+
else:
|
518
|
+
logger.error(
|
519
|
+
f"Failed to create {pod_display_name} after "
|
520
|
+
f"{startup_max_retries} retries. Exiting."
|
521
|
+
)
|
522
|
+
raise
|
523
|
+
|
524
|
+
# Wait for pod to start
|
525
|
+
logger.info(f"Waiting for {pod_display_name} to start...")
|
526
|
+
max_wait = startup_timeout
|
527
|
+
total_wait: float = 0
|
528
|
+
delay = startup_failure_delay
|
529
|
+
while True:
|
530
|
+
pod = get_pod(
|
531
|
+
core_api=core_api,
|
532
|
+
pod_name=pod_name,
|
533
|
+
namespace=namespace,
|
534
|
+
)
|
535
|
+
if not pod or pod_is_not_pending(pod):
|
536
|
+
break
|
537
|
+
if total_wait >= max_wait:
|
538
|
+
# Have to delete the pending pod so it doesn't start running
|
539
|
+
# later on.
|
540
|
+
try:
|
541
|
+
core_api.delete_namespaced_pod(
|
542
|
+
name=pod_name,
|
543
|
+
namespace=namespace,
|
544
|
+
)
|
545
|
+
except Exception:
|
546
|
+
pass
|
547
|
+
raise TimeoutError(
|
548
|
+
f"The {pod_display_name} is still in a pending state "
|
549
|
+
f"after {total_wait} seconds. Exiting."
|
550
|
+
)
|
551
|
+
|
552
|
+
if total_wait + delay > max_wait:
|
553
|
+
delay = max_wait - total_wait
|
554
|
+
total_wait += delay
|
555
|
+
time.sleep(delay)
|
556
|
+
delay *= startup_failure_backoff
|
@@ -543,14 +543,24 @@ class KubernetesOrchestrator(ContainerizedOrchestrator):
|
|
543
543
|
mount_local_stores=self.config.is_local,
|
544
544
|
)
|
545
545
|
|
546
|
-
|
546
|
+
logger.info("Waiting for Kubernetes orchestrator pod to start...")
|
547
|
+
kube_utils.create_and_wait_for_pod_to_start(
|
548
|
+
core_api=self._k8s_core_api,
|
549
|
+
pod_display_name="Kubernetes orchestrator pod",
|
550
|
+
pod_name=pod_name,
|
551
|
+
pod_manifest=pod_manifest,
|
547
552
|
namespace=self.config.kubernetes_namespace,
|
548
|
-
|
553
|
+
startup_max_retries=settings.pod_failure_max_retries,
|
554
|
+
startup_failure_delay=settings.pod_failure_retry_delay,
|
555
|
+
startup_failure_backoff=settings.pod_failure_backoff,
|
556
|
+
startup_timeout=settings.pod_startup_timeout,
|
549
557
|
)
|
550
558
|
|
551
559
|
# Wait for the orchestrator pod to finish and stream logs.
|
552
560
|
if settings.synchronous:
|
553
|
-
logger.info(
|
561
|
+
logger.info(
|
562
|
+
"Waiting for Kubernetes orchestrator pod to finish..."
|
563
|
+
)
|
554
564
|
kube_utils.wait_pod(
|
555
565
|
kube_client_fn=self.get_kube_client,
|
556
566
|
pod_name=pod_name,
|
@@ -15,7 +15,6 @@
|
|
15
15
|
|
16
16
|
import argparse
|
17
17
|
import socket
|
18
|
-
import time
|
19
18
|
from typing import Any, Dict
|
20
19
|
from uuid import UUID
|
21
20
|
|
@@ -103,8 +102,6 @@ def main() -> None:
|
|
103
102
|
|
104
103
|
Raises:
|
105
104
|
Exception: If the pod fails to start.
|
106
|
-
TimeoutError: If the pod is still in a pending state after the
|
107
|
-
maximum wait time has elapsed.
|
108
105
|
"""
|
109
106
|
# Define Kubernetes pod name.
|
110
107
|
pod_name = f"{orchestrator_run_id}-{step_name}"
|
@@ -176,68 +173,18 @@ def main() -> None:
|
|
176
173
|
mount_local_stores=mount_local_stores,
|
177
174
|
)
|
178
175
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
break
|
192
|
-
except Exception as e:
|
193
|
-
retries += 1
|
194
|
-
if retries < max_retries:
|
195
|
-
logger.debug(
|
196
|
-
f"Pod for step `{step_name}` failed to start: {e}"
|
197
|
-
)
|
198
|
-
logger.error(
|
199
|
-
f"Failed to create pod for step `{step_name}`. "
|
200
|
-
f"Retrying in {delay} seconds..."
|
201
|
-
)
|
202
|
-
time.sleep(delay)
|
203
|
-
delay *= backoff
|
204
|
-
else:
|
205
|
-
logger.error(
|
206
|
-
f"Failed to create pod for step `{step_name}` after "
|
207
|
-
f"{max_retries} retries. Exiting."
|
208
|
-
)
|
209
|
-
raise
|
210
|
-
|
211
|
-
# Wait for pod to start
|
212
|
-
max_wait = settings.pod_startup_timeout
|
213
|
-
total_wait: float = 0
|
214
|
-
delay = settings.pod_failure_retry_delay
|
215
|
-
while True:
|
216
|
-
pod = kube_utils.get_pod(
|
217
|
-
core_api, pod_name, args.kubernetes_namespace
|
218
|
-
)
|
219
|
-
if not pod or kube_utils.pod_is_not_pending(pod):
|
220
|
-
break
|
221
|
-
if total_wait >= max_wait:
|
222
|
-
# Have to delete the pending pod so it doesn't start running
|
223
|
-
# later on.
|
224
|
-
try:
|
225
|
-
core_api.delete_namespaced_pod(
|
226
|
-
name=pod_name,
|
227
|
-
namespace=args.kubernetes_namespace,
|
228
|
-
)
|
229
|
-
except Exception:
|
230
|
-
pass
|
231
|
-
raise TimeoutError(
|
232
|
-
f"Pod for step `{step_name}` is still in a pending state "
|
233
|
-
f"after {total_wait} seconds. Exiting."
|
234
|
-
)
|
235
|
-
|
236
|
-
if total_wait + delay > max_wait:
|
237
|
-
delay = max_wait - total_wait
|
238
|
-
total_wait += delay
|
239
|
-
time.sleep(delay)
|
240
|
-
delay *= backoff
|
176
|
+
logger.info(f"Waiting for pod of step `{step_name}` to start...")
|
177
|
+
kube_utils.create_and_wait_for_pod_to_start(
|
178
|
+
core_api=core_api,
|
179
|
+
pod_display_name=f"pod for step `{step_name}`",
|
180
|
+
pod_name=pod_name,
|
181
|
+
pod_manifest=pod_manifest,
|
182
|
+
namespace=args.kubernetes_namespace,
|
183
|
+
startup_max_retries=settings.pod_failure_max_retries,
|
184
|
+
startup_failure_delay=settings.pod_failure_retry_delay,
|
185
|
+
startup_failure_backoff=settings.pod_failure_backoff,
|
186
|
+
startup_timeout=settings.pod_startup_timeout,
|
187
|
+
)
|
241
188
|
|
242
189
|
# Wait for pod to finish.
|
243
190
|
logger.info(f"Waiting for pod of step `{step_name}` to finish...")
|
@@ -218,13 +218,24 @@ class KubernetesStepOperator(BaseStepOperator):
|
|
218
218
|
mount_local_stores=False,
|
219
219
|
)
|
220
220
|
|
221
|
-
|
221
|
+
logger.info(
|
222
|
+
"Waiting for pod of step `%s` to start...", info.pipeline_step_name
|
223
|
+
)
|
224
|
+
kube_utils.create_and_wait_for_pod_to_start(
|
225
|
+
core_api=self._k8s_core_api,
|
226
|
+
pod_display_name=f"pod of step `{info.pipeline_step_name}`",
|
227
|
+
pod_name=pod_name,
|
228
|
+
pod_manifest=pod_manifest,
|
222
229
|
namespace=self.config.kubernetes_namespace,
|
223
|
-
|
230
|
+
startup_max_retries=settings.pod_failure_max_retries,
|
231
|
+
startup_failure_delay=settings.pod_failure_retry_delay,
|
232
|
+
startup_failure_backoff=settings.pod_failure_backoff,
|
233
|
+
startup_timeout=settings.pod_startup_timeout,
|
224
234
|
)
|
225
235
|
|
226
236
|
logger.info(
|
227
|
-
"Waiting for pod of step `%s` to
|
237
|
+
"Waiting for pod of step `%s` to finish...",
|
238
|
+
info.pipeline_step_name,
|
228
239
|
)
|
229
240
|
kube_utils.wait_pod(
|
230
241
|
kube_client_fn=self.get_kube_client,
|
@@ -26,6 +26,7 @@ from zenml.constants import (
|
|
26
26
|
from zenml.enums import ArtifactType
|
27
27
|
from zenml.io import fileio
|
28
28
|
from zenml.materializers.base_materializer import BaseMaterializer
|
29
|
+
from zenml.utils.io_utils import is_path_within_directory
|
29
30
|
|
30
31
|
|
31
32
|
class PathMaterializer(BaseMaterializer):
|
@@ -71,7 +72,15 @@ class PathMaterializer(BaseMaterializer):
|
|
71
72
|
|
72
73
|
# Extract the archive to the temporary directory
|
73
74
|
with tarfile.open(archive_path_local, "r:gz") as tar:
|
74
|
-
|
75
|
+
# Validate archive members to prevent path traversal attacks
|
76
|
+
# Filter members to only those with safe paths
|
77
|
+
safe_members = []
|
78
|
+
for member in tar.getmembers():
|
79
|
+
if is_path_within_directory(member.name, directory):
|
80
|
+
safe_members.append(member)
|
81
|
+
|
82
|
+
# Extract only safe members
|
83
|
+
tar.extractall(path=directory, members=safe_members) # nosec B202 - members are filtered through is_path_within_directory
|
75
84
|
|
76
85
|
# Clean up the archive file
|
77
86
|
os.remove(archive_path_local)
|
@@ -93,8 +102,14 @@ class PathMaterializer(BaseMaterializer):
|
|
93
102
|
|
94
103
|
Args:
|
95
104
|
data: Path to a local directory or file to store. Must be a Path object.
|
105
|
+
|
106
|
+
Raises:
|
107
|
+
TypeError: If data is not a Path object.
|
96
108
|
"""
|
97
|
-
|
109
|
+
if not isinstance(data, Path):
|
110
|
+
raise TypeError(
|
111
|
+
f"Expected a Path object, got {type(data).__name__}"
|
112
|
+
)
|
98
113
|
|
99
114
|
if data.is_dir():
|
100
115
|
# Handle directory artifact
|
zenml/utils/io_utils.py
CHANGED
@@ -205,6 +205,29 @@ def resolve_relative_path(path: str) -> str:
|
|
205
205
|
return str(Path(path).resolve())
|
206
206
|
|
207
207
|
|
208
|
+
def is_path_within_directory(path: str, directory: str) -> bool:
|
209
|
+
"""Checks if a path is contained within a given directory.
|
210
|
+
|
211
|
+
This utility function verifies that a path (absolute or relative) resolves
|
212
|
+
to a location that is within the specified directory. This is useful for
|
213
|
+
security checks such as preventing path traversal attacks when extracting
|
214
|
+
archives (CVE-2007-4559) or whenever path containment needs to be verified.
|
215
|
+
|
216
|
+
Args:
|
217
|
+
path: The path to check (can be relative or absolute).
|
218
|
+
directory: The directory that should contain the path.
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
Boolean indicating whether the path is contained within the directory (True)
|
222
|
+
or not (False).
|
223
|
+
"""
|
224
|
+
# Convert to absolute path, ensuring it's normalized
|
225
|
+
abs_path = os.path.abspath(os.path.join(directory, path))
|
226
|
+
# Check if the path is within the target directory
|
227
|
+
dir_abs = os.path.abspath(directory)
|
228
|
+
return abs_path.startswith(dir_abs + os.sep) or abs_path == dir_abs
|
229
|
+
|
230
|
+
|
208
231
|
def move(source: str, destination: str, overwrite: bool = False) -> None:
|
209
232
|
"""Moves dir or file from source to destination. Can be used to rename.
|
210
233
|
|
zenml/zen_server/auth.py
CHANGED
@@ -983,6 +983,58 @@ def generate_access_token(
|
|
983
983
|
)
|
984
984
|
|
985
985
|
|
986
|
+
def generate_artifact_download_token(artifact_version_id: UUID) -> str:
|
987
|
+
"""Generate a JWT token for artifact download.
|
988
|
+
|
989
|
+
Args:
|
990
|
+
artifact_version_id: The ID of the artifact version to download.
|
991
|
+
|
992
|
+
Returns:
|
993
|
+
The JWT token for the artifact download.
|
994
|
+
"""
|
995
|
+
import jwt
|
996
|
+
|
997
|
+
config = server_config()
|
998
|
+
|
999
|
+
return jwt.encode(
|
1000
|
+
{
|
1001
|
+
"exp": utc_now() + timedelta(seconds=30),
|
1002
|
+
"artifact_version_id": str(artifact_version_id),
|
1003
|
+
},
|
1004
|
+
key=config.jwt_secret_key,
|
1005
|
+
algorithm=config.jwt_token_algorithm,
|
1006
|
+
)
|
1007
|
+
|
1008
|
+
|
1009
|
+
def verify_artifact_download_token(
|
1010
|
+
token: str, artifact_version_id: UUID
|
1011
|
+
) -> None:
|
1012
|
+
"""Verify a JWT token for artifact download.
|
1013
|
+
|
1014
|
+
Args:
|
1015
|
+
token: The JWT token to verify.
|
1016
|
+
artifact_version_id: The ID of the artifact version to download.
|
1017
|
+
|
1018
|
+
Raises:
|
1019
|
+
CredentialsNotValid: If the token is invalid or the artifact version
|
1020
|
+
ID does not match.
|
1021
|
+
"""
|
1022
|
+
import jwt
|
1023
|
+
|
1024
|
+
config = server_config()
|
1025
|
+
try:
|
1026
|
+
claims = jwt.decode(
|
1027
|
+
token,
|
1028
|
+
config.jwt_secret_key,
|
1029
|
+
algorithms=[config.jwt_token_algorithm],
|
1030
|
+
)
|
1031
|
+
except jwt.PyJWTError as e:
|
1032
|
+
raise CredentialsNotValid(f"Invalid JWT token: {e}") from e
|
1033
|
+
|
1034
|
+
if claims["artifact_version_id"] != str(artifact_version_id):
|
1035
|
+
raise CredentialsNotValid("Invalid artifact version ID")
|
1036
|
+
|
1037
|
+
|
986
1038
|
def http_authentication(
|
987
1039
|
credentials: HTTPBasicCredentials = Depends(HTTPBasic()),
|
988
1040
|
) -> AuthContext:
|
zenml/zen_server/cloud_utils.py
CHANGED
@@ -7,7 +7,10 @@ import requests
|
|
7
7
|
from requests.adapters import HTTPAdapter, Retry
|
8
8
|
|
9
9
|
from zenml.config.server_config import ServerProConfiguration
|
10
|
-
from zenml.exceptions import
|
10
|
+
from zenml.exceptions import (
|
11
|
+
IllegalOperationError,
|
12
|
+
SubscriptionUpgradeRequiredError,
|
13
|
+
)
|
11
14
|
from zenml.utils.time_utils import utc_now
|
12
15
|
from zenml.zen_server.utils import get_zenml_headers, server_config
|
13
16
|
|
@@ -43,6 +46,7 @@ class ZenMLCloudConnection:
|
|
43
46
|
Raises:
|
44
47
|
SubscriptionUpgradeRequiredError: If the current subscription tier
|
45
48
|
is insufficient for the attempted operation.
|
49
|
+
IllegalOperationError: If the request failed with a 403 status code.
|
46
50
|
RuntimeError: If the request failed.
|
47
51
|
|
48
52
|
Returns:
|
@@ -65,6 +69,8 @@ class ZenMLCloudConnection:
|
|
65
69
|
except requests.HTTPError as e:
|
66
70
|
if response.status_code == 402:
|
67
71
|
raise SubscriptionUpgradeRequiredError(response.json())
|
72
|
+
elif response.status_code == 403:
|
73
|
+
raise IllegalOperationError(response.json())
|
68
74
|
else:
|
69
75
|
raise RuntimeError(
|
70
76
|
f"Failed while trying to contact the central zenml pro "
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# Copyright (c) ZenML GmbH 2025. 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
|
+
"""Utility functions for downloading artifacts."""
|
15
|
+
|
16
|
+
import os
|
17
|
+
import tarfile
|
18
|
+
import tempfile
|
19
|
+
from typing import (
|
20
|
+
TYPE_CHECKING,
|
21
|
+
Optional,
|
22
|
+
)
|
23
|
+
|
24
|
+
from zenml.artifacts.utils import _load_artifact_store
|
25
|
+
from zenml.exceptions import (
|
26
|
+
IllegalOperationError,
|
27
|
+
)
|
28
|
+
from zenml.models import (
|
29
|
+
ArtifactVersionResponse,
|
30
|
+
)
|
31
|
+
from zenml.zen_server.utils import server_config, zen_store
|
32
|
+
|
33
|
+
if TYPE_CHECKING:
|
34
|
+
from zenml.artifact_stores.base_artifact_store import BaseArtifactStore
|
35
|
+
|
36
|
+
|
37
|
+
def verify_artifact_is_downloadable(
|
38
|
+
artifact: "ArtifactVersionResponse",
|
39
|
+
) -> "BaseArtifactStore":
|
40
|
+
"""Verify that the given artifact is downloadable.
|
41
|
+
|
42
|
+
Args:
|
43
|
+
artifact: The artifact to verify.
|
44
|
+
|
45
|
+
Raises:
|
46
|
+
IllegalOperationError: If the artifact is too large to be archived.
|
47
|
+
KeyError: If the artifact store is not found or the artifact URI does
|
48
|
+
not exist.
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
The artifact store.
|
52
|
+
"""
|
53
|
+
if not artifact.artifact_store_id:
|
54
|
+
raise KeyError(
|
55
|
+
f"Artifact '{artifact.id}' cannot be downloaded because the "
|
56
|
+
"underlying artifact store was deleted."
|
57
|
+
)
|
58
|
+
|
59
|
+
artifact_store = _load_artifact_store(
|
60
|
+
artifact_store_id=artifact.artifact_store_id, zen_store=zen_store()
|
61
|
+
)
|
62
|
+
|
63
|
+
if not artifact_store.exists(artifact.uri):
|
64
|
+
raise KeyError(f"The artifact URI '{artifact.uri}' does not exist.")
|
65
|
+
|
66
|
+
size = artifact_store.size(artifact.uri)
|
67
|
+
max_download_size = server_config().file_download_size_limit
|
68
|
+
|
69
|
+
if size and size > max_download_size:
|
70
|
+
raise IllegalOperationError(
|
71
|
+
f"The artifact '{artifact.id}' is too large to be downloaded. "
|
72
|
+
f"The maximum download size allowed by your ZenML server is "
|
73
|
+
f"{max_download_size} bytes."
|
74
|
+
)
|
75
|
+
|
76
|
+
return artifact_store
|
77
|
+
|
78
|
+
|
79
|
+
def create_artifact_archive(
|
80
|
+
artifact: "ArtifactVersionResponse",
|
81
|
+
archive_path: Optional[str] = None,
|
82
|
+
) -> str:
|
83
|
+
"""Create an archive of the given artifact.
|
84
|
+
|
85
|
+
Args:
|
86
|
+
artifact: The artifact to archive.
|
87
|
+
archive_path: The path to which to save the archive.
|
88
|
+
|
89
|
+
Returns:
|
90
|
+
The path to the created archive.
|
91
|
+
"""
|
92
|
+
if archive_path is None:
|
93
|
+
archive_path = tempfile.mktemp()
|
94
|
+
|
95
|
+
artifact_store = verify_artifact_is_downloadable(artifact)
|
96
|
+
|
97
|
+
def _prepare_tarinfo(path: str) -> tarfile.TarInfo:
|
98
|
+
archive_path = os.path.relpath(path, artifact.uri)
|
99
|
+
tarinfo = tarfile.TarInfo(name=archive_path)
|
100
|
+
if size := artifact_store.size(path):
|
101
|
+
tarinfo.size = size
|
102
|
+
return tarinfo
|
103
|
+
|
104
|
+
with tarfile.open(name=archive_path, mode="w:gz") as tar:
|
105
|
+
if artifact_store.isdir(artifact.uri):
|
106
|
+
for dir, _, files in artifact_store.walk(artifact.uri):
|
107
|
+
dir = dir.decode() if isinstance(dir, bytes) else dir
|
108
|
+
dir_info = tarfile.TarInfo(
|
109
|
+
name=os.path.relpath(dir, artifact.uri)
|
110
|
+
)
|
111
|
+
dir_info.type = tarfile.DIRTYPE
|
112
|
+
dir_info.mode = 0o755
|
113
|
+
tar.addfile(dir_info)
|
114
|
+
|
115
|
+
for file in files:
|
116
|
+
file = file.decode() if isinstance(file, bytes) else file
|
117
|
+
path = os.path.join(dir, file)
|
118
|
+
tarinfo = _prepare_tarinfo(path)
|
119
|
+
with artifact_store.open(path, "rb") as f:
|
120
|
+
tar.addfile(tarinfo, fileobj=f)
|
121
|
+
else:
|
122
|
+
tarinfo = _prepare_tarinfo(artifact.uri)
|
123
|
+
with artifact_store.open(artifact.uri, "rb") as f:
|
124
|
+
tar.addfile(tarinfo, fileobj=f)
|
125
|
+
|
126
|
+
return archive_path
|
@@ -14,7 +14,7 @@
|
|
14
14
|
"""RBAC interface definition."""
|
15
15
|
|
16
16
|
from abc import ABC, abstractmethod
|
17
|
-
from typing import TYPE_CHECKING, Dict, List, Set, Tuple
|
17
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
18
18
|
|
19
19
|
from zenml.zen_server.rbac.models import Action, Resource
|
20
20
|
|
@@ -63,15 +63,22 @@ class RBACInterface(ABC):
|
|
63
63
|
|
64
64
|
@abstractmethod
|
65
65
|
def update_resource_membership(
|
66
|
-
self,
|
66
|
+
self,
|
67
|
+
sharing_user: "UserResponse",
|
68
|
+
resource: Resource,
|
69
|
+
actions: List[Action],
|
70
|
+
user_id: Optional[str] = None,
|
71
|
+
team_id: Optional[str] = None,
|
67
72
|
) -> None:
|
68
73
|
"""Update the resource membership of a user.
|
69
74
|
|
70
75
|
Args:
|
71
|
-
|
76
|
+
sharing_user: User that is sharing the resource.
|
72
77
|
resource: The resource.
|
73
78
|
actions: The actions that the user should be able to perform on the
|
74
79
|
resource.
|
80
|
+
user_id: ID of the user for which to update the membership.
|
81
|
+
team_id: ID of the team for which to update the membership.
|
75
82
|
"""
|
76
83
|
|
77
84
|
@abstractmethod
|
zenml/zen_server/rbac/utils.py
CHANGED
@@ -688,21 +688,31 @@ def get_schema_for_resource_type(
|
|
688
688
|
|
689
689
|
|
690
690
|
def update_resource_membership(
|
691
|
-
|
691
|
+
sharing_user: "UserResponse",
|
692
|
+
resource: Resource,
|
693
|
+
actions: List[Action],
|
694
|
+
user_id: Optional[str] = None,
|
695
|
+
team_id: Optional[str] = None,
|
692
696
|
) -> None:
|
693
697
|
"""Update the resource membership of a user.
|
694
698
|
|
695
699
|
Args:
|
696
|
-
|
700
|
+
sharing_user: User that is sharing the resource.
|
697
701
|
resource: The resource.
|
698
702
|
actions: The actions that the user should be able to perform on the
|
699
703
|
resource.
|
704
|
+
user_id: ID of the user for which to update the membership.
|
705
|
+
team_id: ID of the team for which to update the membership.
|
700
706
|
"""
|
701
707
|
if not server_config().rbac_enabled:
|
702
708
|
return
|
703
709
|
|
704
710
|
rbac().update_resource_membership(
|
705
|
-
|
711
|
+
sharing_user=sharing_user,
|
712
|
+
resource=resource,
|
713
|
+
actions=actions,
|
714
|
+
user_id=user_id,
|
715
|
+
team_id=team_id,
|
706
716
|
)
|
707
717
|
|
708
718
|
|
@@ -13,7 +13,7 @@
|
|
13
13
|
# permissions and limitations under the License.
|
14
14
|
"""Cloud RBAC implementation."""
|
15
15
|
|
16
|
-
from typing import TYPE_CHECKING, Dict, List, Set, Tuple
|
16
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
17
17
|
|
18
18
|
from zenml.zen_server.cloud_utils import cloud_connection
|
19
19
|
from zenml.zen_server.rbac.models import Action, Resource
|
@@ -117,22 +117,28 @@ class ZenMLCloudRBAC(RBACInterface):
|
|
117
117
|
return full_resource_access, allowed_ids
|
118
118
|
|
119
119
|
def update_resource_membership(
|
120
|
-
self,
|
120
|
+
self,
|
121
|
+
sharing_user: "UserResponse",
|
122
|
+
resource: Resource,
|
123
|
+
actions: List[Action],
|
124
|
+
user_id: Optional[str] = None,
|
125
|
+
team_id: Optional[str] = None,
|
121
126
|
) -> None:
|
122
127
|
"""Update the resource membership of a user.
|
123
128
|
|
124
129
|
Args:
|
125
|
-
|
130
|
+
sharing_user: User that is sharing the resource.
|
126
131
|
resource: The resource.
|
127
132
|
actions: The actions that the user should be able to perform on the
|
128
133
|
resource.
|
134
|
+
user_id: ID of the user for which to update the membership.
|
135
|
+
team_id: ID of the team for which to update the membership.
|
129
136
|
"""
|
130
|
-
|
131
|
-
# Service accounts have full permissions for now
|
132
|
-
return
|
133
|
-
|
137
|
+
assert sharing_user.external_user_id
|
134
138
|
data = {
|
135
|
-
"user_id":
|
139
|
+
"user_id": user_id,
|
140
|
+
"team_id": team_id,
|
141
|
+
"sharing_user_id": str(sharing_user.external_user_id),
|
136
142
|
"resource": str(resource),
|
137
143
|
"actions": [str(action) for action in actions],
|
138
144
|
}
|
@@ -13,13 +13,26 @@
|
|
13
13
|
# permissions and limitations under the License.
|
14
14
|
"""Endpoint definitions for artifact versions."""
|
15
15
|
|
16
|
+
import os
|
16
17
|
from typing import List, Union
|
17
18
|
from uuid import UUID
|
18
19
|
|
19
20
|
from fastapi import APIRouter, Depends, Security
|
21
|
+
from fastapi.responses import FileResponse
|
22
|
+
from starlette.background import BackgroundTask
|
20
23
|
|
21
|
-
from zenml.artifacts.utils import
|
22
|
-
|
24
|
+
from zenml.artifacts.utils import (
|
25
|
+
load_artifact_visualization,
|
26
|
+
)
|
27
|
+
from zenml.constants import (
|
28
|
+
API,
|
29
|
+
ARTIFACT_VERSIONS,
|
30
|
+
BATCH,
|
31
|
+
DATA,
|
32
|
+
DOWNLOAD_TOKEN,
|
33
|
+
VERSION_1,
|
34
|
+
VISUALIZE,
|
35
|
+
)
|
23
36
|
from zenml.models import (
|
24
37
|
ArtifactVersionFilter,
|
25
38
|
ArtifactVersionRequest,
|
@@ -28,7 +41,16 @@ from zenml.models import (
|
|
28
41
|
LoadedVisualization,
|
29
42
|
Page,
|
30
43
|
)
|
31
|
-
from zenml.zen_server.auth import
|
44
|
+
from zenml.zen_server.auth import (
|
45
|
+
AuthContext,
|
46
|
+
authorize,
|
47
|
+
generate_artifact_download_token,
|
48
|
+
verify_artifact_download_token,
|
49
|
+
)
|
50
|
+
from zenml.zen_server.download_utils import (
|
51
|
+
create_artifact_archive,
|
52
|
+
verify_artifact_is_downloadable,
|
53
|
+
)
|
32
54
|
from zenml.zen_server.exceptions import error_response
|
33
55
|
from zenml.zen_server.rbac.endpoint_utils import (
|
34
56
|
verify_permissions_and_batch_create_entity,
|
@@ -275,3 +297,64 @@ def get_artifact_visualization(
|
|
275
297
|
return load_artifact_visualization(
|
276
298
|
artifact=artifact, index=index, zen_store=store, encode_image=True
|
277
299
|
)
|
300
|
+
|
301
|
+
|
302
|
+
@artifact_version_router.get(
|
303
|
+
"/{artifact_version_id}" + DOWNLOAD_TOKEN,
|
304
|
+
responses={401: error_response, 404: error_response, 422: error_response},
|
305
|
+
)
|
306
|
+
@handle_exceptions
|
307
|
+
def get_artifact_download_token(
|
308
|
+
artifact_version_id: UUID,
|
309
|
+
_: AuthContext = Security(authorize),
|
310
|
+
) -> str:
|
311
|
+
"""Get a download token for the artifact data.
|
312
|
+
|
313
|
+
Args:
|
314
|
+
artifact_version_id: ID of the artifact version for which to get the data.
|
315
|
+
|
316
|
+
Returns:
|
317
|
+
The download token for the artifact data.
|
318
|
+
"""
|
319
|
+
artifact = verify_permissions_and_get_entity(
|
320
|
+
id=artifact_version_id, get_method=zen_store().get_artifact_version
|
321
|
+
)
|
322
|
+
verify_artifact_is_downloadable(artifact)
|
323
|
+
|
324
|
+
# The artifact download is handled in a separate tab by the browser. In this
|
325
|
+
# tab, we do not have the ability to set any headers and therefore cannot
|
326
|
+
# include the CSRF token in the request. To handle this, we instead generate
|
327
|
+
# a JWT token in this endpoint (which includes CSRF and RBAC checks) and
|
328
|
+
# then use that token to download the artifact data in a separate endpoint
|
329
|
+
# which only verifies this short-lived token.
|
330
|
+
return generate_artifact_download_token(artifact_version_id)
|
331
|
+
|
332
|
+
|
333
|
+
@artifact_version_router.get(
|
334
|
+
"/{artifact_version_id}" + DATA,
|
335
|
+
responses={401: error_response, 404: error_response, 422: error_response},
|
336
|
+
)
|
337
|
+
@handle_exceptions
|
338
|
+
def download_artifact_data(
|
339
|
+
artifact_version_id: UUID, token: str
|
340
|
+
) -> FileResponse:
|
341
|
+
"""Download the artifact data.
|
342
|
+
|
343
|
+
Args:
|
344
|
+
artifact_version_id: ID of the artifact version for which to get the data.
|
345
|
+
token: The token to authenticate the artifact download.
|
346
|
+
|
347
|
+
Returns:
|
348
|
+
The artifact data.
|
349
|
+
"""
|
350
|
+
verify_artifact_download_token(token, artifact_version_id)
|
351
|
+
|
352
|
+
artifact = zen_store().get_artifact_version(artifact_version_id)
|
353
|
+
archive_path = create_artifact_archive(artifact)
|
354
|
+
|
355
|
+
return FileResponse(
|
356
|
+
archive_path,
|
357
|
+
media_type="application/gzip",
|
358
|
+
filename=f"{artifact.name}-{artifact.version}.tar.gz",
|
359
|
+
background=BackgroundTask(os.remove, archive_path),
|
360
|
+
)
|
@@ -698,7 +698,7 @@ if server_config().auth_scheme != AuthScheme.EXTERNAL:
|
|
698
698
|
if server_config().rbac_enabled:
|
699
699
|
|
700
700
|
@router.post(
|
701
|
-
"/
|
701
|
+
"/resource_membership",
|
702
702
|
responses={
|
703
703
|
401: error_response,
|
704
704
|
404: error_response,
|
@@ -707,16 +707,16 @@ if server_config().rbac_enabled:
|
|
707
707
|
)
|
708
708
|
@handle_exceptions
|
709
709
|
def update_user_resource_membership(
|
710
|
-
user_name_or_id: Union[str, UUID],
|
711
710
|
resource_type: str,
|
712
711
|
resource_id: UUID,
|
713
712
|
actions: List[str],
|
713
|
+
user_id: Optional[str] = None,
|
714
|
+
team_id: Optional[str] = None,
|
714
715
|
auth_context: AuthContext = Security(authorize),
|
715
716
|
) -> None:
|
716
717
|
"""Updates resource memberships of a user.
|
717
718
|
|
718
719
|
Args:
|
719
|
-
user_name_or_id: Name or ID of the user.
|
720
720
|
resource_type: Type of the resource for which to update the
|
721
721
|
membership.
|
722
722
|
resource_id: ID of the resource for which to update the membership.
|
@@ -724,16 +724,19 @@ if server_config().rbac_enabled:
|
|
724
724
|
the resource. If the user currently has permissions to perform
|
725
725
|
actions which are not passed in this list, the permissions will
|
726
726
|
be removed.
|
727
|
+
user_id: ID of the user for which to update the membership.
|
728
|
+
team_id: ID of the team for which to update the membership.
|
727
729
|
auth_context: Authentication context.
|
728
730
|
|
729
731
|
Raises:
|
730
732
|
ValueError: If a user tries to update their own membership.
|
731
733
|
KeyError: If no resource with the given type and ID exists.
|
732
734
|
"""
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
735
|
+
if (
|
736
|
+
user_id
|
737
|
+
and auth_context.user.external_user_id
|
738
|
+
and user_id == str(auth_context.user.external_user_id)
|
739
|
+
):
|
737
740
|
raise ValueError(
|
738
741
|
"Not allowed to call endpoint with the authenticated user."
|
739
742
|
)
|
@@ -765,7 +768,9 @@ if server_config().rbac_enabled:
|
|
765
768
|
verify_permission_for_model(model=model, action=Action(action))
|
766
769
|
|
767
770
|
update_resource_membership(
|
768
|
-
|
771
|
+
sharing_user=auth_context.user,
|
769
772
|
resource=resource,
|
770
773
|
actions=[Action(action) for action in actions],
|
774
|
+
user_id=user_id,
|
775
|
+
team_id=team_id,
|
771
776
|
)
|
@@ -411,14 +411,14 @@ def deployment_request_from_template(
|
|
411
411
|
if unknown_parameters:
|
412
412
|
raise ValueError(
|
413
413
|
"Run configuration contains the following unknown "
|
414
|
-
f"parameters for step {
|
414
|
+
f"parameters for step {invocation_id}: {unknown_parameters}."
|
415
415
|
)
|
416
416
|
|
417
417
|
missing_parameters = required_parameters - configured_parameters
|
418
418
|
if missing_parameters:
|
419
419
|
raise ValueError(
|
420
420
|
"Run configuration is missing the following required "
|
421
|
-
f"parameters for step {
|
421
|
+
f"parameters for step {invocation_id}: {missing_parameters}."
|
422
422
|
)
|
423
423
|
|
424
424
|
step_config = StepConfiguration.model_validate(step_config_dict)
|
@@ -0,0 +1,123 @@
|
|
1
|
+
"""Migrate onboarding state [ff538a321a92].
|
2
|
+
|
3
|
+
Revision ID: ff538a321a92
|
4
|
+
Revises: 0.80.2
|
5
|
+
Create Date: 2025-04-11 09:30:03.324310
|
6
|
+
|
7
|
+
"""
|
8
|
+
|
9
|
+
import json
|
10
|
+
|
11
|
+
import sqlalchemy as sa
|
12
|
+
from alembic import op
|
13
|
+
|
14
|
+
# revision identifiers, used by Alembic.
|
15
|
+
revision = "ff538a321a92"
|
16
|
+
down_revision = "0.80.2"
|
17
|
+
branch_labels = None
|
18
|
+
depends_on = None
|
19
|
+
|
20
|
+
|
21
|
+
def upgrade() -> None:
|
22
|
+
"""Upgrade database schema and/or data, creating a new revision."""
|
23
|
+
with op.batch_alter_table("server_settings", schema=None) as batch_op:
|
24
|
+
batch_op.alter_column(
|
25
|
+
"onboarding_state",
|
26
|
+
existing_type=sa.VARCHAR(),
|
27
|
+
type_=sa.TEXT(),
|
28
|
+
existing_nullable=True,
|
29
|
+
)
|
30
|
+
|
31
|
+
connection = op.get_bind()
|
32
|
+
|
33
|
+
meta = sa.MetaData()
|
34
|
+
meta.reflect(only=("server_settings",), bind=connection)
|
35
|
+
|
36
|
+
server_settings_table = sa.Table("server_settings", meta)
|
37
|
+
|
38
|
+
existing_onboarding_state = connection.execute(
|
39
|
+
sa.select(server_settings_table.c.onboarding_state)
|
40
|
+
).scalar_one_or_none()
|
41
|
+
|
42
|
+
if not existing_onboarding_state:
|
43
|
+
return
|
44
|
+
|
45
|
+
state = json.loads(existing_onboarding_state)
|
46
|
+
|
47
|
+
meta = sa.MetaData()
|
48
|
+
meta.reflect(
|
49
|
+
only=(
|
50
|
+
"pipeline_run",
|
51
|
+
"stack_component",
|
52
|
+
"stack",
|
53
|
+
"stack_composition",
|
54
|
+
"pipeline_deployment",
|
55
|
+
),
|
56
|
+
bind=connection,
|
57
|
+
)
|
58
|
+
|
59
|
+
pipeline_run_table = sa.Table("pipeline_run", meta)
|
60
|
+
stack_component_table = sa.Table("stack_component", meta)
|
61
|
+
stack_table = sa.Table("stack", meta)
|
62
|
+
stack_composition_table = sa.Table("stack_composition", meta)
|
63
|
+
pipeline_deployment_table = sa.Table("pipeline_deployment", meta)
|
64
|
+
|
65
|
+
stack_with_remote_artifact_store_count = connection.execute(
|
66
|
+
sa.select(sa.func.count(stack_table.c.id))
|
67
|
+
.where(stack_composition_table.c.stack_id == stack_table.c.id)
|
68
|
+
.where(
|
69
|
+
stack_composition_table.c.component_id
|
70
|
+
== stack_component_table.c.id
|
71
|
+
)
|
72
|
+
.where(stack_component_table.c.flavor != "local")
|
73
|
+
.where(stack_component_table.c.type == "artifact_store")
|
74
|
+
).scalar()
|
75
|
+
if (
|
76
|
+
stack_with_remote_artifact_store_count
|
77
|
+
and stack_with_remote_artifact_store_count > 0
|
78
|
+
):
|
79
|
+
state.append("stack_with_remote_artifact_store_created")
|
80
|
+
|
81
|
+
pipeline_run_with_remote_artifact_store_count = connection.execute(
|
82
|
+
sa.select(sa.func.count(pipeline_run_table.c.id))
|
83
|
+
.where(
|
84
|
+
pipeline_run_table.c.deployment_id
|
85
|
+
== pipeline_deployment_table.c.id
|
86
|
+
)
|
87
|
+
.where(pipeline_deployment_table.c.stack_id == stack_table.c.id)
|
88
|
+
.where(stack_composition_table.c.stack_id == stack_table.c.id)
|
89
|
+
.where(
|
90
|
+
stack_composition_table.c.component_id
|
91
|
+
== stack_component_table.c.id
|
92
|
+
)
|
93
|
+
.where(stack_component_table.c.flavor != "local")
|
94
|
+
.where(stack_component_table.c.type == "artifact_store")
|
95
|
+
).scalar()
|
96
|
+
if (
|
97
|
+
pipeline_run_with_remote_artifact_store_count
|
98
|
+
and pipeline_run_with_remote_artifact_store_count > 0
|
99
|
+
):
|
100
|
+
state.append("pipeline_run_with_remote_artifact_store")
|
101
|
+
state.append("production_setup_completed")
|
102
|
+
|
103
|
+
# Remove duplicate keys
|
104
|
+
state = list(set(state))
|
105
|
+
|
106
|
+
connection.execute(
|
107
|
+
sa.update(server_settings_table).values(
|
108
|
+
onboarding_state=json.dumps(state)
|
109
|
+
)
|
110
|
+
)
|
111
|
+
|
112
|
+
|
113
|
+
def downgrade() -> None:
|
114
|
+
"""Downgrade database schema and/or data back to the previous revision."""
|
115
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
116
|
+
with op.batch_alter_table("server_settings", schema=None) as batch_op:
|
117
|
+
batch_op.alter_column(
|
118
|
+
"onboarding_state",
|
119
|
+
existing_type=sa.TEXT(),
|
120
|
+
type_=sa.VARCHAR(),
|
121
|
+
existing_nullable=True,
|
122
|
+
)
|
123
|
+
# ### end Alembic commands ###
|
@@ -18,6 +18,7 @@ from datetime import datetime
|
|
18
18
|
from typing import Any, Optional, Set
|
19
19
|
from uuid import UUID
|
20
20
|
|
21
|
+
from sqlalchemy import TEXT, Column
|
21
22
|
from sqlmodel import Field, SQLModel
|
22
23
|
|
23
24
|
from zenml.models import (
|
@@ -42,7 +43,9 @@ class ServerSettingsSchema(SQLModel, table=True):
|
|
42
43
|
enable_analytics: bool = Field(default=False)
|
43
44
|
display_announcements: Optional[bool] = Field(nullable=True)
|
44
45
|
display_updates: Optional[bool] = Field(nullable=True)
|
45
|
-
onboarding_state: Optional[str] = Field(
|
46
|
+
onboarding_state: Optional[str] = Field(
|
47
|
+
sa_column=Column(TEXT, nullable=True)
|
48
|
+
)
|
46
49
|
last_user_activity: datetime = Field(default_factory=utc_now)
|
47
50
|
updated: datetime = Field(default_factory=utc_now)
|
48
51
|
|
{zenml_nightly-0.80.2.dev20250414.dist-info → zenml_nightly-0.80.2.dev20250415.dist-info}/RECORD
RENAMED
@@ -1,5 +1,5 @@
|
|
1
1
|
zenml/README.md,sha256=827dekbOWAs1BpW7VF1a4d7EbwPbjwccX-2zdXBENZo,1777
|
2
|
-
zenml/VERSION,sha256
|
2
|
+
zenml/VERSION,sha256=aDZfR_uYsDKOeb5E9YyMbuM_1askN25eU7qAjXmYltU,19
|
3
3
|
zenml/__init__.py,sha256=CKEyepFK-7akXYiMrNVh92Nb01Cjs23w4_YyI6sgdc8,2242
|
4
4
|
zenml/actions/__init__.py,sha256=mrt6wPo73iKRxK754_NqsGyJ3buW7RnVeIGXr1xEw8Y,681
|
5
5
|
zenml/actions/base_action.py,sha256=UcaHev6BTuLDwuswnyaPjdA8AgUqB5xPZ-lRtuvf2FU,25553
|
@@ -25,7 +25,7 @@ zenml/artifacts/external_artifact.py,sha256=7nLANV0vsGC36H1s_B_awX4hnZgXHCGIscQ2
|
|
25
25
|
zenml/artifacts/external_artifact_config.py,sha256=P172p0JOu8Xx1F8RCQut1dnRpt5lpWXGNqMbY-V90sI,3323
|
26
26
|
zenml/artifacts/preexisting_data_materializer.py,sha256=dcahDcHUD3Lvn0-6zE2BG84bkyo_ydAgzBWxtbyJJZQ,3325
|
27
27
|
zenml/artifacts/unmaterialized_artifact.py,sha256=JNPKq_sNifQx5wP8jEw7TGBEi26zwKirPGlWX9uxbJI,1300
|
28
|
-
zenml/artifacts/utils.py,sha256=
|
28
|
+
zenml/artifacts/utils.py,sha256=G7V0L3Pii1b3eKznhCfGt7CJzBuhHoYwQr8Jx5VSLAI,35255
|
29
29
|
zenml/cli/__init__.py,sha256=nxq4ifwLV5qT7Qghb42h02XUOcOihBkZp2xBqgiykM8,75670
|
30
30
|
zenml/cli/annotator.py,sha256=JRR7_TJOWKyiKGv1kwSjG1Ay6RBWPVgm0X-D0uSBlyE,6976
|
31
31
|
zenml/cli/artifact.py,sha256=7lsAS52DroBTFkFWxkyb-lIDOGP5jPL_Se_RDG_2jgg,9564
|
@@ -77,7 +77,7 @@ zenml/config/retry_config.py,sha256=4UH1xqw0G6fSEbXSNKfmiFEkwadxQef9BGMe3JBm6NI,
|
|
77
77
|
zenml/config/schedule.py,sha256=qtMWa-mEo7jIKvDzQUstMwe57gdbvyWAQ7ggsoddbCA,5349
|
78
78
|
zenml/config/secret_reference_mixin.py,sha256=YvY68MTd1gE23IVprf0BLkNn62hoxcvb5nqGgc8jMkU,5871
|
79
79
|
zenml/config/secrets_store_config.py,sha256=y05zqyQhr_DGrs3IfBGa_FRoZ043hSYRT5wzrx-zHTU,2818
|
80
|
-
zenml/config/server_config.py,sha256=
|
80
|
+
zenml/config/server_config.py,sha256=Kns2ul12Zt4Y4ByKDJ4tgBrmUMvSrnxrc3bXRr5iQnw,31487
|
81
81
|
zenml/config/settings_resolver.py,sha256=PR9BRm_x1dy7nVKa9UqpeFdck8IEATSW6aWT8FKd-DI,4278
|
82
82
|
zenml/config/source.py,sha256=RzUw8lin8QztUjz-AdoCzVM5Om_cSSPuroaPx-qAO4w,8226
|
83
83
|
zenml/config/step_configurations.py,sha256=mngjobhHRj88f3klMdz6iw2mOj9wzYUPIV8Rp_2hV3g,10433
|
@@ -85,7 +85,7 @@ zenml/config/step_run_info.py,sha256=KiVRSTtKmZ1GbvseDTap2imr7XwMHD3jSFVpyLNEK1I
|
|
85
85
|
zenml/config/store_config.py,sha256=Cla5p5dTB6nNlo8_OZDs9hod5hspi64vxwtZj882XgU,3559
|
86
86
|
zenml/config/strict_base_model.py,sha256=iHnO9qOmLUP_eiy9IjRr3JjIs1l1I_CsRQ76EyAneYU,860
|
87
87
|
zenml/console.py,sha256=hj_KerPQKwnyKACj0ehSqUQX0mGVCJBKE1QvCt6ik3A,1160
|
88
|
-
zenml/constants.py,sha256=
|
88
|
+
zenml/constants.py,sha256=8_tNwBZkajdKjE9jttomUSLuQCsFGNTuYIsA1M7AkCA,16200
|
89
89
|
zenml/container_registries/__init__.py,sha256=ZSPbBIOnzhg88kQSpYgKe_POLuru14m629665-kAVAA,2200
|
90
90
|
zenml/container_registries/azure_container_registry.py,sha256=t1sfDa94Vzbyqtb1iPFNutJ2EXV5_p9CUNITasoiQ70,2667
|
91
91
|
zenml/container_registries/base_container_registry.py,sha256=6c2e32wuqxYHJXm5OV2LY1MtX9yopB7WZtes9fmTAz0,7625
|
@@ -335,11 +335,11 @@ zenml/integrations/kubeflow/orchestrators/local_deployment_utils.py,sha256=qszoO
|
|
335
335
|
zenml/integrations/kubernetes/__init__.py,sha256=k1bfrdI1u5RBnAh7yT4w-m-4SWgZ7b4L5uu7dPRc0SI,1809
|
336
336
|
zenml/integrations/kubernetes/flavors/__init__.py,sha256=a5gU45qCj3FkLwl_uVjlIkL_2F5DHk-w1gdcZrvVjBI,1266
|
337
337
|
zenml/integrations/kubernetes/flavors/kubernetes_orchestrator_flavor.py,sha256=VOfb5yOXGpQ8gcKP0nt4bYO48LqG8ZjzGhpw8XGXAzk,9253
|
338
|
-
zenml/integrations/kubernetes/flavors/kubernetes_step_operator_flavor.py,sha256=
|
338
|
+
zenml/integrations/kubernetes/flavors/kubernetes_step_operator_flavor.py,sha256=xFO7cSusji-mgbRrt4mU29gdyC9iEjEHKtomdFLp9mM,6265
|
339
339
|
zenml/integrations/kubernetes/orchestrators/__init__.py,sha256=TJID3OTieZBox36WpQpzD0jdVRA_aZVcs_bNtfXS8ik,811
|
340
|
-
zenml/integrations/kubernetes/orchestrators/kube_utils.py,sha256=
|
341
|
-
zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator.py,sha256=
|
342
|
-
zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator_entrypoint.py,sha256=
|
340
|
+
zenml/integrations/kubernetes/orchestrators/kube_utils.py,sha256=oU0EYP-x35oG7PAy7NZKeFA8_89Eckgq6hibwc9v5l0,18108
|
341
|
+
zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator.py,sha256=v4LY2v81cWX9z-fwbSctjR1yZi8vkUckjj7TIrAqxps,25597
|
342
|
+
zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator_entrypoint.py,sha256=Wm_qw9oeCrJXAabTVmhpJeDWG2Jcmc2PZ8lZW68ScdI,11573
|
343
343
|
zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator_entrypoint_configuration.py,sha256=KjHfQK9VQEQkkkM2i9w51AzqolgIU01M5dgb2YGamvY,2754
|
344
344
|
zenml/integrations/kubernetes/orchestrators/manifest_utils.py,sha256=Owyuz5Iix-QvCnKObC6xhcuQtNG_ik-8Vbdmk13eWfc,12557
|
345
345
|
zenml/integrations/kubernetes/pod_settings.py,sha256=fP2NeHf1XxT__D4g_6oHQf8pkiiamFUFYmfNor2OoP8,6386
|
@@ -347,7 +347,7 @@ zenml/integrations/kubernetes/serialization_utils.py,sha256=cPSe4szdBLzDnUZT9nQc
|
|
347
347
|
zenml/integrations/kubernetes/service_connectors/__init__.py,sha256=Uf6zlHIapYrRDl3xOPWQ2jA7jt85SXx1U7DmSxzxTvQ,818
|
348
348
|
zenml/integrations/kubernetes/service_connectors/kubernetes_service_connector.py,sha256=Cv4tiVxoQOz9ex0lf3JdJrooEkgMwfDfwt5GOeNRpQU,19669
|
349
349
|
zenml/integrations/kubernetes/step_operators/__init__.py,sha256=40utDPYAezxHsFgO0UUIT_6XpCDzDapje6OH951XsTs,806
|
350
|
-
zenml/integrations/kubernetes/step_operators/kubernetes_step_operator.py,sha256=
|
350
|
+
zenml/integrations/kubernetes/step_operators/kubernetes_step_operator.py,sha256=m8bSHskP-wS77tt1DEBoKt11pV4L6OtN0QN1AZ5Tp1g,8859
|
351
351
|
zenml/integrations/label_studio/__init__.py,sha256=sF2c9FxTDRlbcu95OxaUNKNtIhC1LgfmBRKY4jBME38,1475
|
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
|
@@ -601,7 +601,7 @@ zenml/materializers/cloudpickle_materializer.py,sha256=x8a6jEMTky6N2YVHiwrnGWSfV
|
|
601
601
|
zenml/materializers/materializer_registry.py,sha256=ic-aWhJ2Ex9F_rml2dDVAxhRfW3nd71QMxzfTPP6BIM,4002
|
602
602
|
zenml/materializers/numpy_materializer.py,sha256=OLcHF9Z0tAqQ_U8TraA0vGmZjHoT7eT_XevncIutt0M,1715
|
603
603
|
zenml/materializers/pandas_materializer.py,sha256=c4B-ly04504gysA66iCYcmEdeh0ClePRTxRCkmHqIgE,1725
|
604
|
-
zenml/materializers/path_materializer.py,sha256=
|
604
|
+
zenml/materializers/path_materializer.py,sha256=kY37mVwlfQndp3M7--g81dRt2qT81MJ1aURnl4uX29I,5422
|
605
605
|
zenml/materializers/pydantic_materializer.py,sha256=eDp3eOR-X7FOHlpwALOJtVUtJti75Dsa7r0lzSjeQ-Q,2271
|
606
606
|
zenml/materializers/service_materializer.py,sha256=OV5HFUuIc8UF8vE5y_FTWn4_mpCz0AtJPnNA2YFAjZE,2992
|
607
607
|
zenml/materializers/structured_string_materializer.py,sha256=8LIvSH3JQn_QRAWGXK5_NhPQ4NgCt5zzz6f8GnGG9q4,4442
|
@@ -776,7 +776,7 @@ zenml/utils/filesync_model.py,sha256=9ibsIr2HJfkEQ41upQd4uJ79ZhzB0MHS85GOGOAQ19Y
|
|
776
776
|
zenml/utils/function_utils.py,sha256=bznRrCrQIJ3Joc-QQ8ynDyn9AvyIcR61bzLnaXQV3tc,8109
|
777
777
|
zenml/utils/git_utils.py,sha256=O2f6PUpDuMQrcmkcGXIhbJQpTzLM7p8PyiKWjcIuWqM,1831
|
778
778
|
zenml/utils/integration_utils.py,sha256=jQVTnOjB9AQUerU7UiIXtDfcHnUrkMJDJKx5JZaPTAQ,1186
|
779
|
-
zenml/utils/io_utils.py,sha256=
|
779
|
+
zenml/utils/io_utils.py,sha256=S-5dpeOCgEMV7R5y2gZxuWdAwRBunx9sBZi_WwY13Hw,7728
|
780
780
|
zenml/utils/json_utils.py,sha256=mzZnzq5TAjZQLRPOHjRqAUTHqMfw5-lNptWSvrBICic,3668
|
781
781
|
zenml/utils/materializer_utils.py,sha256=DNlO113FXjZ0Y67KpvDB5NiWen1tRZ0E-A_vTjE-BME,1815
|
782
782
|
zenml/utils/metadata_utils.py,sha256=aLDLW25RKzS_uQCjfMKpqbUi4YRd85ioCPfRKsgnADc,11723
|
@@ -803,9 +803,9 @@ zenml/utils/uuid_utils.py,sha256=aOGQ2SdREexcVQICPU2jUAgjvAJxTmh4ESdM52PEhck,204
|
|
803
803
|
zenml/utils/visualization_utils.py,sha256=ZGAnmE4s_rBSyz2FBu9FOIu054blfXuxLEz8wWqv-3s,4739
|
804
804
|
zenml/utils/yaml_utils.py,sha256=747M_BTf3lcHFgJDF8RJxnnGIbCU8e9YxBBMmoQ5O_U,5834
|
805
805
|
zenml/zen_server/__init__.py,sha256=WyltI9TzFW2mEHZVOs6alLWMCQrrZaFALtrQXs83STA,1355
|
806
|
-
zenml/zen_server/auth.py,sha256=
|
806
|
+
zenml/zen_server/auth.py,sha256=DxDEifWvV-mGWgyavTv6In0rypreh0qmxRLXsRLQ5oI,40646
|
807
807
|
zenml/zen_server/cache.py,sha256=Tc4TSugmsU1bhThxlYfE8rv0KmltIX1CcVHgzrJ0Eus,6633
|
808
|
-
zenml/zen_server/cloud_utils.py,sha256=
|
808
|
+
zenml/zen_server/cloud_utils.py,sha256=nHCJjO4jRaiZiVIMbN339xG5_uLKzkRPk-FoId0hhx8,9307
|
809
809
|
zenml/zen_server/csrf.py,sha256=Jsbi_IKriWCOuquSYTOEWqSXiGORJATIhR5Wrkm4XzQ,2684
|
810
810
|
zenml/zen_server/dashboard/assets/404-CYPi9d8E.js,sha256=2zavfhLSXLfKcj5vrItcAWat8SxQ2iGByKTuA5KOQE8,1033
|
811
811
|
zenml/zen_server/dashboard/assets/@radix-C7hRs6Kx.js,sha256=cNA9UX8LGrKUQubGrls3E3Wq1fCrlK51W14yh22FE_U,296700
|
@@ -1010,6 +1010,7 @@ zenml/zen_server/deploy/docker/__init__.py,sha256=lNGI-Pl3PVMqR4BVajZXwe3moVNXPx
|
|
1010
1010
|
zenml/zen_server/deploy/docker/docker_provider.py,sha256=18pNpxvP8xqbxS_KxvYTEIuad8Ky5mVtk5TaKVVrzcY,8371
|
1011
1011
|
zenml/zen_server/deploy/docker/docker_zen_server.py,sha256=83-bjRmtlE-WDCavbvL9uTXOn5fMJ4yPDDb2ewSr4Do,7480
|
1012
1012
|
zenml/zen_server/deploy/exceptions.py,sha256=tX0GNnLB_GMkeN7zGNlJRwtlrpZ5Slvyj_unVYVmGxk,1396
|
1013
|
+
zenml/zen_server/download_utils.py,sha256=zh6Hrs1E3BbzVwyvTpZyOLrZfkqCAxLm8wDcDpXFEIE,4203
|
1013
1014
|
zenml/zen_server/exceptions.py,sha256=H4F9SbbwCiVLnJTdzpWN016Knt6zvoOVmOWxTgXEAsA,9686
|
1014
1015
|
zenml/zen_server/feature_gate/__init__.py,sha256=yabe4fBY6NSusn-QlKQDLOvXVLERNpcAQgigsyWQIbQ,612
|
1015
1016
|
zenml/zen_server/feature_gate/endpoint_utils.py,sha256=o6sBVlqqlc9KokMaEsRTYeMra7f2a6kCt3FrB-oHhCw,2227
|
@@ -1020,14 +1021,14 @@ zenml/zen_server/rate_limit.py,sha256=rOg5H_6rOGQ_qiSCtMKPREmL1LL3bZyn4czKILtImJ
|
|
1020
1021
|
zenml/zen_server/rbac/__init__.py,sha256=nACbn_G7nZt_AWM3zeFL0FCmELvQnvaOFMwvTG3-6ZE,637
|
1021
1022
|
zenml/zen_server/rbac/endpoint_utils.py,sha256=l6V6DwsPGmT6dLJmY4MHMRfRXdc0lZVibD-Aea5UAHc,11604
|
1022
1023
|
zenml/zen_server/rbac/models.py,sha256=Xhz0XqmVqg3ji9uTwjlhQ4IuQ2ivrT4gVdHi5ZkjFC4,4933
|
1023
|
-
zenml/zen_server/rbac/rbac_interface.py,sha256=
|
1024
|
+
zenml/zen_server/rbac/rbac_interface.py,sha256=VPMNnUIPcqJWDngITX6cfaPlM6zJwzCApXAnT_oaxSQ,3431
|
1024
1025
|
zenml/zen_server/rbac/rbac_sql_zen_store.py,sha256=djGycBkciChk48q8oL7mHM0c-cHJM-S7inv5f3gcDgg,5942
|
1025
|
-
zenml/zen_server/rbac/utils.py,sha256=
|
1026
|
-
zenml/zen_server/rbac/zenml_cloud_rbac.py,sha256=
|
1026
|
+
zenml/zen_server/rbac/utils.py,sha256=8hBvCGWnz517hL5gn53dcjcrKePE2OGqZV_ojJ8XZEw,24329
|
1027
|
+
zenml/zen_server/rbac/zenml_cloud_rbac.py,sha256=L1Tio4IcNNxNWnVpAqBWYgdVfrqR20ddvdhWAo1i3f4,6055
|
1027
1028
|
zenml/zen_server/routers/__init__.py,sha256=ViyAhWL-ogHxE9wBvB_iMcur5H1NRVrzXkpogVY7FBA,641
|
1028
1029
|
zenml/zen_server/routers/actions_endpoints.py,sha256=MFausi27i5QBB5s4vbjQQTVqFiRlk5jF5lRM6vsRM2o,9422
|
1029
1030
|
zenml/zen_server/routers/artifact_endpoint.py,sha256=huFufMN-hHzBUKqYmn1WeWVaJic1ypvqSpTWFxklUDQ,4976
|
1030
|
-
zenml/zen_server/routers/artifact_version_endpoints.py,sha256=
|
1031
|
+
zenml/zen_server/routers/artifact_version_endpoints.py,sha256=zda_iINad3VnKcyGgCW2ZveblEzZa5YjwrCAsuEN13s,11254
|
1031
1032
|
zenml/zen_server/routers/auth_endpoints.py,sha256=CZB5ZeuWy1lhP7-O7Be5dGzlr51YUR0Mtf4kMkj96Rw,23634
|
1032
1033
|
zenml/zen_server/routers/code_repositories_endpoints.py,sha256=_D1896lrEgYD516y69iiFrPn5b62MxlrTcmDt1AyqBA,6476
|
1033
1034
|
zenml/zen_server/routers/devices_endpoints.py,sha256=GluKziLikUn_IoflcBwV5wB8ui71STjDsg2Mjgn43MU,10551
|
@@ -1057,12 +1058,12 @@ zenml/zen_server/routers/steps_endpoints.py,sha256=By-9_VKrJeSo6UUqrggDhGKtrnhxi
|
|
1057
1058
|
zenml/zen_server/routers/tag_resource_endpoints.py,sha256=AdmbujrwKK6arnSx2VHpJWbZXnlviGlay7LCov4x9Fc,3203
|
1058
1059
|
zenml/zen_server/routers/tags_endpoints.py,sha256=lRjnMaJOdTnooMHoQYIViKfgu32cMq7rI3Lyeaxq4QI,4467
|
1059
1060
|
zenml/zen_server/routers/triggers_endpoints.py,sha256=5fb0EITa3bs--AtKQRQZ5QswHu4pb37OrG5y0OlnGXE,9983
|
1060
|
-
zenml/zen_server/routers/users_endpoints.py,sha256=
|
1061
|
+
zenml/zen_server/routers/users_endpoints.py,sha256=2SfqHutGp0OqwYVytx3WeIwF51nZ3f874uCiCc_m_7g,24531
|
1061
1062
|
zenml/zen_server/routers/webhook_endpoints.py,sha256=KOJsuykv_TMjL3oEItpC4OWWP75p-OEvVjRSUBd5Mro,3983
|
1062
1063
|
zenml/zen_server/secure_headers.py,sha256=glh6QujnjyeoH1_FK-tAS-105G-qKS_34AqSzqJ6TRc,4182
|
1063
1064
|
zenml/zen_server/template_execution/__init__.py,sha256=79knXLKfegsvVSVSWecpqrepq6iAavTUA4hKuiDk-WE,613
|
1064
1065
|
zenml/zen_server/template_execution/runner_entrypoint_configuration.py,sha256=Y8aYJhqqs8Kv8I1q-dM1WemS5VBIfyoaaYH_YkzC7iY,1541
|
1065
|
-
zenml/zen_server/template_execution/utils.py,sha256=
|
1066
|
+
zenml/zen_server/template_execution/utils.py,sha256=qL62UDH4FwXhpLYwGp-VxF3mttOEr8HO8O7utvyHRfM,17330
|
1066
1067
|
zenml/zen_server/template_execution/workload_manager_interface.py,sha256=CL9c7z8ajuZE01DaHmdCDCZmsroDcTarvN-nE8jv6qQ,2590
|
1067
1068
|
zenml/zen_server/utils.py,sha256=Jc2Q4UBaYG2ruHdsN9JmbOWfWU_eWD9wTBBEgcGAbqg,17439
|
1068
1069
|
zenml/zen_server/zen_server_api.py,sha256=ALyv5096frXXRNySegEtsmkDbHLLqHd402bbQI7RnII,17941
|
@@ -1267,6 +1268,7 @@ zenml/zen_stores/migrations/versions/f3b3964e3a0f_add_oauth_devices.py,sha256=2C
|
|
1267
1268
|
zenml/zen_stores/migrations/versions/f49904a80aa7_increase_length_of_artifact_table_sources.py,sha256=kLgfDUnQdAb5_SyFx3VKXDLC0YbuBKf9iXRDNeBin7Q,1618
|
1268
1269
|
zenml/zen_stores/migrations/versions/f76a368a25a5_add_stack_description.py,sha256=u8fRomaasFeGhxvM2zU-Ab-AEpVsWm5zRcixxKFXdRw,904
|
1269
1270
|
zenml/zen_stores/migrations/versions/fbd7f18ced1e_increase_step_run_field_lengths.py,sha256=kn-ng5EHe_mmLfffIFbz7T59z-to3oMx8III_4wOsz4,1956
|
1271
|
+
zenml/zen_stores/migrations/versions/ff538a321a92_migrate_onboarding_state.py,sha256=gsUFLJQ32_o9U35JCVqkqJVVk-zfq3yel25hXhzVFm4,3829
|
1270
1272
|
zenml/zen_stores/rest_zen_store.py,sha256=zo0OnNZuiC8EWR-P9-oz7VdEFYPimnikERtvpEJjEgs,158091
|
1271
1273
|
zenml/zen_stores/schemas/__init__.py,sha256=4EXqExiVyxdnGxhQ_Hz79mOdRuMD0LsGlw0PaP2Ef6o,4333
|
1272
1274
|
zenml/zen_stores/schemas/action_schemas.py,sha256=2OiUiskFSg5qXGxA6AFq71bWzUczxA563LGFokLZmac,6456
|
@@ -1292,7 +1294,7 @@ zenml/zen_stores/schemas/run_template_schemas.py,sha256=sM3pcegx7qOs5tlwqVnFvc7J
|
|
1292
1294
|
zenml/zen_stores/schemas/schedule_schema.py,sha256=E9hmq8-PBRHxEH5UYEpD_Oc2cHkNp4FG0sTvhiLZ6D4,7653
|
1293
1295
|
zenml/zen_stores/schemas/schema_utils.py,sha256=Xahifq2fJ5szXCM00ZZ6461Div9Suatzl6sy9hVhPkk,3612
|
1294
1296
|
zenml/zen_stores/schemas/secret_schemas.py,sha256=HrihLSUPJKmWbQHogGmi6e0lYPoQHUGWGRl69sLnZMk,9430
|
1295
|
-
zenml/zen_stores/schemas/server_settings_schemas.py,sha256=
|
1297
|
+
zenml/zen_stores/schemas/server_settings_schemas.py,sha256=usBE-idRrmK-LeLN0zDtCRCGP51YTnyKfIx5GZ0_ATg,4275
|
1296
1298
|
zenml/zen_stores/schemas/service_connector_schemas.py,sha256=kNJ6TmM2RA16wc73zusosHxWUm6gVpyowlY_OJv-Oww,9709
|
1297
1299
|
zenml/zen_stores/schemas/service_schemas.py,sha256=yg9OJ2QWa1qQy_gqraGvN3JQrOJDLjhkSL-UVSFJy-s,9517
|
1298
1300
|
zenml/zen_stores/schemas/stack_schemas.py,sha256=SkMspxsnR420KU4mZlulPbEOR4DMSRyrcspe3HSYjHc,5393
|
@@ -1313,8 +1315,8 @@ zenml/zen_stores/secrets_stores/sql_secrets_store.py,sha256=nEO0bAPlULBLxLVk-UTR
|
|
1313
1315
|
zenml/zen_stores/sql_zen_store.py,sha256=ndfeiZSIsilv6aEmTsYVEIYL9eMRUKVQTVYXDTt9xXI,441016
|
1314
1316
|
zenml/zen_stores/template_utils.py,sha256=GWBP5QEOyvhzndS_MLPmvh28sQaOPpPoZFXCIX9CRL4,9065
|
1315
1317
|
zenml/zen_stores/zen_store_interface.py,sha256=fF_uL_FplnvGvM5o3jOQ8i1zHXhuhKLL2n4nvIKSR7E,92090
|
1316
|
-
zenml_nightly-0.80.2.
|
1317
|
-
zenml_nightly-0.80.2.
|
1318
|
-
zenml_nightly-0.80.2.
|
1319
|
-
zenml_nightly-0.80.2.
|
1320
|
-
zenml_nightly-0.80.2.
|
1318
|
+
zenml_nightly-0.80.2.dev20250415.dist-info/LICENSE,sha256=wbnfEnXnafPbqwANHkV6LUsPKOtdpsd-SNw37rogLtc,11359
|
1319
|
+
zenml_nightly-0.80.2.dev20250415.dist-info/METADATA,sha256=uqsecSjp8pzIGZ_ho7pXxHqjQB4M--7DaJ5IpPOD6_g,24233
|
1320
|
+
zenml_nightly-0.80.2.dev20250415.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
1321
|
+
zenml_nightly-0.80.2.dev20250415.dist-info/entry_points.txt,sha256=QK3ETQE0YswAM2mWypNMOv8TLtr7EjnqAFq1br_jEFE,43
|
1322
|
+
zenml_nightly-0.80.2.dev20250415.dist-info/RECORD,,
|
{zenml_nightly-0.80.2.dev20250414.dist-info → zenml_nightly-0.80.2.dev20250415.dist-info}/LICENSE
RENAMED
File without changes
|
{zenml_nightly-0.80.2.dev20250414.dist-info → zenml_nightly-0.80.2.dev20250415.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|