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.
Files changed (165) hide show
  1. zenml/VERSION +1 -1
  2. zenml/cli/service_connectors.py +8 -4
  3. zenml/cli/stack.py +2 -2
  4. zenml/config/pipeline_configurations.py +2 -2
  5. zenml/config/server_config.py +20 -0
  6. zenml/constants.py +5 -0
  7. zenml/event_hub/base_event_hub.py +2 -2
  8. zenml/integrations/airflow/orchestrators/airflow_orchestrator.py +4 -2
  9. zenml/integrations/airflow/orchestrators/dag_generator.py +16 -0
  10. zenml/integrations/gcp/__init__.py +3 -0
  11. zenml/integrations/gcp/experiment_trackers/__init__.py +18 -0
  12. zenml/integrations/gcp/experiment_trackers/vertex_experiment_tracker.py +214 -0
  13. zenml/integrations/gcp/flavors/__init__.py +6 -0
  14. zenml/integrations/gcp/flavors/vertex_experiment_tracker_flavor.py +199 -0
  15. zenml/integrations/kubernetes/orchestrators/kube_utils.py +2 -2
  16. zenml/integrations/mlflow/experiment_trackers/mlflow_experiment_tracker.py +0 -1
  17. zenml/integrations/slack/__init__.py +1 -2
  18. zenml/integrations/slack/alerters/slack_alerter.py +119 -61
  19. zenml/integrations/slack/flavors/slack_alerter_flavor.py +18 -8
  20. zenml/integrations/whylogs/data_validators/whylogs_data_validator.py +3 -1
  21. zenml/materializers/built_in_materializer.py +17 -2
  22. zenml/models/v2/core/api_key.py +2 -2
  23. zenml/orchestrators/publish_utils.py +4 -4
  24. zenml/orchestrators/step_launcher.py +3 -3
  25. zenml/orchestrators/step_run_utils.py +2 -2
  26. zenml/pipelines/pipeline_definition.py +1 -1
  27. zenml/pipelines/run_utils.py +2 -2
  28. zenml/service_connectors/service_connector.py +2 -2
  29. zenml/stack/stack.py +3 -3
  30. zenml/stack_deployments/stack_deployment.py +5 -0
  31. zenml/utils/git_utils.py +1 -1
  32. zenml/utils/string_utils.py +2 -2
  33. zenml/utils/yaml_utils.py +3 -4
  34. zenml/zen_server/auth.py +13 -6
  35. zenml/zen_server/dashboard/assets/{404-Dfq64Boz.js → 404-c8OuXDAT.js} +1 -1
  36. zenml/zen_server/dashboard/assets/{@reactflow-BUNIMFeC.js → @reactflow-6JPoencd.js} +1 -1
  37. zenml/zen_server/dashboard/assets/{AlertDialogDropdownItem-B73Vs10T.js → AlertDialogDropdownItem-8yPFDxEI.js} +1 -1
  38. zenml/zen_server/dashboard/assets/{CodeSnippet-DIJRT2NT.js → CodeSnippet-Qh1ae_DJ.js} +1 -1
  39. zenml/zen_server/dashboard/assets/{CollapsibleCard-BzUHGZOU.js → CollapsibleCard-TiI4lId1.js} +1 -1
  40. zenml/zen_server/dashboard/assets/{Commands-BEGyld4c.js → Commands-BcR2Arie.js} +1 -1
  41. zenml/zen_server/dashboard/assets/{ComponentBadge-xyKiek1s.js → ComponentBadge-BqQNUZgb.js} +1 -1
  42. zenml/zen_server/dashboard/assets/{CopyButton-DhW-mapu.js → CopyButton-DCiXO3JC.js} +1 -1
  43. zenml/zen_server/dashboard/assets/{CsvVizualization-D8oazBiE.js → CsvVizualization-O9cVIaL8.js} +1 -1
  44. zenml/zen_server/dashboard/assets/{DeleteAlertDialog-WkSIIgfy.js → DeleteAlertDialog-DrPjHtXX.js} +1 -1
  45. zenml/zen_server/dashboard/assets/{DialogItem-Bgroeg29.js → DialogItem-BYG7d_M2.js} +1 -1
  46. zenml/zen_server/dashboard/assets/{Error-CY5tlu17.js → Error-C1zbWr19.js} +1 -1
  47. zenml/zen_server/dashboard/assets/{ExecutionStatus-G8mjIaeA.js → ExecutionStatus-Ct9srgHC.js} +1 -1
  48. zenml/zen_server/dashboard/assets/{Helpbox-Bb1ed--O.js → Helpbox-Bm_1Zx9f.js} +1 -1
  49. zenml/zen_server/dashboard/assets/{Infobox-Da6-76M2.js → Infobox-OQdkCLSP.js} +1 -1
  50. zenml/zen_server/dashboard/assets/{InlineAvatar-DqnZaBNq.js → InlineAvatar-CQNjKoEQ.js} +1 -1
  51. zenml/zen_server/dashboard/assets/{NestedCollapsible-aK5ojKoF.js → NestedCollapsible-DDgd2SGb.js} +1 -1
  52. zenml/zen_server/dashboard/assets/Partials-MD3e95Dk.js +1 -0
  53. zenml/zen_server/dashboard/assets/{ProBadge-B4tRUYve.js → ProBadge-D784iVNC.js} +1 -1
  54. zenml/zen_server/dashboard/assets/{ProCta-CZuP29Qz.js → ProCta-W2PEvNow.js} +1 -1
  55. zenml/zen_server/dashboard/assets/{ProviderIcon-Bd7GUQ1_.js → ProviderIcon-DfDUOeAy.js} +1 -1
  56. zenml/zen_server/dashboard/assets/{ProviderRadio-mstdqzsS.js → ProviderRadio-B81Elxrc.js} +1 -1
  57. zenml/zen_server/dashboard/assets/{RunSelector-CsruSB4i.js → RunSelector-DOXgdry5.js} +1 -1
  58. zenml/zen_server/dashboard/assets/{RunsBody-DxxtWVYz.js → RunsBody-Bnx2fxub.js} +1 -1
  59. zenml/zen_server/dashboard/assets/SearchField-Yjv-KRW4.js +1 -0
  60. zenml/zen_server/dashboard/assets/{SecretTooltip-CLzJIYW_.js → SecretTooltip-EKpMlG2f.js} +1 -1
  61. zenml/zen_server/dashboard/assets/{SetPassword-Yn50ooBC.js → SetPassword-CDLy57PZ.js} +1 -1
  62. zenml/zen_server/dashboard/assets/StackList-DKQaLDo4.js +1 -0
  63. zenml/zen_server/dashboard/assets/{Tabs-CNv-eTYM.js → Tabs-B5E-o_h6.js} +1 -1
  64. zenml/zen_server/dashboard/assets/{Tick-jEIevzVf.js → Tick-DSYBiuXU.js} +1 -1
  65. zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-C16GW-kX.js → UpdatePasswordSchemas-HBNOeyoP.js} +1 -1
  66. zenml/zen_server/dashboard/assets/{UsageReason-Bf2tzhv1.js → UsageReason-DXtPS5nE.js} +1 -1
  67. zenml/zen_server/dashboard/assets/{WizardFooter-D6i-AP1K.js → WizardFooter-_1VSMZ_c.js} +1 -1
  68. zenml/zen_server/dashboard/assets/{all-pipeline-runs-query-DUti43aF.js → all-pipeline-runs-query-D0qDLdKB.js} +1 -1
  69. zenml/zen_server/dashboard/assets/{create-stack-Ch2WPs9U.js → create-stack-7JzgAYAm.js} +1 -1
  70. zenml/zen_server/dashboard/assets/{delete-run-Byf9hTjA.js → delete-run-CUdtYFLl.js} +1 -1
  71. zenml/zen_server/dashboard/assets/{form-schemas-BZqKBPBF.js → form-schemas-B6PCV3Y4.js} +1 -1
  72. zenml/zen_server/dashboard/assets/index-B6U0OkEN.css +1 -0
  73. zenml/zen_server/dashboard/assets/{index-CyBKZcpO.js → index-CJ5IfeAl.js} +1 -1
  74. zenml/zen_server/dashboard/assets/{index-CtdYkjUi.js → index-Ceyzb1yI.js} +1 -1
  75. zenml/zen_server/dashboard/assets/{index-CE0aQlv8.js → index-CxO6541P.js} +3 -3
  76. zenml/zen_server/dashboard/assets/{index-v6gQjDEo.js → index-D4yoZ_gH.js} +1 -1
  77. zenml/zen_server/dashboard/assets/{login-mutation-DNDVp_2H.js → login-mutation-BaeJ7MAg.js} +1 -1
  78. zenml/zen_server/dashboard/assets/{not-found-Bmup4ctE.js → not-found-MGptrNBk.js} +1 -1
  79. zenml/zen_server/dashboard/assets/{page-DGlm1RVc.js → page-Aeu3v0MQ.js} +1 -1
  80. zenml/zen_server/dashboard/assets/{page-CltCNL0T.js → page-BCgEdmhP.js} +1 -1
  81. zenml/zen_server/dashboard/assets/{page-Hn8q9iJZ.js → page-BKwwfTNy.js} +1 -1
  82. zenml/zen_server/dashboard/assets/{page-BNxYrN0q.js → page-BUjw8Tp1.js} +1 -1
  83. zenml/zen_server/dashboard/assets/{page-BYJfqgLN.js → page-BXgXP-Qj.js} +1 -1
  84. zenml/zen_server/dashboard/assets/{page-DN4BVIOL.js → page-BXrtxEbw.js} +1 -1
  85. zenml/zen_server/dashboard/assets/{page-CHRn1fQm.js → page-BaUDR9Ri.js} +1 -1
  86. zenml/zen_server/dashboard/assets/{page-DlIi5ThM.js → page-BbljjC-k.js} +1 -1
  87. zenml/zen_server/dashboard/assets/{page-BrmJp1Wt.js → page-BhOXn-s9.js} +1 -1
  88. zenml/zen_server/dashboard/assets/{page-Cc8ZEuj4.js → page-C37IDa-Q.js} +1 -1
  89. zenml/zen_server/dashboard/assets/{page-Dif8CWyZ.js → page-C4JpDeUM.js} +1 -1
  90. zenml/zen_server/dashboard/assets/{page-BC27C_OI.js → page-CB2_GdBA.js} +1 -1
  91. zenml/zen_server/dashboard/assets/{page-B5Sr8pib.js → page-CBiT2Ox9.js} +1 -1
  92. zenml/zen_server/dashboard/assets/{page-IhckKFnD.js → page-CXPc-HN1.js} +1 -1
  93. zenml/zen_server/dashboard/assets/{page-Dth9X1Ih.js → page-CbwI6emp.js} +1 -1
  94. zenml/zen_server/dashboard/assets/{page-DweqqCkF.js → page-CeNL9JWi.js} +1 -1
  95. zenml/zen_server/dashboard/assets/{page-LyZ_l8vR.js → page-CkPwPmLZ.js} +1 -1
  96. zenml/zen_server/dashboard/assets/{page-C70wZtV2.js → page-CmJU3Gqo.js} +1 -1
  97. zenml/zen_server/dashboard/assets/{page-D9Oh05fl.js → page-CoFVtzhG.js} +1 -1
  98. zenml/zen_server/dashboard/assets/{page-PamGpk0j.js → page-D-KPzeQb.js} +1 -1
  99. zenml/zen_server/dashboard/assets/{page-DoW7YxTu.js → page-DKQ3wZgr.js} +1 -1
  100. zenml/zen_server/dashboard/assets/page-DWWhxCoF.js +1 -0
  101. zenml/zen_server/dashboard/assets/{page-CmlYj7Nl.js → page-DbW8MfQ4.js} +1 -1
  102. zenml/zen_server/dashboard/assets/{page-CWr96ZKN.js → page-Dv5lN2w7.js} +1 -1
  103. zenml/zen_server/dashboard/assets/{page-ANYGfEUL.js → page-Dvbq1BoF.js} +1 -1
  104. zenml/zen_server/dashboard/assets/{page-D6Ev5P8V.js → page-DyAuja95.js} +1 -1
  105. zenml/zen_server/dashboard/assets/{page-DyOJ_pq3.js → page-DzrdL2v1.js} +1 -1
  106. zenml/zen_server/dashboard/assets/{page-CXAbSyp9.js → page-I2B4Ocv8.js} +1 -1
  107. zenml/zen_server/dashboard/assets/page-OdjGauvw.js +2 -0
  108. zenml/zen_server/dashboard/assets/{page-CaeI9ptC.js → page-Ox-eC1ik.js} +1 -1
  109. zenml/zen_server/dashboard/assets/{page-B_0XkV48.js → page-khp8QJ6b.js} +1 -1
  110. zenml/zen_server/dashboard/assets/{page--XLMzHrn.js → page-yNh6PQKt.js} +1 -1
  111. zenml/zen_server/dashboard/assets/{persist-vP0-Xl4f.js → persist-DBTFy--v.js} +1 -1
  112. zenml/zen_server/dashboard/assets/{persist-DeXRG61d.js → persist-K7AY0ju4.js} +1 -1
  113. zenml/zen_server/dashboard/assets/{service-DH_oUqQj.js → service-BvOYLH5b.js} +1 -1
  114. zenml/zen_server/dashboard/assets/{sharedSchema-Bw1_Wa7l.js → sharedSchema-xJDsJNgJ.js} +1 -1
  115. zenml/zen_server/dashboard/assets/{stack-detail-query-B_0R_fd6.js → stack-detail-query-DMJoxwgv.js} +1 -1
  116. zenml/zen_server/dashboard/assets/{update-server-settings-mutation-D9qYhfaN.js → update-server-settings-mutation-ATZDNNZk.js} +1 -1
  117. zenml/zen_server/dashboard/assets/{url-Dh93fvh0.js → url-BWJXzuI4.js} +1 -1
  118. zenml/zen_server/dashboard/index.html +4 -4
  119. zenml/zen_server/deploy/helm/Chart.yaml +1 -1
  120. zenml/zen_server/deploy/helm/README.md +2 -2
  121. zenml/zen_server/deploy/helm/templates/server-db-job.yaml +5 -3
  122. zenml/zen_server/deploy/helm/values.yaml +4 -0
  123. zenml/zen_server/routers/devices_endpoints.py +4 -2
  124. zenml/zen_stores/migrations/versions/0.73.0_release.py +23 -0
  125. zenml/zen_stores/migrations/versions/25155145c545_separate_actions_and_triggers.py +2 -2
  126. zenml/zen_stores/migrations/versions/46506f72f0ed_add_server_settings.py +2 -2
  127. zenml/zen_stores/migrations/versions/5994f9ad0489_introduce_role_permissions.py +6 -6
  128. zenml/zen_stores/migrations/versions/7500f434b71c_remove_shared_columns.py +2 -2
  129. zenml/zen_stores/migrations/versions/a91762e6be36_artifact_version_table.py +3 -3
  130. zenml/zen_stores/schemas/action_schemas.py +2 -2
  131. zenml/zen_stores/schemas/api_key_schemas.py +4 -4
  132. zenml/zen_stores/schemas/artifact_schemas.py +3 -3
  133. zenml/zen_stores/schemas/base_schemas.py +7 -3
  134. zenml/zen_stores/schemas/code_repository_schemas.py +2 -2
  135. zenml/zen_stores/schemas/component_schemas.py +2 -2
  136. zenml/zen_stores/schemas/device_schemas.py +4 -4
  137. zenml/zen_stores/schemas/event_source_schemas.py +2 -2
  138. zenml/zen_stores/schemas/flavor_schemas.py +2 -2
  139. zenml/zen_stores/schemas/model_schemas.py +3 -3
  140. zenml/zen_stores/schemas/pipeline_run_schemas.py +3 -3
  141. zenml/zen_stores/schemas/pipeline_schemas.py +2 -2
  142. zenml/zen_stores/schemas/run_template_schemas.py +2 -2
  143. zenml/zen_stores/schemas/schedule_schema.py +2 -2
  144. zenml/zen_stores/schemas/secret_schemas.py +2 -2
  145. zenml/zen_stores/schemas/server_settings_schemas.py +9 -5
  146. zenml/zen_stores/schemas/service_connector_schemas.py +2 -2
  147. zenml/zen_stores/schemas/service_schemas.py +2 -2
  148. zenml/zen_stores/schemas/stack_schemas.py +2 -2
  149. zenml/zen_stores/schemas/step_run_schemas.py +2 -2
  150. zenml/zen_stores/schemas/tag_schemas.py +2 -2
  151. zenml/zen_stores/schemas/trigger_schemas.py +2 -2
  152. zenml/zen_stores/schemas/user_schemas.py +3 -3
  153. zenml/zen_stores/schemas/workspace_schemas.py +2 -2
  154. zenml/zen_stores/sql_zen_store.py +10 -1
  155. {zenml_nightly-0.72.0.dev20250120.dist-info → zenml_nightly-0.73.0.dev20250123.dist-info}/METADATA +2 -2
  156. {zenml_nightly-0.72.0.dev20250120.dist-info → zenml_nightly-0.73.0.dev20250123.dist-info}/RECORD +159 -155
  157. zenml/zen_server/dashboard/assets/Partials-CqZp5NMX.js +0 -1
  158. zenml/zen_server/dashboard/assets/SearchField-D6tPxyqw.js +0 -1
  159. zenml/zen_server/dashboard/assets/StackList-U537qoYd.js +0 -1
  160. zenml/zen_server/dashboard/assets/index-DXvT1_Um.css +0 -1
  161. zenml/zen_server/dashboard/assets/page-C2nU3Gxn.js +0 -1
  162. zenml/zen_server/dashboard/assets/page-PxOWfKgF.js +0 -2
  163. {zenml_nightly-0.72.0.dev20250120.dist-info → zenml_nightly-0.73.0.dev20250123.dist-info}/LICENSE +0 -0
  164. {zenml_nightly-0.72.0.dev20250120.dist-info → zenml_nightly-0.73.0.dev20250123.dist-info}/WHEEL +0 -0
  165. {zenml_nightly-0.72.0.dev20250120.dist-info → zenml_nightly-0.73.0.dev20250123.dist-info}/entry_points.txt +0 -0
