zenml-nightly 0.72.0.dev20250121__py3-none-any.whl → 0.73.0.dev20250124__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 +2 -0
- zenml/cli/server.py +1 -0
- zenml/cli/service_connectors.py +8 -4
- zenml/cli/stack.py +2 -2
- zenml/config/pipeline_configurations.py +2 -2
- zenml/config/server_config.py +20 -0
- zenml/enums.py +1 -0
- zenml/event_hub/base_event_hub.py +2 -2
- zenml/integrations/airflow/orchestrators/airflow_orchestrator.py +4 -2
- zenml/integrations/aws/__init__.py +2 -1
- zenml/integrations/aws/flavors/sagemaker_orchestrator_flavor.py +15 -0
- zenml/integrations/aws/orchestrators/sagemaker_orchestrator.py +308 -70
- zenml/integrations/gcp/__init__.py +3 -0
- zenml/integrations/gcp/experiment_trackers/__init__.py +18 -0
- zenml/integrations/gcp/experiment_trackers/vertex_experiment_tracker.py +214 -0
- zenml/integrations/gcp/flavors/__init__.py +6 -0
- zenml/integrations/gcp/flavors/vertex_experiment_tracker_flavor.py +199 -0
- zenml/integrations/huggingface/__init__.py +1 -6
- zenml/integrations/kubernetes/orchestrators/kube_utils.py +2 -2
- zenml/integrations/mlflow/experiment_trackers/mlflow_experiment_tracker.py +0 -1
- zenml/integrations/whylogs/data_validators/whylogs_data_validator.py +3 -1
- zenml/models/v2/core/api_key.py +2 -2
- zenml/models/v2/core/schedule.py +16 -1
- zenml/orchestrators/publish_utils.py +4 -4
- zenml/orchestrators/step_launcher.py +3 -3
- zenml/orchestrators/step_run_utils.py +2 -2
- zenml/pipelines/run_utils.py +2 -2
- zenml/service_connectors/service_connector.py +2 -2
- zenml/stack/stack.py +3 -3
- zenml/stack/stack_component.py +10 -2
- zenml/stack_deployments/stack_deployment.py +5 -0
- zenml/utils/git_utils.py +1 -1
- zenml/utils/string_utils.py +2 -2
- zenml/zen_server/auth.py +13 -6
- zenml/zen_server/dashboard/assets/{404-Dfq64Boz.js → 404-c8OuXDAT.js} +1 -1
- zenml/zen_server/dashboard/assets/{@reactflow-BUNIMFeC.js → @reactflow-6JPoencd.js} +1 -1
- zenml/zen_server/dashboard/assets/{AlertDialogDropdownItem-B73Vs10T.js → AlertDialogDropdownItem-8yPFDxEI.js} +1 -1
- zenml/zen_server/dashboard/assets/{CodeSnippet-DIJRT2NT.js → CodeSnippet-Qh1ae_DJ.js} +1 -1
- zenml/zen_server/dashboard/assets/{CollapsibleCard-BzUHGZOU.js → CollapsibleCard-TiI4lId1.js} +1 -1
- zenml/zen_server/dashboard/assets/{Commands-BEGyld4c.js → Commands-BcR2Arie.js} +1 -1
- zenml/zen_server/dashboard/assets/{ComponentBadge-xyKiek1s.js → ComponentBadge-BqQNUZgb.js} +1 -1
- zenml/zen_server/dashboard/assets/{CopyButton-DhW-mapu.js → CopyButton-DCiXO3JC.js} +1 -1
- zenml/zen_server/dashboard/assets/{CsvVizualization-D8oazBiE.js → CsvVizualization-O9cVIaL8.js} +1 -1
- zenml/zen_server/dashboard/assets/{DeleteAlertDialog-WkSIIgfy.js → DeleteAlertDialog-DrPjHtXX.js} +1 -1
- zenml/zen_server/dashboard/assets/{DialogItem-Bgroeg29.js → DialogItem-BYG7d_M2.js} +1 -1
- zenml/zen_server/dashboard/assets/{Error-CY5tlu17.js → Error-C1zbWr19.js} +1 -1
- zenml/zen_server/dashboard/assets/{ExecutionStatus-G8mjIaeA.js → ExecutionStatus-Ct9srgHC.js} +1 -1
- zenml/zen_server/dashboard/assets/{Helpbox-Bb1ed--O.js → Helpbox-Bm_1Zx9f.js} +1 -1
- zenml/zen_server/dashboard/assets/{Infobox-Da6-76M2.js → Infobox-OQdkCLSP.js} +1 -1
- zenml/zen_server/dashboard/assets/{InlineAvatar-DqnZaBNq.js → InlineAvatar-CQNjKoEQ.js} +1 -1
- zenml/zen_server/dashboard/assets/{NestedCollapsible-aK5ojKoF.js → NestedCollapsible-DDgd2SGb.js} +1 -1
- zenml/zen_server/dashboard/assets/Partials-MD3e95Dk.js +1 -0
- zenml/zen_server/dashboard/assets/{ProBadge-B4tRUYve.js → ProBadge-D784iVNC.js} +1 -1
- zenml/zen_server/dashboard/assets/{ProCta-CZuP29Qz.js → ProCta-W2PEvNow.js} +1 -1
- zenml/zen_server/dashboard/assets/{ProviderIcon-Bd7GUQ1_.js → ProviderIcon-DfDUOeAy.js} +1 -1
- zenml/zen_server/dashboard/assets/{ProviderRadio-mstdqzsS.js → ProviderRadio-B81Elxrc.js} +1 -1
- zenml/zen_server/dashboard/assets/{RunSelector-CsruSB4i.js → RunSelector-DOXgdry5.js} +1 -1
- zenml/zen_server/dashboard/assets/{RunsBody-DxxtWVYz.js → RunsBody-Bnx2fxub.js} +1 -1
- zenml/zen_server/dashboard/assets/SearchField-Yjv-KRW4.js +1 -0
- zenml/zen_server/dashboard/assets/{SecretTooltip-CLzJIYW_.js → SecretTooltip-EKpMlG2f.js} +1 -1
- zenml/zen_server/dashboard/assets/{SetPassword-Yn50ooBC.js → SetPassword-CDLy57PZ.js} +1 -1
- zenml/zen_server/dashboard/assets/StackList-DKQaLDo4.js +1 -0
- zenml/zen_server/dashboard/assets/{Tabs-CNv-eTYM.js → Tabs-B5E-o_h6.js} +1 -1
- zenml/zen_server/dashboard/assets/{Tick-jEIevzVf.js → Tick-DSYBiuXU.js} +1 -1
- zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-C16GW-kX.js → UpdatePasswordSchemas-HBNOeyoP.js} +1 -1
- zenml/zen_server/dashboard/assets/{UsageReason-Bf2tzhv1.js → UsageReason-DXtPS5nE.js} +1 -1
- zenml/zen_server/dashboard/assets/{WizardFooter-D6i-AP1K.js → WizardFooter-_1VSMZ_c.js} +1 -1
- zenml/zen_server/dashboard/assets/{all-pipeline-runs-query-DUti43aF.js → all-pipeline-runs-query-D0qDLdKB.js} +1 -1
- zenml/zen_server/dashboard/assets/{create-stack-Ch2WPs9U.js → create-stack-7JzgAYAm.js} +1 -1
- zenml/zen_server/dashboard/assets/{delete-run-Byf9hTjA.js → delete-run-CUdtYFLl.js} +1 -1
- zenml/zen_server/dashboard/assets/{form-schemas-BZqKBPBF.js → form-schemas-B6PCV3Y4.js} +1 -1
- zenml/zen_server/dashboard/assets/index-B6U0OkEN.css +1 -0
- zenml/zen_server/dashboard/assets/{index-CyBKZcpO.js → index-CJ5IfeAl.js} +1 -1
- zenml/zen_server/dashboard/assets/{index-CtdYkjUi.js → index-Ceyzb1yI.js} +1 -1
- zenml/zen_server/dashboard/assets/{index-CE0aQlv8.js → index-CxO6541P.js} +3 -3
- zenml/zen_server/dashboard/assets/{index-v6gQjDEo.js → index-D4yoZ_gH.js} +1 -1
- zenml/zen_server/dashboard/assets/{login-mutation-DNDVp_2H.js → login-mutation-BaeJ7MAg.js} +1 -1
- zenml/zen_server/dashboard/assets/{not-found-Bmup4ctE.js → not-found-MGptrNBk.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DGlm1RVc.js → page-Aeu3v0MQ.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CltCNL0T.js → page-BCgEdmhP.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Hn8q9iJZ.js → page-BKwwfTNy.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BNxYrN0q.js → page-BUjw8Tp1.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BYJfqgLN.js → page-BXgXP-Qj.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DN4BVIOL.js → page-BXrtxEbw.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CHRn1fQm.js → page-BaUDR9Ri.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DlIi5ThM.js → page-BbljjC-k.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BrmJp1Wt.js → page-BhOXn-s9.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Cc8ZEuj4.js → page-C37IDa-Q.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Dif8CWyZ.js → page-C4JpDeUM.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BC27C_OI.js → page-CB2_GdBA.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-B5Sr8pib.js → page-CBiT2Ox9.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-IhckKFnD.js → page-CXPc-HN1.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Dth9X1Ih.js → page-CbwI6emp.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DweqqCkF.js → page-CeNL9JWi.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-LyZ_l8vR.js → page-CkPwPmLZ.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-C70wZtV2.js → page-CmJU3Gqo.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-D9Oh05fl.js → page-CoFVtzhG.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-PamGpk0j.js → page-D-KPzeQb.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DoW7YxTu.js → page-DKQ3wZgr.js} +1 -1
- zenml/zen_server/dashboard/assets/page-DWWhxCoF.js +1 -0
- zenml/zen_server/dashboard/assets/{page-CmlYj7Nl.js → page-DbW8MfQ4.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CWr96ZKN.js → page-Dv5lN2w7.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-ANYGfEUL.js → page-Dvbq1BoF.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-D6Ev5P8V.js → page-DyAuja95.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DyOJ_pq3.js → page-DzrdL2v1.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CXAbSyp9.js → page-I2B4Ocv8.js} +1 -1
- zenml/zen_server/dashboard/assets/page-OdjGauvw.js +2 -0
- zenml/zen_server/dashboard/assets/{page-CaeI9ptC.js → page-Ox-eC1ik.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-B_0XkV48.js → page-khp8QJ6b.js} +1 -1
- zenml/zen_server/dashboard/assets/{page--XLMzHrn.js → page-yNh6PQKt.js} +1 -1
- zenml/zen_server/dashboard/assets/{persist-vP0-Xl4f.js → persist-DBTFy--v.js} +1 -1
- zenml/zen_server/dashboard/assets/{persist-DeXRG61d.js → persist-K7AY0ju4.js} +1 -1
- zenml/zen_server/dashboard/assets/{service-DH_oUqQj.js → service-BvOYLH5b.js} +1 -1
- zenml/zen_server/dashboard/assets/{sharedSchema-Bw1_Wa7l.js → sharedSchema-xJDsJNgJ.js} +1 -1
- zenml/zen_server/dashboard/assets/{stack-detail-query-B_0R_fd6.js → stack-detail-query-DMJoxwgv.js} +1 -1
- zenml/zen_server/dashboard/assets/{update-server-settings-mutation-D9qYhfaN.js → update-server-settings-mutation-ATZDNNZk.js} +1 -1
- zenml/zen_server/dashboard/assets/{url-Dh93fvh0.js → url-BWJXzuI4.js} +1 -1
- zenml/zen_server/dashboard/index.html +4 -4
- zenml/zen_server/deploy/helm/Chart.yaml +1 -1
- zenml/zen_server/deploy/helm/README.md +2 -2
- zenml/zen_server/deploy/helm/templates/server-db-job.yaml +5 -3
- zenml/zen_server/deploy/helm/values.yaml +4 -0
- zenml/zen_server/routers/devices_endpoints.py +4 -2
- zenml/zen_server/routers/workspaces_endpoints.py +2 -0
- zenml/zen_stores/migrations/versions/0.73.0_release.py +23 -0
- zenml/zen_stores/migrations/versions/25155145c545_separate_actions_and_triggers.py +2 -2
- zenml/zen_stores/migrations/versions/46506f72f0ed_add_server_settings.py +2 -2
- zenml/zen_stores/migrations/versions/5994f9ad0489_introduce_role_permissions.py +6 -6
- zenml/zen_stores/migrations/versions/7500f434b71c_remove_shared_columns.py +2 -2
- zenml/zen_stores/migrations/versions/a91762e6be36_artifact_version_table.py +3 -3
- zenml/zen_stores/schemas/action_schemas.py +2 -2
- zenml/zen_stores/schemas/api_key_schemas.py +4 -4
- zenml/zen_stores/schemas/artifact_schemas.py +3 -3
- zenml/zen_stores/schemas/base_schemas.py +7 -3
- zenml/zen_stores/schemas/code_repository_schemas.py +2 -2
- zenml/zen_stores/schemas/component_schemas.py +2 -2
- zenml/zen_stores/schemas/device_schemas.py +4 -4
- zenml/zen_stores/schemas/event_source_schemas.py +2 -2
- zenml/zen_stores/schemas/flavor_schemas.py +2 -2
- zenml/zen_stores/schemas/model_schemas.py +3 -3
- zenml/zen_stores/schemas/pipeline_run_schemas.py +10 -3
- zenml/zen_stores/schemas/pipeline_schemas.py +2 -2
- zenml/zen_stores/schemas/run_template_schemas.py +2 -2
- zenml/zen_stores/schemas/schedule_schema.py +19 -4
- zenml/zen_stores/schemas/secret_schemas.py +2 -2
- zenml/zen_stores/schemas/server_settings_schemas.py +9 -5
- zenml/zen_stores/schemas/service_connector_schemas.py +2 -2
- zenml/zen_stores/schemas/service_schemas.py +2 -2
- zenml/zen_stores/schemas/stack_schemas.py +2 -2
- zenml/zen_stores/schemas/step_run_schemas.py +2 -2
- zenml/zen_stores/schemas/tag_schemas.py +2 -2
- zenml/zen_stores/schemas/trigger_schemas.py +2 -2
- zenml/zen_stores/schemas/user_schemas.py +3 -3
- zenml/zen_stores/schemas/workspace_schemas.py +2 -2
- zenml/zen_stores/sql_zen_store.py +10 -1
- {zenml_nightly-0.72.0.dev20250121.dist-info → zenml_nightly-0.73.0.dev20250124.dist-info}/METADATA +3 -3
- {zenml_nightly-0.72.0.dev20250121.dist-info → zenml_nightly-0.73.0.dev20250124.dist-info}/RECORD +161 -157
- zenml/zen_server/dashboard/assets/Partials-CqZp5NMX.js +0 -1
- zenml/zen_server/dashboard/assets/SearchField-D6tPxyqw.js +0 -1
- zenml/zen_server/dashboard/assets/StackList-U537qoYd.js +0 -1
- zenml/zen_server/dashboard/assets/index-DXvT1_Um.css +0 -1
- zenml/zen_server/dashboard/assets/page-C2nU3Gxn.js +0 -1
- zenml/zen_server/dashboard/assets/page-PxOWfKgF.js +0 -2
- {zenml_nightly-0.72.0.dev20250121.dist-info → zenml_nightly-0.73.0.dev20250124.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.72.0.dev20250121.dist-info → zenml_nightly-0.73.0.dev20250124.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.72.0.dev20250121.dist-info → zenml_nightly-0.73.0.dev20250124.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,214 @@
|
|
1
|
+
# Copyright (c) ZenML GmbH 2022. 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
|
+
"""Implementation of the VertexAI experiment tracker for ZenML."""
|
15
|
+
|
16
|
+
import re
|
17
|
+
from typing import TYPE_CHECKING, Dict, Optional, Type, cast
|
18
|
+
|
19
|
+
from google.api_core import exceptions
|
20
|
+
from google.cloud import aiplatform
|
21
|
+
from google.cloud.aiplatform.compat.types import execution
|
22
|
+
|
23
|
+
from zenml.constants import METADATA_EXPERIMENT_TRACKER_URL
|
24
|
+
from zenml.experiment_trackers.base_experiment_tracker import (
|
25
|
+
BaseExperimentTracker,
|
26
|
+
)
|
27
|
+
from zenml.integrations.gcp.flavors.vertex_experiment_tracker_flavor import (
|
28
|
+
VertexExperimentTrackerConfig,
|
29
|
+
VertexExperimentTrackerSettings,
|
30
|
+
)
|
31
|
+
from zenml.integrations.gcp.google_credentials_mixin import (
|
32
|
+
GoogleCredentialsMixin,
|
33
|
+
)
|
34
|
+
from zenml.logger import get_logger
|
35
|
+
from zenml.metadata.metadata_types import Uri
|
36
|
+
|
37
|
+
if TYPE_CHECKING:
|
38
|
+
from zenml.config.step_run_info import StepRunInfo
|
39
|
+
from zenml.metadata.metadata_types import MetadataType
|
40
|
+
|
41
|
+
logger = get_logger(__name__)
|
42
|
+
|
43
|
+
|
44
|
+
class VertexExperimentTracker(BaseExperimentTracker, GoogleCredentialsMixin):
|
45
|
+
"""Track experiments using VertexAI."""
|
46
|
+
|
47
|
+
@property
|
48
|
+
def config(self) -> VertexExperimentTrackerConfig:
|
49
|
+
"""Returns the `VertexExperimentTrackerConfig` config.
|
50
|
+
|
51
|
+
Returns:
|
52
|
+
The configuration.
|
53
|
+
"""
|
54
|
+
return cast(VertexExperimentTrackerConfig, self._config)
|
55
|
+
|
56
|
+
@property
|
57
|
+
def settings_class(self) -> Type[VertexExperimentTrackerSettings]:
|
58
|
+
"""Returns the `BaseSettings` settings class.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
The settings class.
|
62
|
+
"""
|
63
|
+
return VertexExperimentTrackerSettings
|
64
|
+
|
65
|
+
def prepare_step_run(self, info: "StepRunInfo") -> None:
|
66
|
+
"""Configures a VertexAI run.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
info: Info about the step that will be executed.
|
70
|
+
"""
|
71
|
+
self._initialize_vertex(info=info)
|
72
|
+
self.experiment_name = self._get_experiment_name(info=info)
|
73
|
+
self.run_name = self._get_run_name(info=info)
|
74
|
+
|
75
|
+
def get_step_run_metadata(
|
76
|
+
self, info: "StepRunInfo"
|
77
|
+
) -> Dict[str, "MetadataType"]:
|
78
|
+
"""Get component- and step-specific metadata after a step ran.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
info: Info about the step that was executed.
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
A dictionary of metadata.
|
85
|
+
"""
|
86
|
+
experiment_name = self._get_experiment_name(info=info)
|
87
|
+
run_name = self._get_run_name(info=info)
|
88
|
+
tensorboard_resource_name = self._get_tensorboard_resource_name(
|
89
|
+
experiment=experiment_name
|
90
|
+
)
|
91
|
+
dashboard_url = self._get_dashboard_url(experiment=experiment_name)
|
92
|
+
return {
|
93
|
+
METADATA_EXPERIMENT_TRACKER_URL: Uri(dashboard_url),
|
94
|
+
"tensorboard_resource_name": tensorboard_resource_name or "",
|
95
|
+
"vertex_run_name": run_name,
|
96
|
+
}
|
97
|
+
|
98
|
+
def _format_name(self, name: str) -> str:
|
99
|
+
return re.sub(r"[^a-z0-9-]", "-", name.strip().lower())[:128].rstrip(
|
100
|
+
"-"
|
101
|
+
)
|
102
|
+
|
103
|
+
def _get_experiment_name(self, info: "StepRunInfo") -> str:
|
104
|
+
"""Gets the experiment name.
|
105
|
+
|
106
|
+
Args:
|
107
|
+
info: Info about the step.
|
108
|
+
|
109
|
+
Returns:
|
110
|
+
The experiment name.
|
111
|
+
"""
|
112
|
+
settings = cast(
|
113
|
+
VertexExperimentTrackerSettings, self.get_settings(info)
|
114
|
+
)
|
115
|
+
name = settings.experiment or info.pipeline.name
|
116
|
+
return self._format_name(name)
|
117
|
+
|
118
|
+
def _get_run_name(self, info: "StepRunInfo") -> str:
|
119
|
+
"""Gets the run name.
|
120
|
+
|
121
|
+
Args:
|
122
|
+
info: Info about the step that will be executed.
|
123
|
+
|
124
|
+
Returns:
|
125
|
+
The run name.
|
126
|
+
"""
|
127
|
+
return self._format_name(info.run_name)
|
128
|
+
|
129
|
+
def _get_dashboard_url(self, experiment: str) -> str:
|
130
|
+
"""Gets the run URL.
|
131
|
+
|
132
|
+
Args:
|
133
|
+
experiment: The name of the experiment.
|
134
|
+
|
135
|
+
Returns:
|
136
|
+
The run URL.
|
137
|
+
"""
|
138
|
+
resource = aiplatform.Experiment(experiment_name=experiment)
|
139
|
+
return cast(str, resource.dashboard_url)
|
140
|
+
|
141
|
+
def _get_tensorboard_resource_name(self, experiment: str) -> Optional[str]:
|
142
|
+
resource = aiplatform.Experiment(
|
143
|
+
experiment_name=experiment
|
144
|
+
).get_backing_tensorboard_resource()
|
145
|
+
resource_name = (
|
146
|
+
str(resource.resource_name) if resource is not None else None
|
147
|
+
)
|
148
|
+
return resource_name
|
149
|
+
|
150
|
+
def _initialize_vertex(self, info: "StepRunInfo") -> None:
|
151
|
+
"""Initializes a VertexAI run.
|
152
|
+
|
153
|
+
Args:
|
154
|
+
info: Info about the step that will be executed.
|
155
|
+
"""
|
156
|
+
settings = cast(
|
157
|
+
VertexExperimentTrackerSettings, self.get_settings(info)
|
158
|
+
)
|
159
|
+
experiment = self._get_experiment_name(info=info)
|
160
|
+
run_name = self._get_run_name(info=info)
|
161
|
+
credentials, project = self._get_authentication()
|
162
|
+
logger.info(
|
163
|
+
f"Initializing VertexAI with experiment name {experiment} "
|
164
|
+
f"and run name {run_name}."
|
165
|
+
)
|
166
|
+
|
167
|
+
aiplatform.init(
|
168
|
+
project=project,
|
169
|
+
location=self.config.location,
|
170
|
+
experiment=experiment,
|
171
|
+
experiment_tensorboard=settings.experiment_tensorboard,
|
172
|
+
staging_bucket=self.config.staging_bucket,
|
173
|
+
credentials=credentials,
|
174
|
+
encryption_spec_key_name=self.config.encryption_spec_key_name,
|
175
|
+
network=self.config.network,
|
176
|
+
api_endpoint=self.config.api_endpoint,
|
177
|
+
api_key=self.config.api_key,
|
178
|
+
api_transport=self.config.api_transport,
|
179
|
+
request_metadata=self.config.request_metadata,
|
180
|
+
)
|
181
|
+
|
182
|
+
try:
|
183
|
+
aiplatform.start_run(
|
184
|
+
run=run_name,
|
185
|
+
tensorboard=settings.experiment_tensorboard,
|
186
|
+
resume=True,
|
187
|
+
)
|
188
|
+
except exceptions.NotFound:
|
189
|
+
aiplatform.start_run(
|
190
|
+
run=run_name,
|
191
|
+
tensorboard=settings.experiment_tensorboard,
|
192
|
+
resume=False,
|
193
|
+
)
|
194
|
+
|
195
|
+
logger.info(
|
196
|
+
f"VertexAI experiment dashboard: {self._get_dashboard_url(experiment=experiment)}"
|
197
|
+
)
|
198
|
+
logger.info(
|
199
|
+
f"Tensorboard resource name: {self._get_tensorboard_resource_name(experiment=experiment)}"
|
200
|
+
)
|
201
|
+
|
202
|
+
def cleanup_step_run(self, info: "StepRunInfo", step_failed: bool) -> None:
|
203
|
+
"""Stops the VertexAI run.
|
204
|
+
|
205
|
+
Args:
|
206
|
+
info: Info about the step that was executed.
|
207
|
+
step_failed: Whether the step failed or not.
|
208
|
+
"""
|
209
|
+
state = (
|
210
|
+
execution.Execution.State.FAILED
|
211
|
+
if step_failed
|
212
|
+
else execution.Execution.State.COMPLETE
|
213
|
+
)
|
214
|
+
aiplatform.end_run(state=state)
|
@@ -21,6 +21,10 @@ from zenml.integrations.gcp.flavors.gcp_image_builder_flavor import (
|
|
21
21
|
GCPImageBuilderConfig,
|
22
22
|
GCPImageBuilderFlavor,
|
23
23
|
)
|
24
|
+
from zenml.integrations.gcp.flavors.vertex_experiment_tracker_flavor import (
|
25
|
+
VertexExperimentTrackerConfig,
|
26
|
+
VertexExperimentTrackerFlavor,
|
27
|
+
)
|
24
28
|
from zenml.integrations.gcp.flavors.vertex_orchestrator_flavor import (
|
25
29
|
VertexOrchestratorConfig,
|
26
30
|
VertexOrchestratorFlavor,
|
@@ -35,6 +39,8 @@ __all__ = [
|
|
35
39
|
"GCPArtifactStoreConfig",
|
36
40
|
"GCPImageBuilderFlavor",
|
37
41
|
"GCPImageBuilderConfig",
|
42
|
+
"VertexExperimentTrackerFlavor",
|
43
|
+
"VertexExperimentTrackerConfig",
|
38
44
|
"VertexOrchestratorFlavor",
|
39
45
|
"VertexOrchestratorConfig",
|
40
46
|
"VertexStepOperatorFlavor",
|
@@ -0,0 +1,199 @@
|
|
1
|
+
# Copyright (c) ZenML GmbH 2022. 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
|
+
"""Vertex experiment tracker flavor."""
|
15
|
+
|
16
|
+
import re
|
17
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional, Type, Union
|
18
|
+
|
19
|
+
from pydantic import field_validator
|
20
|
+
|
21
|
+
from zenml.config.base_settings import BaseSettings
|
22
|
+
from zenml.experiment_trackers.base_experiment_tracker import (
|
23
|
+
BaseExperimentTrackerConfig,
|
24
|
+
BaseExperimentTrackerFlavor,
|
25
|
+
)
|
26
|
+
from zenml.integrations.gcp import (
|
27
|
+
GCP_RESOURCE_TYPE,
|
28
|
+
GCP_VERTEX_EXPERIMENT_TRACKER_FLAVOR,
|
29
|
+
)
|
30
|
+
from zenml.integrations.gcp.google_credentials_mixin import (
|
31
|
+
GoogleCredentialsConfigMixin,
|
32
|
+
)
|
33
|
+
from zenml.models import ServiceConnectorRequirements
|
34
|
+
from zenml.utils.secret_utils import SecretField
|
35
|
+
|
36
|
+
if TYPE_CHECKING:
|
37
|
+
from zenml.integrations.gcp.experiment_trackers import (
|
38
|
+
VertexExperimentTracker,
|
39
|
+
)
|
40
|
+
|
41
|
+
|
42
|
+
class VertexExperimentTrackerSettings(BaseSettings):
|
43
|
+
"""Settings for the VertexAI experiment tracker.
|
44
|
+
|
45
|
+
Attributes:
|
46
|
+
experiment: The VertexAI experiment name.
|
47
|
+
experiment_tensorboard: The VertexAI experiment tensorboard.
|
48
|
+
"""
|
49
|
+
|
50
|
+
experiment: Optional[str] = None
|
51
|
+
experiment_tensorboard: Optional[Union[str, bool]] = None
|
52
|
+
|
53
|
+
@field_validator("experiment", mode="before")
|
54
|
+
def _validate_experiment(cls, value: str) -> str:
|
55
|
+
"""Validates the experiment name matches the the allowed format.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
value: The experiment.
|
59
|
+
|
60
|
+
Raises:
|
61
|
+
ValueError: If the experiment name does not match the expected
|
62
|
+
format.
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
The experiment.
|
66
|
+
"""
|
67
|
+
if value and not re.match(r"^[a-z0-9][a-z0-9-]{0,127}$", value):
|
68
|
+
raise ValueError(
|
69
|
+
"Experiment name must match regex [a-z0-9][a-z0-9-]{0,127}"
|
70
|
+
)
|
71
|
+
return value
|
72
|
+
|
73
|
+
|
74
|
+
class VertexExperimentTrackerConfig(
|
75
|
+
BaseExperimentTrackerConfig,
|
76
|
+
GoogleCredentialsConfigMixin,
|
77
|
+
VertexExperimentTrackerSettings,
|
78
|
+
):
|
79
|
+
"""Config for the VertexAI experiment tracker.
|
80
|
+
|
81
|
+
Attributes:
|
82
|
+
location: Optional. The default location to use when making API calls. If not
|
83
|
+
set defaults to us-central1.
|
84
|
+
staging_bucket: Optional. The default staging bucket to use to stage artifacts
|
85
|
+
when making API calls. In the form gs://...
|
86
|
+
network:
|
87
|
+
Optional. The full name of the Compute Engine network to which jobs
|
88
|
+
and resources should be peered. E.g. "projects/12345/global/networks/myVPC".
|
89
|
+
Private services access must already be configured for the network.
|
90
|
+
If specified, all eligible jobs and resources created will be peered
|
91
|
+
with this VPC.
|
92
|
+
encryption_spec_key_name:
|
93
|
+
Optional. The Cloud KMS resource identifier of the customer
|
94
|
+
managed encryption key used to protect a resource. Has the
|
95
|
+
form:
|
96
|
+
``projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key``.
|
97
|
+
The key needs to be in the same region as where the compute
|
98
|
+
resource is created.
|
99
|
+
api_endpoint (str):
|
100
|
+
Optional. The desired API endpoint,
|
101
|
+
e.g., us-central1-aiplatform.googleapis.com
|
102
|
+
api_key (str):
|
103
|
+
Optional. The API key to use for service calls.
|
104
|
+
NOTE: Not all services support API keys.
|
105
|
+
api_transport (str):
|
106
|
+
Optional. The transport method which is either 'grpc' or 'rest'.
|
107
|
+
NOTE: "rest" transport functionality is currently in a
|
108
|
+
beta state (preview).
|
109
|
+
request_metadata:
|
110
|
+
Optional. Additional gRPC metadata to send with every client request.
|
111
|
+
"""
|
112
|
+
|
113
|
+
location: Optional[str] = None
|
114
|
+
staging_bucket: Optional[str] = None
|
115
|
+
network: Optional[str] = None
|
116
|
+
encryption_spec_key_name: Optional[str] = SecretField(default=None)
|
117
|
+
api_endpoint: Optional[str] = SecretField(default=None)
|
118
|
+
api_key: Optional[str] = SecretField(default=None)
|
119
|
+
api_transport: Optional[str] = None
|
120
|
+
request_metadata: Optional[Dict[str, Any]] = None
|
121
|
+
|
122
|
+
|
123
|
+
class VertexExperimentTrackerFlavor(BaseExperimentTrackerFlavor):
|
124
|
+
"""Flavor for the VertexAI experiment tracker."""
|
125
|
+
|
126
|
+
@property
|
127
|
+
def name(self) -> str:
|
128
|
+
"""Name of the flavor.
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
The name of the flavor.
|
132
|
+
"""
|
133
|
+
return GCP_VERTEX_EXPERIMENT_TRACKER_FLAVOR
|
134
|
+
|
135
|
+
@property
|
136
|
+
def docs_url(self) -> Optional[str]:
|
137
|
+
"""A URL to point at docs explaining this flavor.
|
138
|
+
|
139
|
+
Returns:
|
140
|
+
A flavor docs url.
|
141
|
+
"""
|
142
|
+
return self.generate_default_docs_url()
|
143
|
+
|
144
|
+
@property
|
145
|
+
def sdk_docs_url(self) -> Optional[str]:
|
146
|
+
"""A URL to point at SDK docs explaining this flavor.
|
147
|
+
|
148
|
+
Returns:
|
149
|
+
A flavor SDK docs url.
|
150
|
+
"""
|
151
|
+
return self.generate_default_sdk_docs_url()
|
152
|
+
|
153
|
+
@property
|
154
|
+
def logo_url(self) -> str:
|
155
|
+
"""A URL to represent the flavor in the dashboard.
|
156
|
+
|
157
|
+
Returns:
|
158
|
+
The flavor logo.
|
159
|
+
"""
|
160
|
+
return "https://public-flavor-logos.s3.eu-central-1.amazonaws.com/experiment_tracker/vertexai.png"
|
161
|
+
|
162
|
+
@property
|
163
|
+
def config_class(self) -> Type[VertexExperimentTrackerConfig]:
|
164
|
+
"""Returns `VertexExperimentTrackerConfig` config class.
|
165
|
+
|
166
|
+
Returns:
|
167
|
+
The config class.
|
168
|
+
"""
|
169
|
+
return VertexExperimentTrackerConfig
|
170
|
+
|
171
|
+
@property
|
172
|
+
def implementation_class(self) -> Type["VertexExperimentTracker"]:
|
173
|
+
"""Implementation class for this flavor.
|
174
|
+
|
175
|
+
Returns:
|
176
|
+
The implementation class.
|
177
|
+
"""
|
178
|
+
from zenml.integrations.gcp.experiment_trackers import (
|
179
|
+
VertexExperimentTracker,
|
180
|
+
)
|
181
|
+
|
182
|
+
return VertexExperimentTracker
|
183
|
+
|
184
|
+
@property
|
185
|
+
def service_connector_requirements(
|
186
|
+
self,
|
187
|
+
) -> Optional[ServiceConnectorRequirements]:
|
188
|
+
"""Service connector resource requirements for service connectors.
|
189
|
+
|
190
|
+
Specifies resource requirements that are used to filter the available
|
191
|
+
service connector types that are compatible with this flavor.
|
192
|
+
|
193
|
+
Returns:
|
194
|
+
Requirements for compatible service connectors, if a service
|
195
|
+
connector is required for this flavor.
|
196
|
+
"""
|
197
|
+
return ServiceConnectorRequirements(
|
198
|
+
resource_type=GCP_RESOURCE_TYPE,
|
199
|
+
)
|
@@ -47,16 +47,11 @@ class HuggingfaceIntegration(Integration):
|
|
47
47
|
A list of requirements.
|
48
48
|
"""
|
49
49
|
requirements = [
|
50
|
-
"datasets",
|
50
|
+
"datasets>=2.16.0",
|
51
51
|
"huggingface_hub>0.19.0",
|
52
52
|
"accelerate",
|
53
53
|
"bitsandbytes>=0.41.3",
|
54
54
|
"peft",
|
55
|
-
# temporary fix for CI issue similar to:
|
56
|
-
# - https://github.com/huggingface/datasets/issues/6737
|
57
|
-
# - https://github.com/huggingface/datasets/issues/6697
|
58
|
-
# TODO try relaxing it back going forward
|
59
|
-
"fsspec<=2023.12.0",
|
60
55
|
"transformers",
|
61
56
|
]
|
62
57
|
|
@@ -248,7 +248,7 @@ def wait_pod(
|
|
248
248
|
Returns:
|
249
249
|
The pod object which meets the exit condition.
|
250
250
|
"""
|
251
|
-
start_time = datetime.datetime.
|
251
|
+
start_time = datetime.datetime.now(datetime.timezone.utc)
|
252
252
|
|
253
253
|
# Link to exponential back-off algorithm used here:
|
254
254
|
# https://cloud.google.com/storage/docs/exponential-backoff
|
@@ -288,7 +288,7 @@ def wait_pod(
|
|
288
288
|
return resp
|
289
289
|
|
290
290
|
# Check if wait timed out.
|
291
|
-
elapse_time = datetime.datetime.
|
291
|
+
elapse_time = datetime.datetime.now(datetime.timezone.utc) - start_time
|
292
292
|
if elapse_time.seconds >= timeout_sec and timeout_sec != 0:
|
293
293
|
raise RuntimeError(
|
294
294
|
f"Waiting for pod `{namespace}:{pod_name}` timed out after "
|
@@ -97,7 +97,9 @@ class WhylogsDataValidator(BaseDataValidator, AuthenticationMixin):
|
|
97
97
|
"""
|
98
98
|
results = why.log(pandas=dataset)
|
99
99
|
profile = results.profile()
|
100
|
-
dataset_timestamp = dataset_timestamp or datetime.datetime.
|
100
|
+
dataset_timestamp = dataset_timestamp or datetime.datetime.now(
|
101
|
+
datetime.timezone.utc
|
102
|
+
)
|
101
103
|
profile.set_dataset_timestamp(dataset_timestamp=dataset_timestamp)
|
102
104
|
return profile.view()
|
103
105
|
|
zenml/models/v2/core/api_key.py
CHANGED
@@ -13,7 +13,7 @@
|
|
13
13
|
# permissions and limitations under the License.
|
14
14
|
"""Models representing API keys."""
|
15
15
|
|
16
|
-
from datetime import datetime, timedelta
|
16
|
+
from datetime import datetime, timedelta, timezone
|
17
17
|
from typing import TYPE_CHECKING, ClassVar, List, Optional, Type, Union
|
18
18
|
from uuid import UUID
|
19
19
|
|
@@ -319,7 +319,7 @@ class APIKeyInternalResponse(APIKeyResponse):
|
|
319
319
|
and self.retain_period_minutes > 0
|
320
320
|
):
|
321
321
|
# check if the previous key is still valid
|
322
|
-
if datetime.
|
322
|
+
if datetime.now(timezone.utc) - self.last_rotated < timedelta(
|
323
323
|
minutes=self.retain_period_minutes
|
324
324
|
):
|
325
325
|
key_hash = self.previous_key
|
zenml/models/v2/core/schedule.py
CHANGED
@@ -14,13 +14,14 @@
|
|
14
14
|
"""Models representing schedules."""
|
15
15
|
|
16
16
|
import datetime
|
17
|
-
from typing import Optional, Union
|
17
|
+
from typing import Dict, Optional, Union
|
18
18
|
from uuid import UUID
|
19
19
|
|
20
20
|
from pydantic import Field, model_validator
|
21
21
|
|
22
22
|
from zenml.constants import STR_FIELD_MAX_LENGTH
|
23
23
|
from zenml.logger import get_logger
|
24
|
+
from zenml.metadata.metadata_types import MetadataType
|
24
25
|
from zenml.models.v2.base.base import BaseUpdate
|
25
26
|
from zenml.models.v2.base.scoped import (
|
26
27
|
WorkspaceScopedFilter,
|
@@ -136,6 +137,11 @@ class ScheduleResponseMetadata(WorkspaceScopedResponseMetadata):
|
|
136
137
|
orchestrator_id: Optional[UUID]
|
137
138
|
pipeline_id: Optional[UUID]
|
138
139
|
|
140
|
+
run_metadata: Dict[str, MetadataType] = Field(
|
141
|
+
title="Metadata associated with this schedule.",
|
142
|
+
default={},
|
143
|
+
)
|
144
|
+
|
139
145
|
|
140
146
|
class ScheduleResponseResources(WorkspaceScopedResponseResources):
|
141
147
|
"""Class for all resource models associated with the schedule entity."""
|
@@ -272,6 +278,15 @@ class ScheduleResponse(
|
|
272
278
|
"""
|
273
279
|
return self.get_metadata().pipeline_id
|
274
280
|
|
281
|
+
@property
|
282
|
+
def run_metadata(self) -> Dict[str, MetadataType]:
|
283
|
+
"""The `run_metadata` property.
|
284
|
+
|
285
|
+
Returns:
|
286
|
+
the value of the property.
|
287
|
+
"""
|
288
|
+
return self.get_metadata().run_metadata
|
289
|
+
|
275
290
|
|
276
291
|
# ------------------ Filter Model ------------------
|
277
292
|
|
@@ -13,7 +13,7 @@
|
|
13
13
|
# permissions and limitations under the License.
|
14
14
|
"""Utilities to publish pipeline and step runs."""
|
15
15
|
|
16
|
-
from datetime import datetime
|
16
|
+
from datetime import datetime, timezone
|
17
17
|
from typing import TYPE_CHECKING, Dict, List
|
18
18
|
|
19
19
|
from zenml.client import Client
|
@@ -48,7 +48,7 @@ def publish_successful_step_run(
|
|
48
48
|
step_run_id=step_run_id,
|
49
49
|
step_run_update=StepRunUpdate(
|
50
50
|
status=ExecutionStatus.COMPLETED,
|
51
|
-
end_time=datetime.
|
51
|
+
end_time=datetime.now(timezone.utc),
|
52
52
|
outputs=output_artifact_ids,
|
53
53
|
),
|
54
54
|
)
|
@@ -67,7 +67,7 @@ def publish_failed_step_run(step_run_id: "UUID") -> "StepRunResponse":
|
|
67
67
|
step_run_id=step_run_id,
|
68
68
|
step_run_update=StepRunUpdate(
|
69
69
|
status=ExecutionStatus.FAILED,
|
70
|
-
end_time=datetime.
|
70
|
+
end_time=datetime.now(timezone.utc),
|
71
71
|
),
|
72
72
|
)
|
73
73
|
|
@@ -87,7 +87,7 @@ def publish_failed_pipeline_run(
|
|
87
87
|
run_id=pipeline_run_id,
|
88
88
|
run_update=PipelineRunUpdate(
|
89
89
|
status=ExecutionStatus.FAILED,
|
90
|
-
end_time=datetime.
|
90
|
+
end_time=datetime.now(timezone.utc),
|
91
91
|
),
|
92
92
|
)
|
93
93
|
|
@@ -16,7 +16,7 @@
|
|
16
16
|
import os
|
17
17
|
import time
|
18
18
|
from contextlib import nullcontext
|
19
|
-
from datetime import datetime
|
19
|
+
from datetime import datetime, timezone
|
20
20
|
from functools import partial
|
21
21
|
from typing import TYPE_CHECKING, Any, Callable, Dict, Tuple
|
22
22
|
|
@@ -201,7 +201,7 @@ class StepLauncher:
|
|
201
201
|
f"Failed preparing step `{self._step_name}`."
|
202
202
|
)
|
203
203
|
step_run_request.status = ExecutionStatus.FAILED
|
204
|
-
step_run_request.end_time = datetime.
|
204
|
+
step_run_request.end_time = datetime.now(timezone.utc)
|
205
205
|
raise
|
206
206
|
finally:
|
207
207
|
step_run = Client().zen_store.create_run_step(
|
@@ -305,7 +305,7 @@ class StepLauncher:
|
|
305
305
|
The created or existing pipeline run,
|
306
306
|
and a boolean indicating whether the run was created or reused.
|
307
307
|
"""
|
308
|
-
start_time = datetime.
|
308
|
+
start_time = datetime.now(timezone.utc)
|
309
309
|
run_name = string_utils.format_name_template(
|
310
310
|
name_template=self._deployment.run_name_template,
|
311
311
|
substitutions=self._deployment.pipeline_configuration._get_full_substitutions(
|
@@ -13,7 +13,7 @@
|
|
13
13
|
# permissions and limitations under the License.
|
14
14
|
"""Utilities for creating step runs."""
|
15
15
|
|
16
|
-
from datetime import datetime
|
16
|
+
from datetime import datetime, timezone
|
17
17
|
from typing import Dict, List, Optional, Set, Tuple
|
18
18
|
|
19
19
|
from zenml.client import Client
|
@@ -75,7 +75,7 @@ class StepRunRequestFactory:
|
|
75
75
|
pipeline_run_id=self.pipeline_run.id,
|
76
76
|
deployment=self.deployment.id,
|
77
77
|
status=ExecutionStatus.RUNNING,
|
78
|
-
start_time=datetime.
|
78
|
+
start_time=datetime.now(timezone.utc),
|
79
79
|
user=Client().active_user.id,
|
80
80
|
workspace=Client().active_workspace.id,
|
81
81
|
)
|
zenml/pipelines/run_utils.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
"""Utility functions for running pipelines."""
|
2
2
|
|
3
3
|
import time
|
4
|
-
from datetime import datetime
|
4
|
+
from datetime import datetime, timezone
|
5
5
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union
|
6
6
|
from uuid import UUID
|
7
7
|
|
@@ -65,7 +65,7 @@ def create_placeholder_run(
|
|
65
65
|
|
66
66
|
if deployment.schedule:
|
67
67
|
return None
|
68
|
-
start_time = datetime.
|
68
|
+
start_time = datetime.now(timezone.utc)
|
69
69
|
run_request = PipelineRunRequest(
|
70
70
|
name=string_utils.format_name_template(
|
71
71
|
name_template=deployment.run_name_template,
|
@@ -799,8 +799,8 @@ class ServiceConnector(BaseModel, metaclass=ServiceConnectorMeta):
|
|
799
799
|
name=name,
|
800
800
|
body=ServiceConnectorResponseBody(
|
801
801
|
user=user,
|
802
|
-
created=datetime.
|
803
|
-
updated=datetime.
|
802
|
+
created=datetime.now(timezone.utc),
|
803
|
+
updated=datetime.now(timezone.utc),
|
804
804
|
description=description,
|
805
805
|
connector_type=self.get_type(),
|
806
806
|
auth_method=self.auth_method,
|
zenml/stack/stack.py
CHANGED
@@ -16,7 +16,7 @@
|
|
16
16
|
import itertools
|
17
17
|
import json
|
18
18
|
import os
|
19
|
-
from datetime import datetime
|
19
|
+
from datetime import datetime, timezone
|
20
20
|
from typing import (
|
21
21
|
TYPE_CHECKING,
|
22
22
|
AbstractSet,
|
@@ -751,8 +751,8 @@ class Stack:
|
|
751
751
|
config=LocalImageBuilderConfig(),
|
752
752
|
user=Client().active_user.id,
|
753
753
|
workspace=Client().active_workspace.id,
|
754
|
-
created=datetime.
|
755
|
-
updated=datetime.
|
754
|
+
created=datetime.now(timezone.utc),
|
755
|
+
updated=datetime.now(timezone.utc),
|
756
756
|
)
|
757
757
|
|
758
758
|
self._image_builder = image_builder
|