zenml-nightly 0.72.0.dev20250120__py3-none-any.whl → 0.73.0.dev20250123__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/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/constants.py +5 -0
- zenml/event_hub/base_event_hub.py +2 -2
- zenml/integrations/airflow/orchestrators/airflow_orchestrator.py +4 -2
- zenml/integrations/airflow/orchestrators/dag_generator.py +16 -0
- 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/kubernetes/orchestrators/kube_utils.py +2 -2
- zenml/integrations/mlflow/experiment_trackers/mlflow_experiment_tracker.py +0 -1
- zenml/integrations/slack/__init__.py +1 -2
- zenml/integrations/slack/alerters/slack_alerter.py +119 -61
- zenml/integrations/slack/flavors/slack_alerter_flavor.py +18 -8
- zenml/integrations/whylogs/data_validators/whylogs_data_validator.py +3 -1
- zenml/materializers/built_in_materializer.py +17 -2
- zenml/models/v2/core/api_key.py +2 -2
- zenml/orchestrators/publish_utils.py +4 -4
- zenml/orchestrators/step_launcher.py +3 -3
- zenml/orchestrators/step_run_utils.py +2 -2
- zenml/pipelines/pipeline_definition.py +1 -1
- zenml/pipelines/run_utils.py +2 -2
- zenml/service_connectors/service_connector.py +2 -2
- zenml/stack/stack.py +3 -3
- zenml/stack_deployments/stack_deployment.py +5 -0
- zenml/utils/git_utils.py +1 -1
- zenml/utils/string_utils.py +2 -2
- zenml/utils/yaml_utils.py +3 -4
- 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_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 +3 -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 +2 -2
- 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.dev20250120.dist-info → zenml_nightly-0.73.0.dev20250123.dist-info}/METADATA +2 -2
- {zenml_nightly-0.72.0.dev20250120.dist-info → zenml_nightly-0.73.0.dev20250123.dist-info}/RECORD +159 -155
- 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.dev20250120.dist-info → zenml_nightly-0.73.0.dev20250123.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.72.0.dev20250120.dist-info → zenml_nightly-0.73.0.dev20250123.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.72.0.dev20250120.dist-info → zenml_nightly-0.73.0.dev20250123.dist-info}/entry_points.txt +0 -0
zenml/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.73.0.dev20250123
|
zenml/cli/service_connectors.py
CHANGED
@@ -13,7 +13,7 @@
|
|
13
13
|
# permissions and limitations under the License.
|
14
14
|
"""Service connector CLI commands."""
|
15
15
|
|
16
|
-
from datetime import datetime
|
16
|
+
from datetime import datetime, timezone
|
17
17
|
from typing import Any, Dict, List, Optional, Union, cast
|
18
18
|
from uuid import UUID
|
19
19
|
|
@@ -291,7 +291,9 @@ def prompt_expires_at(
|
|
291
291
|
while True:
|
292
292
|
default_str = ""
|
293
293
|
if default is not None:
|
294
|
-
seconds = int(
|
294
|
+
seconds = int(
|
295
|
+
(default - datetime.now(timezone.utc)).total_seconds()
|
296
|
+
)
|
295
297
|
default_str = (
|
296
298
|
f" [{str(default)} i.e. in "
|
297
299
|
f"{seconds_to_human_readable(seconds)}]"
|
@@ -307,14 +309,16 @@ def prompt_expires_at(
|
|
307
309
|
|
308
310
|
assert expires_at is not None
|
309
311
|
assert isinstance(expires_at, datetime)
|
310
|
-
if expires_at < datetime.
|
312
|
+
if expires_at < datetime.now(timezone.utc):
|
311
313
|
cli_utils.warning(
|
312
314
|
"The expiration time must be in the future. Please enter a "
|
313
315
|
"later date and time."
|
314
316
|
)
|
315
317
|
continue
|
316
318
|
|
317
|
-
seconds = int(
|
319
|
+
seconds = int(
|
320
|
+
(expires_at - datetime.now(timezone.utc)).total_seconds()
|
321
|
+
)
|
318
322
|
|
319
323
|
confirm = click.confirm(
|
320
324
|
f"Credentials will be valid until {str(expires_at)} UTC (i.e. "
|
zenml/cli/stack.py
CHANGED
@@ -17,7 +17,7 @@ import getpass
|
|
17
17
|
import re
|
18
18
|
import time
|
19
19
|
import webbrowser
|
20
|
-
from datetime import datetime
|
20
|
+
from datetime import datetime, timezone
|
21
21
|
from typing import (
|
22
22
|
TYPE_CHECKING,
|
23
23
|
Any,
|
@@ -1575,7 +1575,7 @@ def deploy(
|
|
1575
1575
|
):
|
1576
1576
|
raise click.Abort()
|
1577
1577
|
|
1578
|
-
date_start = datetime.
|
1578
|
+
date_start = datetime.now(timezone.utc)
|
1579
1579
|
|
1580
1580
|
webbrowser.open(deployment_config.deployment_url)
|
1581
1581
|
console.print(
|
@@ -13,7 +13,7 @@
|
|
13
13
|
# permissions and limitations under the License.
|
14
14
|
"""Pipeline configuration classes."""
|
15
15
|
|
16
|
-
from datetime import datetime
|
16
|
+
from datetime import datetime, timezone
|
17
17
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
18
18
|
|
19
19
|
from pydantic import SerializeAsAny, field_validator
|
@@ -61,7 +61,7 @@ class PipelineConfigurationUpdate(StrictBaseModel):
|
|
61
61
|
The full substitutions dict including date and time.
|
62
62
|
"""
|
63
63
|
if start_time is None:
|
64
|
-
start_time = datetime.
|
64
|
+
start_time = datetime.now(timezone.utc)
|
65
65
|
ret = self.substitutions.copy()
|
66
66
|
ret.setdefault("date", start_time.strftime("%Y_%m_%d"))
|
67
67
|
ret.setdefault("time", start_time.strftime("%H_%M_%S_%f"))
|
zenml/config/server_config.py
CHANGED
@@ -432,6 +432,26 @@ class ServerConfiguration(BaseModel):
|
|
432
432
|
|
433
433
|
return data
|
434
434
|
|
435
|
+
@field_validator("reportable_resources", mode="before")
|
436
|
+
@classmethod
|
437
|
+
def _convert_reportable_resources(cls, value: Any) -> Any:
|
438
|
+
"""Convert reportable resources value if necessary.
|
439
|
+
|
440
|
+
This was previously set via an environment variable as a JSON-formatted
|
441
|
+
string. In case this still exists somewhere, this method converts the
|
442
|
+
JSON string to a list.
|
443
|
+
|
444
|
+
Args:
|
445
|
+
value: The reportable resources values.
|
446
|
+
|
447
|
+
Returns:
|
448
|
+
The potentially converted value.
|
449
|
+
"""
|
450
|
+
if isinstance(value, str):
|
451
|
+
value = json.loads(value)
|
452
|
+
|
453
|
+
return value
|
454
|
+
|
435
455
|
@property
|
436
456
|
def deployment_id(self) -> UUID:
|
437
457
|
"""Get the ZenML server deployment ID.
|
zenml/constants.py
CHANGED
@@ -174,6 +174,11 @@ ENV_ZENML_PIPELINE_RUN_API_TOKEN_EXPIRATION = (
|
|
174
174
|
"ZENML_PIPELINE_API_TOKEN_EXPIRATION"
|
175
175
|
)
|
176
176
|
|
177
|
+
# Materializer environment variables
|
178
|
+
ENV_ZENML_MATERIALIZER_ALLOW_NON_ASCII_JSON_DUMPS = (
|
179
|
+
"ZENML_MATERIALIZER_ALLOW_NON_ASCII_JSON_DUMPS"
|
180
|
+
)
|
181
|
+
|
177
182
|
# ZenML Server environment variables
|
178
183
|
ENV_ZENML_SERVER_PREFIX = "ZENML_SERVER_"
|
179
184
|
ENV_ZENML_SERVER_PRO_PREFIX = "ZENML_SERVER_PRO_"
|
@@ -14,7 +14,7 @@
|
|
14
14
|
"""Base class for event hub implementations."""
|
15
15
|
|
16
16
|
from abc import ABC, abstractmethod
|
17
|
-
from datetime import datetime, timedelta
|
17
|
+
from datetime import datetime, timedelta, timezone
|
18
18
|
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple
|
19
19
|
|
20
20
|
from zenml import EventSourceResponse
|
@@ -134,7 +134,7 @@ class BaseEventHub(ABC):
|
|
134
134
|
)
|
135
135
|
expires: Optional[datetime] = None
|
136
136
|
if trigger.action.auth_window:
|
137
|
-
expires = datetime.
|
137
|
+
expires = datetime.now(timezone.utc) + timedelta(
|
138
138
|
minutes=trigger.action.auth_window
|
139
139
|
)
|
140
140
|
encoded_token = token.encode(expires=expires)
|
@@ -408,7 +408,8 @@ class AirflowOrchestrator(ContainerizedOrchestrator):
|
|
408
408
|
if schedule:
|
409
409
|
if schedule.cron_expression:
|
410
410
|
start_time = schedule.start_time or (
|
411
|
-
datetime.datetime.
|
411
|
+
datetime.datetime.now(datetime.timezone.utc)
|
412
|
+
- datetime.timedelta(7)
|
412
413
|
)
|
413
414
|
return {
|
414
415
|
"schedule": schedule.cron_expression,
|
@@ -428,6 +429,7 @@ class AirflowOrchestrator(ContainerizedOrchestrator):
|
|
428
429
|
"schedule": "@once",
|
429
430
|
# set a start time in the past and disable catchup so airflow
|
430
431
|
# runs the dag immediately
|
431
|
-
"start_date": datetime.datetime.
|
432
|
+
"start_date": datetime.datetime.now(datetime.timezone.utc)
|
433
|
+
- datetime.timedelta(7),
|
432
434
|
"catchup": False,
|
433
435
|
}
|
@@ -119,6 +119,22 @@ def get_operator_init_kwargs(
|
|
119
119
|
except ImportError:
|
120
120
|
pass
|
121
121
|
|
122
|
+
try:
|
123
|
+
# Support for apache-airflow-providers-cncf-kubernetes>=10.0.0 where
|
124
|
+
# the import changed
|
125
|
+
from airflow.providers.cncf.kubernetes.operators.pod import (
|
126
|
+
KubernetesPodOperator,
|
127
|
+
)
|
128
|
+
|
129
|
+
if issubclass(operator_class, KubernetesPodOperator):
|
130
|
+
init_kwargs.update(
|
131
|
+
get_kubernetes_pod_operator_init_kwargs(
|
132
|
+
dag_config=dag_config, task_config=task_config
|
133
|
+
)
|
134
|
+
)
|
135
|
+
except ImportError:
|
136
|
+
pass
|
137
|
+
|
122
138
|
init_kwargs.update(task_config.operator_args)
|
123
139
|
return init_kwargs
|
124
140
|
|
@@ -30,6 +30,7 @@ from zenml.stack import Flavor
|
|
30
30
|
|
31
31
|
GCP_ARTIFACT_STORE_FLAVOR = "gcp"
|
32
32
|
GCP_IMAGE_BUILDER_FLAVOR = "gcp"
|
33
|
+
GCP_VERTEX_EXPERIMENT_TRACKER_FLAVOR = "vertex"
|
33
34
|
GCP_VERTEX_ORCHESTRATOR_FLAVOR = "vertex"
|
34
35
|
GCP_VERTEX_STEP_OPERATOR_FLAVOR = "vertex"
|
35
36
|
|
@@ -70,6 +71,7 @@ class GcpIntegration(Integration):
|
|
70
71
|
from zenml.integrations.gcp.flavors import (
|
71
72
|
GCPArtifactStoreFlavor,
|
72
73
|
GCPImageBuilderFlavor,
|
74
|
+
VertexExperimentTrackerFlavor,
|
73
75
|
VertexOrchestratorFlavor,
|
74
76
|
VertexStepOperatorFlavor,
|
75
77
|
)
|
@@ -77,6 +79,7 @@ class GcpIntegration(Integration):
|
|
77
79
|
return [
|
78
80
|
GCPArtifactStoreFlavor,
|
79
81
|
GCPImageBuilderFlavor,
|
82
|
+
VertexExperimentTrackerFlavor,
|
80
83
|
VertexOrchestratorFlavor,
|
81
84
|
VertexStepOperatorFlavor,
|
82
85
|
]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
# you may not use this file except in compliance with the License.
|
3
|
+
# You may obtain a copy of the License at:
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
10
|
+
# or implied. See the License for the specific language governing
|
11
|
+
# permissions and limitations under the License.
|
12
|
+
"""Initialization for the VertexAI experiment tracker."""
|
13
|
+
|
14
|
+
from zenml.integrations.gcp.experiment_trackers.vertex_experiment_tracker import ( # noqa
|
15
|
+
VertexExperimentTracker,
|
16
|
+
)
|
17
|
+
|
18
|
+
__all__ = ["VertexExperimentTracker"]
|
@@ -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
|
+
)
|
@@ -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 "
|