zenml-nightly 0.73.0.dev20250204__py3-none-any.whl → 0.73.0.dev20250206__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/utils.py +42 -53
- zenml/client.py +6 -2
- zenml/constants.py +1 -0
- zenml/integrations/gcp/image_builders/gcp_image_builder.py +5 -8
- zenml/models/__init__.py +4 -2
- zenml/models/v2/base/filter.py +34 -11
- zenml/models/v2/base/scoped.py +4 -4
- zenml/models/v2/core/artifact.py +3 -3
- zenml/models/v2/core/artifact_version.py +22 -5
- zenml/models/v2/core/model.py +13 -3
- zenml/models/v2/core/model_version.py +13 -3
- zenml/models/v2/core/pipeline.py +11 -4
- zenml/models/v2/core/pipeline_run.py +20 -7
- zenml/models/v2/core/run_template.py +13 -3
- zenml/models/v2/core/step_run.py +9 -2
- zenml/pipelines/pipeline_definition.py +28 -12
- zenml/stack/stack.py +5 -0
- zenml/zen_stores/schemas/artifact_schemas.py +31 -4
- zenml/zen_stores/schemas/model_schemas.py +31 -6
- zenml/zen_stores/schemas/pipeline_run_schemas.py +1 -1
- zenml/zen_stores/schemas/pipeline_schemas.py +35 -8
- zenml/zen_stores/schemas/run_template_schemas.py +42 -14
- zenml/zen_stores/sql_zen_store.py +22 -56
- {zenml_nightly-0.73.0.dev20250204.dist-info → zenml_nightly-0.73.0.dev20250206.dist-info}/METADATA +1 -1
- {zenml_nightly-0.73.0.dev20250204.dist-info → zenml_nightly-0.73.0.dev20250206.dist-info}/RECORD +29 -46
- zenml/zen_server/deploy/helm/.helmignore +0 -23
- zenml/zen_server/deploy/helm/Chart.yaml +0 -12
- zenml/zen_server/deploy/helm/README.md +0 -50
- zenml/zen_server/deploy/helm/templates/NOTES.txt +0 -52
- zenml/zen_server/deploy/helm/templates/_environment.tpl +0 -511
- zenml/zen_server/deploy/helm/templates/_helpers.tpl +0 -70
- zenml/zen_server/deploy/helm/templates/cert-secret.yaml +0 -45
- zenml/zen_server/deploy/helm/templates/hpa.yaml +0 -32
- zenml/zen_server/deploy/helm/templates/server-db-job.yaml +0 -121
- zenml/zen_server/deploy/helm/templates/server-db-pvc.yaml +0 -25
- zenml/zen_server/deploy/helm/templates/server-deployment.yaml +0 -132
- zenml/zen_server/deploy/helm/templates/server-ingress.yaml +0 -59
- zenml/zen_server/deploy/helm/templates/server-secret.yaml +0 -60
- zenml/zen_server/deploy/helm/templates/server-service.yaml +0 -15
- zenml/zen_server/deploy/helm/templates/serviceaccount.yaml +0 -27
- zenml/zen_server/deploy/helm/templates/tests/test-connection.yaml +0 -15
- zenml/zen_server/deploy/helm/values.yaml +0 -1008
- {zenml_nightly-0.73.0.dev20250204.dist-info → zenml_nightly-0.73.0.dev20250206.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.73.0.dev20250204.dist-info → zenml_nightly-0.73.0.dev20250206.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.73.0.dev20250204.dist-info → zenml_nightly-0.73.0.dev20250206.dist-info}/entry_points.txt +0 -0
@@ -33,12 +33,13 @@ from zenml.constants import STR_FIELD_MAX_LENGTH, TEXT_FIELD_MAX_LENGTH
|
|
33
33
|
from zenml.enums import ExecutionStatus
|
34
34
|
from zenml.models.v2.base.base import BaseUpdate
|
35
35
|
from zenml.models.v2.base.scoped import (
|
36
|
+
TaggableFilter,
|
37
|
+
WorkspaceScopedFilter,
|
36
38
|
WorkspaceScopedRequest,
|
37
39
|
WorkspaceScopedResponse,
|
38
40
|
WorkspaceScopedResponseBody,
|
39
41
|
WorkspaceScopedResponseMetadata,
|
40
42
|
WorkspaceScopedResponseResources,
|
41
|
-
WorkspaceScopedTaggableFilter,
|
42
43
|
)
|
43
44
|
from zenml.models.v2.core.code_reference import (
|
44
45
|
CodeReferenceResponse,
|
@@ -307,11 +308,12 @@ class RunTemplateResponse(
|
|
307
308
|
# ------------------ Filter Model ------------------
|
308
309
|
|
309
310
|
|
310
|
-
class RunTemplateFilter(
|
311
|
+
class RunTemplateFilter(WorkspaceScopedFilter, TaggableFilter):
|
311
312
|
"""Model for filtering of run templates."""
|
312
313
|
|
313
314
|
FILTER_EXCLUDE_FIELDS: ClassVar[List[str]] = [
|
314
|
-
*
|
315
|
+
*WorkspaceScopedFilter.FILTER_EXCLUDE_FIELDS,
|
316
|
+
*TaggableFilter.FILTER_EXCLUDE_FIELDS,
|
315
317
|
"code_repository_id",
|
316
318
|
"stack_id",
|
317
319
|
"build_id",
|
@@ -320,6 +322,14 @@ class RunTemplateFilter(WorkspaceScopedTaggableFilter):
|
|
320
322
|
"pipeline",
|
321
323
|
"stack",
|
322
324
|
]
|
325
|
+
CUSTOM_SORTING_OPTIONS = [
|
326
|
+
*WorkspaceScopedFilter.CUSTOM_SORTING_OPTIONS,
|
327
|
+
*TaggableFilter.CUSTOM_SORTING_OPTIONS,
|
328
|
+
]
|
329
|
+
CLI_EXCLUDE_FIELDS = [
|
330
|
+
*WorkspaceScopedFilter.CLI_EXCLUDE_FIELDS,
|
331
|
+
*TaggableFilter.CLI_EXCLUDE_FIELDS,
|
332
|
+
]
|
323
333
|
|
324
334
|
name: Optional[str] = Field(
|
325
335
|
default=None,
|
zenml/models/v2/core/step_run.py
CHANGED
@@ -16,6 +16,7 @@
|
|
16
16
|
from datetime import datetime
|
17
17
|
from typing import (
|
18
18
|
TYPE_CHECKING,
|
19
|
+
Any,
|
19
20
|
ClassVar,
|
20
21
|
Dict,
|
21
22
|
List,
|
@@ -574,7 +575,7 @@ class StepRunFilter(WorkspaceScopedFilter):
|
|
574
575
|
default=None,
|
575
576
|
description="Name/ID of the model associated with the step run.",
|
576
577
|
)
|
577
|
-
run_metadata: Optional[Dict[str,
|
578
|
+
run_metadata: Optional[Dict[str, Any]] = Field(
|
578
579
|
default=None,
|
579
580
|
description="The run_metadata to filter the step runs by.",
|
580
581
|
)
|
@@ -619,13 +620,19 @@ class StepRunFilter(WorkspaceScopedFilter):
|
|
619
620
|
additional_filter = and_(
|
620
621
|
RunMetadataResourceSchema.resource_id == StepRunSchema.id,
|
621
622
|
RunMetadataResourceSchema.resource_type
|
622
|
-
== MetadataResourceTypes.STEP_RUN,
|
623
|
+
== MetadataResourceTypes.STEP_RUN.value,
|
623
624
|
RunMetadataResourceSchema.run_metadata_id
|
624
625
|
== RunMetadataSchema.id,
|
626
|
+
self.generate_custom_query_conditions_for_column(
|
627
|
+
value=key,
|
628
|
+
table=RunMetadataSchema,
|
629
|
+
column="key",
|
630
|
+
),
|
625
631
|
self.generate_custom_query_conditions_for_column(
|
626
632
|
value=value,
|
627
633
|
table=RunMetadataSchema,
|
628
634
|
column="value",
|
635
|
+
json_encode_value=True,
|
629
636
|
),
|
630
637
|
)
|
631
638
|
custom_filters.append(additional_filter)
|
@@ -407,17 +407,31 @@ class Pipeline:
|
|
407
407
|
return self
|
408
408
|
|
409
409
|
@property
|
410
|
-
def
|
411
|
-
"""
|
410
|
+
def required_parameters(self) -> List[str]:
|
411
|
+
"""List of required parameters for the pipeline entrypoint.
|
412
412
|
|
413
413
|
Returns:
|
414
|
-
|
414
|
+
List of required parameters for the pipeline entrypoint.
|
415
415
|
"""
|
416
416
|
signature = inspect.signature(self.entrypoint, follow_wrapped=True)
|
417
|
-
return
|
418
|
-
parameter.
|
417
|
+
return [
|
418
|
+
parameter.name
|
419
419
|
for parameter in signature.parameters.values()
|
420
|
-
|
420
|
+
if parameter.default is inspect.Parameter.empty
|
421
|
+
]
|
422
|
+
|
423
|
+
@property
|
424
|
+
def missing_parameters(self) -> List[str]:
|
425
|
+
"""List of missing parameters for the pipeline entrypoint.
|
426
|
+
|
427
|
+
Returns:
|
428
|
+
List of missing parameters for the pipeline entrypoint.
|
429
|
+
"""
|
430
|
+
available_parameters = set(self.configuration.parameters or {})
|
431
|
+
if params_from_file := self._from_config_file.get("parameters", None):
|
432
|
+
available_parameters.update(params_from_file)
|
433
|
+
|
434
|
+
return list(set(self.required_parameters) - available_parameters)
|
421
435
|
|
422
436
|
@property
|
423
437
|
def is_prepared(self) -> bool:
|
@@ -1412,7 +1426,7 @@ To avoid this consider setting pipeline parameters only in one place (config or
|
|
1412
1426
|
except ValidationError as e:
|
1413
1427
|
raise ValueError(
|
1414
1428
|
"Invalid or missing pipeline function entrypoint arguments. "
|
1415
|
-
"Only JSON serializable inputs are allowed as pipeline inputs."
|
1429
|
+
"Only JSON serializable inputs are allowed as pipeline inputs. "
|
1416
1430
|
"Check out the pydantic error above for more details."
|
1417
1431
|
) from e
|
1418
1432
|
|
@@ -1427,15 +1441,17 @@ To avoid this consider setting pipeline parameters only in one place (config or
|
|
1427
1441
|
requires parameters.
|
1428
1442
|
"""
|
1429
1443
|
if not self.is_prepared:
|
1430
|
-
if self.
|
1444
|
+
if missing_parameters := self.missing_parameters:
|
1431
1445
|
raise RuntimeError(
|
1432
1446
|
f"Failed while trying to prepare pipeline {self.name}. "
|
1433
1447
|
"The entrypoint function of the pipeline requires "
|
1434
|
-
"arguments
|
1435
|
-
"
|
1448
|
+
"arguments which have not been configured yet: "
|
1449
|
+
f"{missing_parameters}. Please provide those parameters by "
|
1450
|
+
"calling `pipeline_instance.configure(parameters=...)` or "
|
1451
|
+
"by calling `pipeline_instance.prepare(...)` and try again."
|
1436
1452
|
)
|
1437
|
-
|
1438
|
-
|
1453
|
+
|
1454
|
+
self.prepare()
|
1439
1455
|
|
1440
1456
|
def create_run_template(
|
1441
1457
|
self, name: str, **kwargs: Any
|
zenml/stack/stack.py
CHANGED
@@ -37,6 +37,7 @@ from zenml.config.global_config import GlobalConfiguration
|
|
37
37
|
from zenml.constants import (
|
38
38
|
ENV_ZENML_SECRET_VALIDATION_LEVEL,
|
39
39
|
ENV_ZENML_SKIP_IMAGE_BUILDER_DEFAULT,
|
40
|
+
ENV_ZENML_SKIP_STACK_VALIDATION,
|
40
41
|
handle_bool_env_var,
|
41
42
|
)
|
42
43
|
from zenml.enums import SecretValidationLevel, StackComponentType
|
@@ -706,6 +707,10 @@ class Stack:
|
|
706
707
|
if a secret for a component is missing. Otherwise, only a
|
707
708
|
warning will be logged.
|
708
709
|
"""
|
710
|
+
if handle_bool_env_var(ENV_ZENML_SKIP_STACK_VALIDATION, default=False):
|
711
|
+
logger.debug("Skipping stack validation.")
|
712
|
+
return
|
713
|
+
|
709
714
|
self.validate_image_builder()
|
710
715
|
for component in self.components.values():
|
711
716
|
if component.validator:
|
@@ -18,7 +18,8 @@ from uuid import UUID
|
|
18
18
|
|
19
19
|
from pydantic import ValidationError
|
20
20
|
from sqlalchemy import TEXT, Column, UniqueConstraint
|
21
|
-
from
|
21
|
+
from sqlalchemy.orm import object_session
|
22
|
+
from sqlmodel import Field, Relationship, desc, select
|
22
23
|
|
23
24
|
from zenml.config.source import Source
|
24
25
|
from zenml.enums import (
|
@@ -90,6 +91,32 @@ class ArtifactSchema(NamedSchema, table=True):
|
|
90
91
|
),
|
91
92
|
)
|
92
93
|
|
94
|
+
@property
|
95
|
+
def latest_version(self) -> Optional["ArtifactVersionSchema"]:
|
96
|
+
"""Fetch the latest version for this artifact.
|
97
|
+
|
98
|
+
Raises:
|
99
|
+
RuntimeError: If no session for the schema exists.
|
100
|
+
|
101
|
+
Returns:
|
102
|
+
The latest version for this artifact.
|
103
|
+
"""
|
104
|
+
if session := object_session(self):
|
105
|
+
return (
|
106
|
+
session.execute(
|
107
|
+
select(ArtifactVersionSchema)
|
108
|
+
.where(ArtifactVersionSchema.artifact_id == self.id)
|
109
|
+
.order_by(desc(ArtifactVersionSchema.created))
|
110
|
+
.limit(1)
|
111
|
+
)
|
112
|
+
.scalars()
|
113
|
+
.one_or_none()
|
114
|
+
)
|
115
|
+
else:
|
116
|
+
raise RuntimeError(
|
117
|
+
"Missing DB session to fetch latest version for artifact."
|
118
|
+
)
|
119
|
+
|
93
120
|
@classmethod
|
94
121
|
def from_request(
|
95
122
|
cls,
|
@@ -127,9 +154,9 @@ class ArtifactSchema(NamedSchema, table=True):
|
|
127
154
|
The created `ArtifactResponse`.
|
128
155
|
"""
|
129
156
|
latest_id, latest_name = None, None
|
130
|
-
if self.
|
131
|
-
|
132
|
-
|
157
|
+
if latest_version := self.latest_version:
|
158
|
+
latest_id = latest_version.id
|
159
|
+
latest_name = latest_version.version
|
133
160
|
|
134
161
|
# Create the body of the model
|
135
162
|
body = ArtifactResponseBody(
|
@@ -24,7 +24,8 @@ from sqlalchemy import (
|
|
24
24
|
Column,
|
25
25
|
UniqueConstraint,
|
26
26
|
)
|
27
|
-
from
|
27
|
+
from sqlalchemy.orm import object_session
|
28
|
+
from sqlmodel import Field, Relationship, desc, select
|
28
29
|
|
29
30
|
from zenml.enums import (
|
30
31
|
ArtifactType,
|
@@ -126,6 +127,32 @@ class ModelSchema(NamedSchema, table=True):
|
|
126
127
|
sa_relationship_kwargs={"cascade": "delete"},
|
127
128
|
)
|
128
129
|
|
130
|
+
@property
|
131
|
+
def latest_version(self) -> Optional["ModelVersionSchema"]:
|
132
|
+
"""Fetch the latest version for this model.
|
133
|
+
|
134
|
+
Raises:
|
135
|
+
RuntimeError: If no session for the schema exists.
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
The latest version for this model.
|
139
|
+
"""
|
140
|
+
if session := object_session(self):
|
141
|
+
return (
|
142
|
+
session.execute(
|
143
|
+
select(ModelVersionSchema)
|
144
|
+
.where(ModelVersionSchema.model_id == self.id)
|
145
|
+
.order_by(desc(ModelVersionSchema.number))
|
146
|
+
.limit(1)
|
147
|
+
)
|
148
|
+
.scalars()
|
149
|
+
.one_or_none()
|
150
|
+
)
|
151
|
+
else:
|
152
|
+
raise RuntimeError(
|
153
|
+
"Missing DB session to fetch latest version for model."
|
154
|
+
)
|
155
|
+
|
129
156
|
@classmethod
|
130
157
|
def from_request(cls, model_request: ModelRequest) -> "ModelSchema":
|
131
158
|
"""Convert an `ModelRequest` to an `ModelSchema`.
|
@@ -169,11 +196,9 @@ class ModelSchema(NamedSchema, table=True):
|
|
169
196
|
"""
|
170
197
|
tags = [tag.to_model() for tag in self.tags]
|
171
198
|
|
172
|
-
if self.
|
173
|
-
|
174
|
-
|
175
|
-
latest_version_name = self.model_versions[latest_version_idx].name
|
176
|
-
latest_version_id = self.model_versions[latest_version_idx].id
|
199
|
+
if latest_version := self.latest_version:
|
200
|
+
latest_version_name = latest_version.name
|
201
|
+
latest_version_id = latest_version.id
|
177
202
|
else:
|
178
203
|
latest_version_name = None
|
179
204
|
latest_version_id = None
|
@@ -208,7 +208,7 @@ class PipelineRunSchema(NamedSchema, RunMetadataInterface, table=True):
|
|
208
208
|
stack: Optional["StackSchema"] = Relationship()
|
209
209
|
build: Optional["PipelineBuildSchema"] = Relationship()
|
210
210
|
schedule: Optional["ScheduleSchema"] = Relationship()
|
211
|
-
pipeline: Optional["PipelineSchema"] = Relationship(
|
211
|
+
pipeline: Optional["PipelineSchema"] = Relationship()
|
212
212
|
trigger_execution: Optional["TriggerExecutionSchema"] = Relationship()
|
213
213
|
|
214
214
|
services: List["ServiceSchema"] = Relationship(
|
@@ -17,7 +17,8 @@ from typing import TYPE_CHECKING, Any, List, Optional
|
|
17
17
|
from uuid import UUID
|
18
18
|
|
19
19
|
from sqlalchemy import TEXT, Column, UniqueConstraint
|
20
|
-
from
|
20
|
+
from sqlalchemy.orm import object_session
|
21
|
+
from sqlmodel import Field, Relationship, desc, select
|
21
22
|
|
22
23
|
from zenml.enums import TaggableResourceTypes
|
23
24
|
from zenml.models import (
|
@@ -85,10 +86,6 @@ class PipelineSchema(NamedSchema, table=True):
|
|
85
86
|
schedules: List["ScheduleSchema"] = Relationship(
|
86
87
|
back_populates="pipeline",
|
87
88
|
)
|
88
|
-
runs: List["PipelineRunSchema"] = Relationship(
|
89
|
-
back_populates="pipeline",
|
90
|
-
sa_relationship_kwargs={"order_by": "PipelineRunSchema.created"},
|
91
|
-
)
|
92
89
|
builds: List["PipelineBuildSchema"] = Relationship(
|
93
90
|
back_populates="pipeline"
|
94
91
|
)
|
@@ -105,6 +102,34 @@ class PipelineSchema(NamedSchema, table=True):
|
|
105
102
|
),
|
106
103
|
)
|
107
104
|
|
105
|
+
@property
|
106
|
+
def latest_run(self) -> Optional["PipelineRunSchema"]:
|
107
|
+
"""Fetch the latest run for this pipeline.
|
108
|
+
|
109
|
+
Raises:
|
110
|
+
RuntimeError: If no session for the schema exists.
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
The latest run for this pipeline.
|
114
|
+
"""
|
115
|
+
from zenml.zen_stores.schemas import PipelineRunSchema
|
116
|
+
|
117
|
+
if session := object_session(self):
|
118
|
+
return (
|
119
|
+
session.execute(
|
120
|
+
select(PipelineRunSchema)
|
121
|
+
.where(PipelineRunSchema.pipeline_id == self.id)
|
122
|
+
.order_by(desc(PipelineRunSchema.created))
|
123
|
+
.limit(1)
|
124
|
+
)
|
125
|
+
.scalars()
|
126
|
+
.one_or_none()
|
127
|
+
)
|
128
|
+
else:
|
129
|
+
raise RuntimeError(
|
130
|
+
"Missing DB session to fetch latest run for pipeline."
|
131
|
+
)
|
132
|
+
|
108
133
|
@classmethod
|
109
134
|
def from_request(
|
110
135
|
cls,
|
@@ -141,10 +166,12 @@ class PipelineSchema(NamedSchema, table=True):
|
|
141
166
|
Returns:
|
142
167
|
The created PipelineResponse.
|
143
168
|
"""
|
169
|
+
latest_run = self.latest_run
|
170
|
+
|
144
171
|
body = PipelineResponseBody(
|
145
172
|
user=self.user.to_model() if self.user else None,
|
146
|
-
latest_run_id=
|
147
|
-
latest_run_status=
|
173
|
+
latest_run_id=latest_run.id if latest_run else None,
|
174
|
+
latest_run_status=latest_run.status if latest_run else None,
|
148
175
|
created=self.created,
|
149
176
|
updated=self.updated,
|
150
177
|
)
|
@@ -158,7 +185,7 @@ class PipelineSchema(NamedSchema, table=True):
|
|
158
185
|
|
159
186
|
resources = None
|
160
187
|
if include_resources:
|
161
|
-
latest_run_user =
|
188
|
+
latest_run_user = latest_run.user if latest_run else None
|
162
189
|
|
163
190
|
resources = PipelineResponseResources(
|
164
191
|
latest_run_user=latest_run_user.to_model()
|
@@ -18,7 +18,8 @@ from uuid import UUID
|
|
18
18
|
|
19
19
|
from sqlalchemy import Column, String, UniqueConstraint
|
20
20
|
from sqlalchemy.dialects.mysql import MEDIUMTEXT
|
21
|
-
from
|
21
|
+
from sqlalchemy.orm import object_session
|
22
|
+
from sqlmodel import Field, Relationship, col, desc, select
|
22
23
|
|
23
24
|
from zenml.constants import MEDIUMTEXT_MAX_LENGTH
|
24
25
|
from zenml.enums import TaggableResourceTypes
|
@@ -99,17 +100,6 @@ class RunTemplateSchema(BaseSchema, table=True):
|
|
99
100
|
}
|
100
101
|
)
|
101
102
|
|
102
|
-
runs: List["PipelineRunSchema"] = Relationship(
|
103
|
-
sa_relationship_kwargs={
|
104
|
-
"primaryjoin": "RunTemplateSchema.id==PipelineDeploymentSchema.template_id",
|
105
|
-
"secondaryjoin": "PipelineDeploymentSchema.id==PipelineRunSchema.deployment_id",
|
106
|
-
"secondary": "pipeline_deployment",
|
107
|
-
"cascade": "delete",
|
108
|
-
"viewonly": True,
|
109
|
-
"order_by": "PipelineRunSchema.created",
|
110
|
-
}
|
111
|
-
)
|
112
|
-
|
113
103
|
tags: List["TagSchema"] = Relationship(
|
114
104
|
sa_relationship_kwargs=dict(
|
115
105
|
primaryjoin=f"and_(foreign(TagResourceSchema.resource_type)=='{TaggableResourceTypes.RUN_TEMPLATE.value}', foreign(TagResourceSchema.resource_id)==RunTemplateSchema.id)",
|
@@ -120,6 +110,42 @@ class RunTemplateSchema(BaseSchema, table=True):
|
|
120
110
|
),
|
121
111
|
)
|
122
112
|
|
113
|
+
@property
|
114
|
+
def latest_run(self) -> Optional["PipelineRunSchema"]:
|
115
|
+
"""Fetch the latest run for this template.
|
116
|
+
|
117
|
+
Raises:
|
118
|
+
RuntimeError: If no session for the schema exists.
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
The latest run for this template.
|
122
|
+
"""
|
123
|
+
from zenml.zen_stores.schemas import (
|
124
|
+
PipelineDeploymentSchema,
|
125
|
+
PipelineRunSchema,
|
126
|
+
)
|
127
|
+
|
128
|
+
if session := object_session(self):
|
129
|
+
return (
|
130
|
+
session.execute(
|
131
|
+
select(PipelineRunSchema)
|
132
|
+
.join(
|
133
|
+
PipelineDeploymentSchema,
|
134
|
+
col(PipelineDeploymentSchema.id)
|
135
|
+
== col(PipelineRunSchema.deployment_id),
|
136
|
+
)
|
137
|
+
.where(PipelineDeploymentSchema.template_id == self.id)
|
138
|
+
.order_by(desc(PipelineRunSchema.created))
|
139
|
+
.limit(1)
|
140
|
+
)
|
141
|
+
.scalars()
|
142
|
+
.one_or_none()
|
143
|
+
)
|
144
|
+
else:
|
145
|
+
raise RuntimeError(
|
146
|
+
"Missing DB session to fetch latest run for template."
|
147
|
+
)
|
148
|
+
|
123
149
|
@classmethod
|
124
150
|
def from_request(
|
125
151
|
cls,
|
@@ -184,13 +210,15 @@ class RunTemplateSchema(BaseSchema, table=True):
|
|
184
210
|
):
|
185
211
|
runnable = True
|
186
212
|
|
213
|
+
latest_run = self.latest_run
|
214
|
+
|
187
215
|
body = RunTemplateResponseBody(
|
188
216
|
user=self.user.to_model() if self.user else None,
|
189
217
|
created=self.created,
|
190
218
|
updated=self.updated,
|
191
219
|
runnable=runnable,
|
192
|
-
latest_run_id=
|
193
|
-
latest_run_status=
|
220
|
+
latest_run_id=latest_run.id if latest_run else None,
|
221
|
+
latest_run_status=latest_run.status if latest_run else None,
|
194
222
|
)
|
195
223
|
|
196
224
|
metadata = None
|
@@ -303,7 +303,6 @@ from zenml.utils.enum_utils import StrEnum
|
|
303
303
|
from zenml.utils.networking_utils import (
|
304
304
|
replace_localhost_with_internal_hostname,
|
305
305
|
)
|
306
|
-
from zenml.utils.pydantic_utils import before_validator_handler
|
307
306
|
from zenml.utils.secret_utils import PlainSerializedSecretStr
|
308
307
|
from zenml.utils.string_utils import (
|
309
308
|
format_name_template,
|
@@ -434,6 +433,7 @@ class SqlZenStoreConfiguration(StoreConfiguration):
|
|
434
433
|
be created automatically on first access.
|
435
434
|
username: The database username.
|
436
435
|
password: The database password.
|
436
|
+
ssl: Whether to use SSL.
|
437
437
|
ssl_ca: certificate authority certificate. Required for SSL
|
438
438
|
enabled authentication if the CA certificate is not part of the
|
439
439
|
certificates shipped by the operating system.
|
@@ -463,6 +463,7 @@ class SqlZenStoreConfiguration(StoreConfiguration):
|
|
463
463
|
database: Optional[str] = None
|
464
464
|
username: Optional[PlainSerializedSecretStr] = None
|
465
465
|
password: Optional[PlainSerializedSecretStr] = None
|
466
|
+
ssl: bool = False
|
466
467
|
ssl_ca: Optional[PlainSerializedSecretStr] = None
|
467
468
|
ssl_cert: Optional[PlainSerializedSecretStr] = None
|
468
469
|
ssl_key: Optional[PlainSerializedSecretStr] = None
|
@@ -499,35 +500,6 @@ class SqlZenStoreConfiguration(StoreConfiguration):
|
|
499
500
|
|
500
501
|
return secrets_store
|
501
502
|
|
502
|
-
@model_validator(mode="before")
|
503
|
-
@classmethod
|
504
|
-
@before_validator_handler
|
505
|
-
def _remove_grpc_attributes(cls, data: Dict[str, Any]) -> Dict[str, Any]:
|
506
|
-
"""Removes old GRPC attributes.
|
507
|
-
|
508
|
-
Args:
|
509
|
-
data: All model attribute values.
|
510
|
-
|
511
|
-
Returns:
|
512
|
-
The model attribute values
|
513
|
-
"""
|
514
|
-
grpc_attribute_keys = [
|
515
|
-
"grpc_metadata_host",
|
516
|
-
"grpc_metadata_port",
|
517
|
-
"grpc_metadata_ssl_ca",
|
518
|
-
"grpc_metadata_ssl_key",
|
519
|
-
"grpc_metadata_ssl_cert",
|
520
|
-
]
|
521
|
-
grpc_values = [data.pop(key, None) for key in grpc_attribute_keys]
|
522
|
-
if any(grpc_values):
|
523
|
-
logger.warning(
|
524
|
-
"The GRPC attributes %s are unused and will be removed soon. "
|
525
|
-
"Please remove them from SQLZenStore configuration. This will "
|
526
|
-
"become an error in future versions of ZenML."
|
527
|
-
)
|
528
|
-
|
529
|
-
return data
|
530
|
-
|
531
503
|
@model_validator(mode="after")
|
532
504
|
def _validate_backup_strategy(self) -> "SqlZenStoreConfiguration":
|
533
505
|
"""Validate the backup strategy.
|
@@ -641,15 +613,21 @@ class SqlZenStoreConfiguration(StoreConfiguration):
|
|
641
613
|
return None
|
642
614
|
|
643
615
|
for k, v in sql_url.query.items():
|
644
|
-
if k == "
|
616
|
+
if k == "ssl":
|
617
|
+
if r := _get_query_result(v):
|
618
|
+
self.ssl = is_true_string_value(r)
|
619
|
+
elif k == "ssl_ca":
|
645
620
|
if r := _get_query_result(v):
|
646
621
|
self.ssl_ca = PlainSerializedSecretStr(r)
|
622
|
+
self.ssl = True
|
647
623
|
elif k == "ssl_cert":
|
648
624
|
if r := _get_query_result(v):
|
649
625
|
self.ssl_cert = PlainSerializedSecretStr(r)
|
626
|
+
self.ssl = True
|
650
627
|
elif k == "ssl_key":
|
651
628
|
if r := _get_query_result(v):
|
652
629
|
self.ssl_key = PlainSerializedSecretStr(r)
|
630
|
+
self.ssl = True
|
653
631
|
elif k == "ssl_verify_server_cert":
|
654
632
|
if r := _get_query_result(v):
|
655
633
|
if is_true_string_value(r):
|
@@ -659,7 +637,7 @@ class SqlZenStoreConfiguration(StoreConfiguration):
|
|
659
637
|
else:
|
660
638
|
raise ValueError(
|
661
639
|
"Invalid MySQL URL query parameter `%s`: The "
|
662
|
-
"parameter must be one of: ssl_ca, ssl_cert, "
|
640
|
+
"parameter must be one of: ssl, ssl_ca, ssl_cert, "
|
663
641
|
"ssl_key, or ssl_verify_server_cert.",
|
664
642
|
k,
|
665
643
|
)
|
@@ -728,15 +706,6 @@ class SqlZenStoreConfiguration(StoreConfiguration):
|
|
728
706
|
"""
|
729
707
|
return make_url(url).drivername in SQLDatabaseDriver.values()
|
730
708
|
|
731
|
-
def expand_certificates(self) -> None:
|
732
|
-
"""Expands the certificates in the verify_ssl field."""
|
733
|
-
# Load the certificate values back into the configuration
|
734
|
-
for key in ["ssl_key", "ssl_ca", "ssl_cert"]:
|
735
|
-
file_path = getattr(self, key, None)
|
736
|
-
if file_path and os.path.isfile(file_path.get_secret_value()):
|
737
|
-
with open(file_path, "r") as f:
|
738
|
-
setattr(self, key, f.read())
|
739
|
-
|
740
709
|
def get_sqlalchemy_config(
|
741
710
|
self,
|
742
711
|
database: Optional[str] = None,
|
@@ -789,22 +758,19 @@ class SqlZenStoreConfiguration(StoreConfiguration):
|
|
789
758
|
sqlalchemy_ssl_args: Dict[str, Any] = {}
|
790
759
|
|
791
760
|
# Handle SSL params
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
761
|
+
if self.ssl:
|
762
|
+
sqlalchemy_ssl_args["ssl"] = True
|
763
|
+
for key in ["ssl_key", "ssl_ca", "ssl_cert"]:
|
764
|
+
ssl_setting = getattr(self, key)
|
765
|
+
if not ssl_setting:
|
766
|
+
continue
|
767
|
+
if not os.path.isfile(ssl_setting.get_secret_value()):
|
768
|
+
logger.warning(
|
769
|
+
f"Database SSL setting `{key}` is not a file. "
|
770
|
+
)
|
771
|
+
sqlalchemy_ssl_args[key.removeprefix("ssl_")] = (
|
772
|
+
ssl_setting.get_secret_value()
|
799
773
|
)
|
800
|
-
sqlalchemy_ssl_args[key.lstrip("ssl_")] = (
|
801
|
-
ssl_setting.get_secret_value()
|
802
|
-
)
|
803
|
-
sqlalchemy_ssl_args[key.removeprefix("ssl_")] = (
|
804
|
-
ssl_setting.get_secret_value()
|
805
|
-
)
|
806
|
-
|
807
|
-
if len(sqlalchemy_ssl_args) > 0:
|
808
774
|
sqlalchemy_ssl_args["check_hostname"] = (
|
809
775
|
self.ssl_verify_server_cert
|
810
776
|
)
|