@@ -30,8 +30,7 @@ class SlackIntegration(Integration):
30
30
  """
31
31
 
32
32
  NAME = SLACK
33
- REQUIREMENTS = ["slack-sdk>=3.16.1", "aiohttp>=3.8.1"]
34
- REQUIREMENTS_IGNORED_ON_UNINSTALL = ["aiohttp"]
33
+ REQUIREMENTS = ["slack-sdk==3.30.0"]
35
34
 
36
35
  @classmethod
37
36
  def flavors(cls) -> List[Type[Flavor]]:
@@ -14,12 +14,12 @@
14
14
  # or implied. See the License for the specific language governing
15
15
  # permissions and limitations under the License.
16
16
 
17
- from typing import Any, Dict, List, Optional, Type, cast
17
+ import time
18
+ from typing import Dict, List, Optional, Type, cast
18
19
 
19
20
  from pydantic import BaseModel
20
21
  from slack_sdk import WebClient
21
22
  from slack_sdk.errors import SlackApiError
22
- from slack_sdk.rtm import RTMClient
23
23
 
24
24
  from zenml import get_step_context
25
25
  from zenml.alerter.base_alerter import BaseAlerter, BaseAlerterStepParameters
@@ -58,7 +58,7 @@ class SlackAlerterParameters(BaseAlerterStepParameters):
58
58
  payload: Optional[SlackAlerterPayload] = None
59
59
  include_format_blocks: Optional[bool] = True
60
60
 
61
- # Allowing user to use their own custom blocks in the slack post message
61
+ # Allowing user to use their own custom blocks in the Slack post message
62
62
  blocks: Optional[List[Dict]] = None # type: ignore
63
63
 
64
64
 
@@ -96,7 +96,7 @@ class SlackAlerter(BaseAlerter):
96
96
 
97
97
  Raises:
98
98
  RuntimeError: if config is not of type `BaseAlerterStepConfig`.
99
- ValueError: if a slack channel was neither defined in the config
99
+ ValueError: if a Slack channel was neither defined in the config
100
100
  nor in the slack alerter component.
101
101
  """
102
102
  if params and not isinstance(params, BaseAlerterStepParameters):
@@ -111,24 +111,48 @@ class SlackAlerter(BaseAlerter):
111
111
  ):
112
112
  return params.slack_channel_id
113
113
 
114
- settings = cast(
115
- SlackAlerterSettings,
116
- self.get_settings(get_step_context().step_run),
117
- )
118
- if settings.slack_channel_id is not None:
114
+ try:
115
+ settings = cast(
116
+ SlackAlerterSettings,
117
+ self.get_settings(get_step_context().step_run),
118
+ )
119
+ except RuntimeError:
120
+ settings = None
121
+
122
+ if settings is not None and settings.slack_channel_id is not None:
119
123
  return settings.slack_channel_id
120
124
 
121
- if self.config.default_slack_channel_id is not None:
122
- return self.config.default_slack_channel_id
125
+ if self.config.slack_channel_id is not None:
126
+ return self.config.slack_channel_id
123
127
 
124
128
  raise ValueError(
125
- "Neither the `slack_channel_id` in the runtime "
126
- "configuration, nor the `default_slack_channel_id` in the alerter "
127
- "stack component is specified. Please specify at least one."
129
+ "The `slack_channel_id` is not set either in the runtime settings, "
130
+ "or the component configuration of the alerter. Please specify at "
131
+ "least one."
128
132
  )
129
133
 
134
+ def _get_timeout_duration(self) -> int:
135
+ """Gets the timeout duration used by the ask method .
136
+
137
+ Returns:
138
+ number of seconds for the timeout to happen.
139
+ """
140
+ try:
141
+ settings = cast(
142
+ SlackAlerterSettings,
143
+ self.get_settings(get_step_context().step_run),
144
+ )
145
+ except RuntimeError:
146
+ settings = None
147
+
148
+ if settings is not None:
149
+ return settings.timeout
150
+
151
+ return self.config.timeout
152
+
153
+ @staticmethod
130
154
  def _get_approve_msg_options(
131
- self, params: Optional[BaseAlerterStepParameters]
155
+ params: Optional[BaseAlerterStepParameters],
132
156
  ) -> List[str]:
133
157
  """Define which messages will lead to approval during ask().
134
158
 
@@ -146,8 +170,9 @@ class SlackAlerter(BaseAlerter):
146
170
  return params.approve_msg_options
147
171
  return DEFAULT_APPROVE_MSG_OPTIONS
148
172
 
173
+ @staticmethod
149
174
  def _get_disapprove_msg_options(
150
- self, params: Optional[BaseAlerterStepParameters]
175
+ params: Optional[BaseAlerterStepParameters],
151
176
  ) -> List[str]:
152
177
  """Define which messages will lead to disapproval during ask().
153
178
 
@@ -165,8 +190,8 @@ class SlackAlerter(BaseAlerter):
165
190
  return params.disapprove_msg_options
166
191
  return DEFAULT_DISAPPROVE_MSG_OPTIONS
167
192
 
193
+ @staticmethod
168
194
  def _create_blocks(
169
- self,
170
195
  message: Optional[str],
171
196
  params: Optional[BaseAlerterStepParameters],
172
197
  ) -> List[Dict]: # type: ignore
@@ -185,7 +210,8 @@ class SlackAlerter(BaseAlerter):
185
210
  return params.blocks
186
211
  elif hasattr(params, "payload") and params.payload is not None:
187
212
  logger.info(
188
- "No custom blocks set. Using default blocks for Slack alerter"
213
+ "No custom blocks set. Using default blocks for Slack "
214
+ "alerter."
189
215
  )
190
216
  payload = params.payload
191
217
  return [
@@ -228,7 +254,8 @@ class SlackAlerter(BaseAlerter):
228
254
  return []
229
255
  else:
230
256
  logger.info(
231
- "params is not of type SlackAlerterParameters. Returning empty blocks."
257
+ "params is not of type SlackAlerterParameters. Returning empty "
258
+ "blocks."
232
259
  )
233
260
  return []
234
261
 
@@ -253,64 +280,95 @@ class SlackAlerter(BaseAlerter):
253
280
  response = client.chat_postMessage(
254
281
  channel=slack_channel_id, text=message, blocks=blocks
255
282
  )
283
+ if not response.get("ok", False):
284
+ error_details = response.get("error", "Unknown error")
285
+ logger.error(
286
+ f"Failed to send message to Slack channel. "
287
+ f"Error: {error_details}. Full response: {response}"
288
+ )
289
+ return False
256
290
  return True
257
291
  except SlackApiError as error:
258
- response = error.response["error"]
259
- logger.error(f"SlackAlerter.post() failed: {response}")
292
+ error_message = error.response.get("error", "Unknown error")
293
+ logger.error(
294
+ "SlackAlerter.post() failed with Slack API error: "
295
+ f"{error_message}. Full response: {error.response}"
296
+ )
297
+ return False
298
+ except Exception as e:
299
+ logger.error(f"Unexpected error in SlackAlerter.post(): {str(e)}")
260
300
  return False
261
301
 
262
302
  def ask(
263
- self, message: str, params: Optional[BaseAlerterStepParameters] = None
303
+ self, question: str, params: Optional[BaseAlerterStepParameters] = None
264
304
  ) -> bool:
265
305
  """Post a message to a Slack channel and wait for approval.
266
306
 
267
307
  Args:
268
- message: Initial message to be posted.
308
+ question: Initial message to be posted.
269
309
  params: Optional parameters.
270
310
 
271
311
  Returns:
272
312
  True if a user approved the operation, else False
273
313
  """
274
- rtm = RTMClient(token=self.config.slack_token)
275
314
  slack_channel_id = self._get_channel_id(params=params)
276
315
 
277
- approved = False # will be modified by handle()
278
-
279
- @RTMClient.run_on(event="hello") # type: ignore
280
- def post_initial_message(**payload: Any) -> None:
281
- """Post an initial message in a channel and start listening.
316
+ client = WebClient(token=self.config.slack_token)
317
+ approve_options = self._get_approve_msg_options(params)
318
+ disapprove_options = self._get_disapprove_msg_options(params)
282
319
 
283
- Args:
284
- payload: payload of the received Slack event.
285
- """
286
- web_client = payload["web_client"]
287
- blocks = self._create_blocks(message, params)
288
- web_client.chat_postMessage(
289
- channel=slack_channel_id, text=message, blocks=blocks
320
+ try:
321
+ # Send message to the Slack channel
322
+ response = client.chat_postMessage(
323
+ channel=slack_channel_id,
324
+ text=question,
325
+ blocks=self._create_blocks(question, params),
290
326
  )
291
327
 
292
- @RTMClient.run_on(event="message") # type: ignore
293
- def handle(**payload: Any) -> None:
294
- """Listen / handle messages posted in the channel.
295
-
296
- Args:
297
- payload: payload of the received Slack event.
298
- """
299
- event = payload["data"]
300
- if event["channel"] == slack_channel_id:
301
- # approve request (return True)
302
- if event["text"] in self._get_approve_msg_options(params):
303
- print(f"User {event['user']} approved on slack.")
304
- nonlocal approved
305
- approved = True
306
- rtm.stop() # type: ignore[no-untyped-call]
307
-
308
- # disapprove request (return False)
309
- elif event["text"] in self._get_disapprove_msg_options(params):
310
- print(f"User {event['user']} disapproved on slack.")
311
- rtm.stop() # type: ignore[no-untyped-call]
312
-
313
- # start another thread until `rtm.stop()` is called in handle()
314
- rtm.start()
315
-
316
- return approved
328
+ if not response.get("ok", False):
329
+ error_details = response.get("error", "Unknown error")
330
+ logger.error(
331
+ f"Failed to send the initial message to the Slack channel. "
332
+ f"Error: {error_details}. Full response: {response}"
333
+ )
334
+ return False
335
+
336
+ # Retrieve timestamp of sent message
337
+ timestamp = response["ts"]
338
+
339
+ # Wait for a response
340
+ start_time = time.time()
341
+
342
+ while time.time() - start_time < self._get_timeout_duration():
343
+ history = client.conversations_history(
344
+ channel=slack_channel_id, oldest=timestamp
345
+ )
346
+ for msg in history["messages"]:
347
+ if "ts" in msg and "user" in msg:
348
+ user_message = msg["text"].strip().lower()
349
+ if user_message in [
350
+ opt.lower() for opt in approve_options
351
+ ]:
352
+ logger.info("User approved the operation.")
353
+ return True
354
+ elif user_message in [
355
+ opt.lower() for opt in disapprove_options
356
+ ]:
357
+ logger.info("User disapproved the operation.")
358
+ return False
359
+
360
+ time.sleep(1) # Polling interval
361
+
362
+ logger.warning("No response received within the timeout period.")
363
+ return False
364
+
365
+ except SlackApiError as error:
366
+ error_message = error.response.get("error", "Unknown error")
367
+ logger.error(
368
+ f"SlackAlerter.ask() failed with Slack API error: "
369
+ f"{error_message}. Full response: {error.response}"
370
+ )
371
+ return False
372
+ except Exception as e:
373
+ logger.error(f"Unexpected error in SlackAlerter.ask(): {str(e)}")
374
+ return False
@@ -19,6 +19,7 @@ from zenml.alerter.base_alerter import BaseAlerterConfig, BaseAlerterFlavor
19
19
  from zenml.config.base_settings import BaseSettings
20
20
  from zenml.integrations.slack import SLACK_ALERTER_FLAVOR
21
21
  from zenml.logger import get_logger
22
+ from zenml.utils.deprecation_utils import deprecate_pydantic_attributes
22
23
  from zenml.utils.secret_utils import SecretField
23
24
 
24
25
  logger = get_logger(__name__)
@@ -32,21 +33,28 @@ class SlackAlerterSettings(BaseSettings):
32
33
 
33
34
  Attributes:
34
35
  slack_channel_id: The ID of the Slack channel to use for communication.
36
+ timeout: The amount of seconds to wait for the ask method.
35
37
  """
36
38
 
37
39
  slack_channel_id: Optional[str] = None
40
+ timeout: int = 300
38
41
 
39
42
 
40
- class SlackAlerterConfig(BaseAlerterConfig):
43
+ class SlackAlerterConfig(BaseAlerterConfig, SlackAlerterSettings):
41
44
  """Slack alerter config.
42
45
 
43
46
  Attributes:
44
47
  slack_token: The Slack token tied to the Slack account to be used.
45
- default_slack_channel_id: The ID of the default Slack channel to use for communication.
48
+ default_slack_channel_id: (deprecated) The ID of the default Slack
49
+ channel to use for communication.
46
50
  """
47
51
 
48
52
  slack_token: str = SecretField()
53
+
49
54
  default_slack_channel_id: Optional[str] = None
55
+ _deprecation_validator = deprecate_pydantic_attributes(
56
+ ("default_slack_channel_id", "slack_channel_id")
57
+ )
50
58
 
51
59
  @property
52
60
  def is_valid(self) -> bool:
@@ -60,9 +68,11 @@ class SlackAlerterConfig(BaseAlerterConfig):
60
68
  from slack_sdk.errors import SlackApiError
61
69
  except ImportError:
62
70
  logger.warning(
63
- "Unable to validate Slack alerter credentials because the Slack integration is not installed."
71
+ "Unable to validate the slack alerter, because the Slack "
72
+ "integration is not installed."
64
73
  )
65
74
  return True
75
+
66
76
  client = WebClient(token=self.slack_token)
67
77
  try:
68
78
  # Check slack token validity
@@ -70,10 +80,10 @@ class SlackAlerterConfig(BaseAlerterConfig):
70
80
  if not response["ok"]:
71
81
  return False
72
82
 
73
- if self.default_slack_channel_id:
83
+ if self.slack_channel_id:
74
84
  # Check channel validity
75
85
  response = client.conversations_info(
76
- channel=self.default_slack_channel_id
86
+ channel=self.slack_channel_id
77
87
  )
78
88
  valid: bool = response["ok"]
79
89
  return valid
@@ -97,7 +107,7 @@ class SlackAlerterFlavor(BaseAlerterFlavor):
97
107
 
98
108
  @property
99
109
  def docs_url(self) -> Optional[str]:
100
- """A url to point at docs explaining this flavor.
110
+ """A URL to point at docs explaining this flavor.
101
111
 
102
112
  Returns:
103
113
  A flavor docs url.
@@ -106,7 +116,7 @@ class SlackAlerterFlavor(BaseAlerterFlavor):
106
116
 
107
117
  @property
108
118
  def sdk_docs_url(self) -> Optional[str]:
109
- """A url to point at SDK docs explaining this flavor.
119
+ """A URL to point at SDK docs explaining this flavor.
110
120
 
111
121
  Returns:
112
122
  A flavor SDK docs url.
@@ -115,7 +125,7 @@ class SlackAlerterFlavor(BaseAlerterFlavor):
115
125
 
116
126
  @property
117
127
  def logo_url(self) -> str:
118
- """A url to represent the flavor in the dashboard.
128
+ """A URL to represent the flavor in the dashboard.
119
129
 
120
130
  Returns:
121
131
  The flavor logo.
@@ -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.utcnow()
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
 
@@ -28,6 +28,10 @@ from typing import (
28
28
  )
29
29
 
30
30
  from zenml.artifact_stores.base_artifact_store import BaseArtifactStore
31
+ from zenml.constants import (
32
+ ENV_ZENML_MATERIALIZER_ALLOW_NON_ASCII_JSON_DUMPS,
33
+ handle_bool_env_var,
34
+ )
31
35
  from zenml.enums import ArtifactType, VisualizationType
32
36
  from zenml.logger import get_logger
33
37
  from zenml.materializers.base_materializer import BaseMaterializer
@@ -48,6 +52,9 @@ BASIC_TYPES = (
48
52
  str,
49
53
  type(None),
50
54
  ) # complex/bytes are not JSON serializable
55
+ ZENML_MATERIALIZER_ALLOW_NON_ASCII_JSON_DUMPS = handle_bool_env_var(
56
+ ENV_ZENML_MATERIALIZER_ALLOW_NON_ASCII_JSON_DUMPS, False
57
+ )
51
58
 
52
59
 
53
60
  class BuiltInMaterializer(BaseMaterializer):
@@ -94,7 +101,11 @@ class BuiltInMaterializer(BaseMaterializer):
94
101
  Args:
95
102
  data: The data to store.
96
103
  """
97
- yaml_utils.write_json(self.data_path, data)
104
+ yaml_utils.write_json(
105
+ self.data_path,
106
+ data,
107
+ ensure_ascii=not ZENML_MATERIALIZER_ALLOW_NON_ASCII_JSON_DUMPS,
108
+ )
98
109
 
99
110
  def extract_metadata(
100
111
  self, data: Union[bool, float, int, str]
@@ -371,7 +382,11 @@ class BuiltInContainerMaterializer(BaseMaterializer):
371
382
 
372
383
  # If the data is serializable, just write it into a single JSON file.
373
384
  if _is_serializable(data):
374
- yaml_utils.write_json(self.data_path, data)
385
+ yaml_utils.write_json(
386
+ self.data_path,
387
+ data,
388
+ ensure_ascii=not ZENML_MATERIALIZER_ALLOW_NON_ASCII_JSON_DUMPS,
389
+ )
375
390
  return
376
391
 
377
392
  # non-serializable dict: Handle as non-serializable list of lists.
@@ -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.utcnow() - self.last_rotated < timedelta(
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
@@ -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.utcnow(),
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.utcnow(),
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.utcnow(),
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.utcnow()
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.utcnow()
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.utcnow(),
78
+ start_time=datetime.now(timezone.utc),
79
79
  user=Client().active_user.id,
80
80
  workspace=Client().active_workspace.id,
81
81
  )
@@ -474,7 +474,7 @@ def pipeline_(param_name: str):
474
474
  step_name()
475
475
 
476
476
  if __name__=="__main__":
477
- pipeline_.with_options(config_file="config.yaml")(param_name="value2")
477
+ pipeline_.with_options(config_path="config.yaml")(param_name="value2")
478
478
  ```
479
479
  To avoid this consider setting pipeline parameters only in one place (config or code).
480
480
  """
@@ -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.utcnow()
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.utcnow(),
803
- updated=datetime.utcnow(),
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.utcnow(),
755
- updated=datetime.utcnow(),
754
+ created=datetime.now(timezone.utc),
755
+ updated=datetime.now(timezone.utc),
756
756
  )
757
757
 
758
758
  self._image_builder = image_builder
@@ -197,6 +197,11 @@ class ZenMLCloudStackDeployment(BaseModel):
197
197
  # that was registered after this deployment was created.
198
198
 
199
199
  # Get all stacks created after the start date
200
+
201
+ if date_start and date_start.tzinfo:
202
+ date_start = date_start.astimezone(datetime.timezone.utc).replace(
203
+ tzinfo=None
204
+ )
200
205
  stacks = client.list_stacks(
201
206
  created=f"gt:{str(date_start.replace(microsecond=0))}"
202
207
  if date_start
zenml/utils/git_utils.py CHANGED
@@ -16,7 +16,7 @@
16
16
  import os
17
17
  from typing import Optional
18
18
 
19
- from git import GitCommandError
19
+ from git.exc import GitCommandError
20
20
  from git.repo import Repo
21
21
 
22
22
 
@@ -17,7 +17,7 @@ import base64
17
17
  import functools
18
18
  import random
19
19
  import string
20
- from datetime import datetime
20
+ from datetime import datetime, timezone
21
21
  from typing import Any, Callable, Dict, Optional, TypeVar, cast
22
22
 
23
23
  from pydantic import BaseModel
@@ -180,7 +180,7 @@ def format_name_template(
180
180
  start_time = None
181
181
 
182
182
  if start_time is None:
183
- start_time = datetime.utcnow()
183
+ start_time = datetime.now(timezone.utc)
184
184
  substitutions.setdefault("date", start_time.strftime("%Y_%m_%d"))
185
185
  substitutions.setdefault("time", start_time.strftime("%H_%M_%S_%f"))
186
186
 
zenml/utils/yaml_utils.py CHANGED
@@ -122,6 +122,7 @@ def write_json(
122
122
  file_path: str,
123
123
  contents: Any,
124
124
  encoder: Optional[Type[json.JSONEncoder]] = None,
125
+ **json_dump_args: Any,
125
126
  ) -> None:
126
127
  """Write contents as JSON format to file_path.
127
128
 
@@ -129,6 +130,7 @@ def write_json(
129
130
  file_path: Path to JSON file.
130
131
  contents: Contents of JSON file.
131
132
  encoder: Custom JSON encoder to use when saving json.
133
+ **json_dump_args: Extra arguments to pass to json.dumps.
132
134
 
133
135
  Raises:
134
136
  FileNotFoundError: if directory does not exist.
@@ -140,10 +142,7 @@ def write_json(
140
142
  raise FileNotFoundError(f"Directory {dir_} does not exist.")
141
143
  io_utils.write_file_contents_as_string(
142
144
  file_path,
143
- json.dumps(
144
- contents,
145
- cls=encoder,
146
- ),
145
+ json.dumps(contents, cls=encoder, **json_dump_args),
147
146
  )
148
147
 
149
148