mlrun 1.6.4rc8__py3-none-any.whl → 1.7.0__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.

Potentially problematic release.


This version of mlrun might be problematic. Click here for more details.

Files changed (305) hide show
  1. mlrun/__init__.py +11 -1
  2. mlrun/__main__.py +40 -122
  3. mlrun/alerts/__init__.py +15 -0
  4. mlrun/alerts/alert.py +248 -0
  5. mlrun/api/schemas/__init__.py +5 -4
  6. mlrun/artifacts/__init__.py +8 -3
  7. mlrun/artifacts/base.py +47 -257
  8. mlrun/artifacts/dataset.py +11 -192
  9. mlrun/artifacts/manager.py +79 -47
  10. mlrun/artifacts/model.py +31 -159
  11. mlrun/artifacts/plots.py +23 -380
  12. mlrun/common/constants.py +74 -1
  13. mlrun/common/db/sql_session.py +5 -5
  14. mlrun/common/formatters/__init__.py +21 -0
  15. mlrun/common/formatters/artifact.py +45 -0
  16. mlrun/common/formatters/base.py +113 -0
  17. mlrun/common/formatters/feature_set.py +33 -0
  18. mlrun/common/formatters/function.py +46 -0
  19. mlrun/common/formatters/pipeline.py +53 -0
  20. mlrun/common/formatters/project.py +51 -0
  21. mlrun/common/formatters/run.py +29 -0
  22. mlrun/common/helpers.py +12 -3
  23. mlrun/common/model_monitoring/helpers.py +9 -5
  24. mlrun/{runtimes → common/runtimes}/constants.py +37 -9
  25. mlrun/common/schemas/__init__.py +31 -5
  26. mlrun/common/schemas/alert.py +202 -0
  27. mlrun/common/schemas/api_gateway.py +196 -0
  28. mlrun/common/schemas/artifact.py +25 -4
  29. mlrun/common/schemas/auth.py +16 -5
  30. mlrun/common/schemas/background_task.py +1 -1
  31. mlrun/common/schemas/client_spec.py +4 -2
  32. mlrun/common/schemas/common.py +7 -4
  33. mlrun/common/schemas/constants.py +3 -0
  34. mlrun/common/schemas/feature_store.py +74 -44
  35. mlrun/common/schemas/frontend_spec.py +15 -7
  36. mlrun/common/schemas/function.py +12 -1
  37. mlrun/common/schemas/hub.py +11 -18
  38. mlrun/common/schemas/memory_reports.py +2 -2
  39. mlrun/common/schemas/model_monitoring/__init__.py +20 -4
  40. mlrun/common/schemas/model_monitoring/constants.py +123 -42
  41. mlrun/common/schemas/model_monitoring/grafana.py +13 -9
  42. mlrun/common/schemas/model_monitoring/model_endpoints.py +101 -54
  43. mlrun/common/schemas/notification.py +71 -14
  44. mlrun/common/schemas/object.py +2 -2
  45. mlrun/{model_monitoring/controller_handler.py → common/schemas/pagination.py} +9 -12
  46. mlrun/common/schemas/pipeline.py +8 -1
  47. mlrun/common/schemas/project.py +69 -18
  48. mlrun/common/schemas/runs.py +7 -1
  49. mlrun/common/schemas/runtime_resource.py +8 -12
  50. mlrun/common/schemas/schedule.py +4 -4
  51. mlrun/common/schemas/tag.py +1 -2
  52. mlrun/common/schemas/workflow.py +12 -4
  53. mlrun/common/types.py +14 -1
  54. mlrun/config.py +154 -69
  55. mlrun/data_types/data_types.py +6 -1
  56. mlrun/data_types/spark.py +2 -2
  57. mlrun/data_types/to_pandas.py +67 -37
  58. mlrun/datastore/__init__.py +6 -8
  59. mlrun/datastore/alibaba_oss.py +131 -0
  60. mlrun/datastore/azure_blob.py +143 -42
  61. mlrun/datastore/base.py +102 -58
  62. mlrun/datastore/datastore.py +34 -13
  63. mlrun/datastore/datastore_profile.py +146 -20
  64. mlrun/datastore/dbfs_store.py +3 -7
  65. mlrun/datastore/filestore.py +1 -4
  66. mlrun/datastore/google_cloud_storage.py +97 -33
  67. mlrun/datastore/hdfs.py +56 -0
  68. mlrun/datastore/inmem.py +6 -3
  69. mlrun/datastore/redis.py +7 -2
  70. mlrun/datastore/s3.py +34 -12
  71. mlrun/datastore/snowflake_utils.py +45 -0
  72. mlrun/datastore/sources.py +303 -111
  73. mlrun/datastore/spark_utils.py +31 -2
  74. mlrun/datastore/store_resources.py +9 -7
  75. mlrun/datastore/storeytargets.py +151 -0
  76. mlrun/datastore/targets.py +453 -176
  77. mlrun/datastore/utils.py +72 -58
  78. mlrun/datastore/v3io.py +6 -1
  79. mlrun/db/base.py +274 -41
  80. mlrun/db/factory.py +1 -1
  81. mlrun/db/httpdb.py +893 -225
  82. mlrun/db/nopdb.py +291 -33
  83. mlrun/errors.py +36 -6
  84. mlrun/execution.py +115 -42
  85. mlrun/feature_store/__init__.py +0 -2
  86. mlrun/feature_store/api.py +65 -73
  87. mlrun/feature_store/common.py +7 -12
  88. mlrun/feature_store/feature_set.py +76 -55
  89. mlrun/feature_store/feature_vector.py +39 -31
  90. mlrun/feature_store/ingestion.py +7 -6
  91. mlrun/feature_store/retrieval/base.py +16 -11
  92. mlrun/feature_store/retrieval/dask_merger.py +2 -0
  93. mlrun/feature_store/retrieval/job.py +13 -4
  94. mlrun/feature_store/retrieval/local_merger.py +2 -0
  95. mlrun/feature_store/retrieval/spark_merger.py +24 -32
  96. mlrun/feature_store/steps.py +45 -34
  97. mlrun/features.py +11 -21
  98. mlrun/frameworks/_common/artifacts_library.py +9 -9
  99. mlrun/frameworks/_common/mlrun_interface.py +5 -5
  100. mlrun/frameworks/_common/model_handler.py +48 -48
  101. mlrun/frameworks/_common/plan.py +5 -6
  102. mlrun/frameworks/_common/producer.py +3 -4
  103. mlrun/frameworks/_common/utils.py +5 -5
  104. mlrun/frameworks/_dl_common/loggers/logger.py +6 -7
  105. mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +9 -9
  106. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +23 -47
  107. mlrun/frameworks/_ml_common/artifacts_library.py +1 -2
  108. mlrun/frameworks/_ml_common/loggers/logger.py +3 -4
  109. mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +4 -5
  110. mlrun/frameworks/_ml_common/model_handler.py +24 -24
  111. mlrun/frameworks/_ml_common/pkl_model_server.py +2 -2
  112. mlrun/frameworks/_ml_common/plan.py +2 -2
  113. mlrun/frameworks/_ml_common/plans/calibration_curve_plan.py +2 -3
  114. mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +2 -3
  115. mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
  116. mlrun/frameworks/_ml_common/plans/feature_importance_plan.py +3 -3
  117. mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
  118. mlrun/frameworks/_ml_common/utils.py +4 -4
  119. mlrun/frameworks/auto_mlrun/auto_mlrun.py +9 -9
  120. mlrun/frameworks/huggingface/model_server.py +4 -4
  121. mlrun/frameworks/lgbm/__init__.py +33 -33
  122. mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
  123. mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -5
  124. mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -5
  125. mlrun/frameworks/lgbm/mlrun_interfaces/booster_mlrun_interface.py +1 -3
  126. mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +6 -6
  127. mlrun/frameworks/lgbm/model_handler.py +10 -10
  128. mlrun/frameworks/lgbm/model_server.py +6 -6
  129. mlrun/frameworks/lgbm/utils.py +5 -5
  130. mlrun/frameworks/onnx/dataset.py +8 -8
  131. mlrun/frameworks/onnx/mlrun_interface.py +3 -3
  132. mlrun/frameworks/onnx/model_handler.py +6 -6
  133. mlrun/frameworks/onnx/model_server.py +7 -7
  134. mlrun/frameworks/parallel_coordinates.py +6 -6
  135. mlrun/frameworks/pytorch/__init__.py +18 -18
  136. mlrun/frameworks/pytorch/callbacks/callback.py +4 -5
  137. mlrun/frameworks/pytorch/callbacks/logging_callback.py +17 -17
  138. mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +11 -11
  139. mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +23 -29
  140. mlrun/frameworks/pytorch/callbacks_handler.py +38 -38
  141. mlrun/frameworks/pytorch/mlrun_interface.py +20 -20
  142. mlrun/frameworks/pytorch/model_handler.py +17 -17
  143. mlrun/frameworks/pytorch/model_server.py +7 -7
  144. mlrun/frameworks/sklearn/__init__.py +13 -13
  145. mlrun/frameworks/sklearn/estimator.py +4 -4
  146. mlrun/frameworks/sklearn/metrics_library.py +14 -14
  147. mlrun/frameworks/sklearn/mlrun_interface.py +16 -9
  148. mlrun/frameworks/sklearn/model_handler.py +2 -2
  149. mlrun/frameworks/tf_keras/__init__.py +10 -7
  150. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +15 -15
  151. mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +11 -11
  152. mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +19 -23
  153. mlrun/frameworks/tf_keras/mlrun_interface.py +9 -11
  154. mlrun/frameworks/tf_keras/model_handler.py +14 -14
  155. mlrun/frameworks/tf_keras/model_server.py +6 -6
  156. mlrun/frameworks/xgboost/__init__.py +13 -13
  157. mlrun/frameworks/xgboost/model_handler.py +6 -6
  158. mlrun/k8s_utils.py +61 -17
  159. mlrun/launcher/__init__.py +1 -1
  160. mlrun/launcher/base.py +16 -15
  161. mlrun/launcher/client.py +13 -11
  162. mlrun/launcher/factory.py +1 -1
  163. mlrun/launcher/local.py +23 -13
  164. mlrun/launcher/remote.py +17 -10
  165. mlrun/lists.py +7 -6
  166. mlrun/model.py +478 -103
  167. mlrun/model_monitoring/__init__.py +1 -1
  168. mlrun/model_monitoring/api.py +163 -371
  169. mlrun/{runtimes/mpijob/v1alpha1.py → model_monitoring/applications/__init__.py} +9 -15
  170. mlrun/model_monitoring/applications/_application_steps.py +188 -0
  171. mlrun/model_monitoring/applications/base.py +108 -0
  172. mlrun/model_monitoring/applications/context.py +341 -0
  173. mlrun/model_monitoring/{evidently_application.py → applications/evidently_base.py} +27 -22
  174. mlrun/model_monitoring/applications/histogram_data_drift.py +354 -0
  175. mlrun/model_monitoring/applications/results.py +99 -0
  176. mlrun/model_monitoring/controller.py +131 -278
  177. mlrun/model_monitoring/db/__init__.py +18 -0
  178. mlrun/model_monitoring/db/stores/__init__.py +136 -0
  179. mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
  180. mlrun/model_monitoring/db/stores/base/store.py +213 -0
  181. mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
  182. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
  183. mlrun/model_monitoring/db/stores/sqldb/models/base.py +190 -0
  184. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +103 -0
  185. mlrun/model_monitoring/{stores/models/mysql.py → db/stores/sqldb/models/sqlite.py} +19 -13
  186. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +659 -0
  187. mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
  188. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +726 -0
  189. mlrun/model_monitoring/db/tsdb/__init__.py +105 -0
  190. mlrun/model_monitoring/db/tsdb/base.py +448 -0
  191. mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
  192. mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
  193. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +279 -0
  194. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +42 -0
  195. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +507 -0
  196. mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
  197. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +158 -0
  198. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +849 -0
  199. mlrun/model_monitoring/features_drift_table.py +134 -106
  200. mlrun/model_monitoring/helpers.py +199 -55
  201. mlrun/model_monitoring/metrics/__init__.py +13 -0
  202. mlrun/model_monitoring/metrics/histogram_distance.py +127 -0
  203. mlrun/model_monitoring/model_endpoint.py +3 -2
  204. mlrun/model_monitoring/stream_processing.py +134 -398
  205. mlrun/model_monitoring/tracking_policy.py +9 -2
  206. mlrun/model_monitoring/writer.py +161 -125
  207. mlrun/package/__init__.py +6 -6
  208. mlrun/package/context_handler.py +5 -5
  209. mlrun/package/packager.py +7 -7
  210. mlrun/package/packagers/default_packager.py +8 -8
  211. mlrun/package/packagers/numpy_packagers.py +15 -15
  212. mlrun/package/packagers/pandas_packagers.py +5 -5
  213. mlrun/package/packagers/python_standard_library_packagers.py +10 -10
  214. mlrun/package/packagers_manager.py +19 -23
  215. mlrun/package/utils/_formatter.py +6 -6
  216. mlrun/package/utils/_pickler.py +2 -2
  217. mlrun/package/utils/_supported_format.py +4 -4
  218. mlrun/package/utils/log_hint_utils.py +2 -2
  219. mlrun/package/utils/type_hint_utils.py +4 -9
  220. mlrun/platforms/__init__.py +11 -10
  221. mlrun/platforms/iguazio.py +24 -203
  222. mlrun/projects/operations.py +52 -25
  223. mlrun/projects/pipelines.py +191 -197
  224. mlrun/projects/project.py +1227 -400
  225. mlrun/render.py +16 -19
  226. mlrun/run.py +209 -184
  227. mlrun/runtimes/__init__.py +83 -15
  228. mlrun/runtimes/base.py +51 -35
  229. mlrun/runtimes/daskjob.py +17 -10
  230. mlrun/runtimes/databricks_job/databricks_cancel_task.py +1 -1
  231. mlrun/runtimes/databricks_job/databricks_runtime.py +8 -7
  232. mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
  233. mlrun/runtimes/funcdoc.py +1 -29
  234. mlrun/runtimes/function_reference.py +1 -1
  235. mlrun/runtimes/kubejob.py +34 -128
  236. mlrun/runtimes/local.py +40 -11
  237. mlrun/runtimes/mpijob/__init__.py +0 -20
  238. mlrun/runtimes/mpijob/abstract.py +9 -10
  239. mlrun/runtimes/mpijob/v1.py +1 -1
  240. mlrun/{model_monitoring/stores/models/sqlite.py → runtimes/nuclio/__init__.py} +7 -9
  241. mlrun/runtimes/nuclio/api_gateway.py +769 -0
  242. mlrun/runtimes/nuclio/application/__init__.py +15 -0
  243. mlrun/runtimes/nuclio/application/application.py +758 -0
  244. mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
  245. mlrun/runtimes/{function.py → nuclio/function.py} +200 -83
  246. mlrun/runtimes/{nuclio.py → nuclio/nuclio.py} +6 -6
  247. mlrun/runtimes/{serving.py → nuclio/serving.py} +65 -68
  248. mlrun/runtimes/pod.py +281 -101
  249. mlrun/runtimes/remotesparkjob.py +12 -9
  250. mlrun/runtimes/sparkjob/spark3job.py +67 -51
  251. mlrun/runtimes/utils.py +41 -75
  252. mlrun/secrets.py +9 -5
  253. mlrun/serving/__init__.py +8 -1
  254. mlrun/serving/remote.py +2 -7
  255. mlrun/serving/routers.py +85 -69
  256. mlrun/serving/server.py +69 -44
  257. mlrun/serving/states.py +209 -36
  258. mlrun/serving/utils.py +22 -14
  259. mlrun/serving/v1_serving.py +6 -7
  260. mlrun/serving/v2_serving.py +133 -54
  261. mlrun/track/tracker.py +2 -1
  262. mlrun/track/tracker_manager.py +3 -3
  263. mlrun/track/trackers/mlflow_tracker.py +6 -2
  264. mlrun/utils/async_http.py +6 -8
  265. mlrun/utils/azure_vault.py +1 -1
  266. mlrun/utils/clones.py +1 -2
  267. mlrun/utils/condition_evaluator.py +3 -3
  268. mlrun/utils/db.py +21 -3
  269. mlrun/utils/helpers.py +405 -225
  270. mlrun/utils/http.py +3 -6
  271. mlrun/utils/logger.py +112 -16
  272. mlrun/utils/notifications/notification/__init__.py +17 -13
  273. mlrun/utils/notifications/notification/base.py +50 -2
  274. mlrun/utils/notifications/notification/console.py +2 -0
  275. mlrun/utils/notifications/notification/git.py +24 -1
  276. mlrun/utils/notifications/notification/ipython.py +3 -1
  277. mlrun/utils/notifications/notification/slack.py +96 -21
  278. mlrun/utils/notifications/notification/webhook.py +59 -2
  279. mlrun/utils/notifications/notification_pusher.py +149 -30
  280. mlrun/utils/regex.py +9 -0
  281. mlrun/utils/retryer.py +208 -0
  282. mlrun/utils/singleton.py +1 -1
  283. mlrun/utils/v3io_clients.py +4 -6
  284. mlrun/utils/version/version.json +2 -2
  285. mlrun/utils/version/version.py +2 -6
  286. mlrun-1.7.0.dist-info/METADATA +378 -0
  287. mlrun-1.7.0.dist-info/RECORD +351 -0
  288. {mlrun-1.6.4rc8.dist-info → mlrun-1.7.0.dist-info}/WHEEL +1 -1
  289. mlrun/feature_store/retrieval/conversion.py +0 -273
  290. mlrun/kfpops.py +0 -868
  291. mlrun/model_monitoring/application.py +0 -310
  292. mlrun/model_monitoring/batch.py +0 -1095
  293. mlrun/model_monitoring/prometheus.py +0 -219
  294. mlrun/model_monitoring/stores/__init__.py +0 -111
  295. mlrun/model_monitoring/stores/kv_model_endpoint_store.py +0 -576
  296. mlrun/model_monitoring/stores/model_endpoint_store.py +0 -147
  297. mlrun/model_monitoring/stores/models/__init__.py +0 -27
  298. mlrun/model_monitoring/stores/models/base.py +0 -84
  299. mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -384
  300. mlrun/platforms/other.py +0 -306
  301. mlrun-1.6.4rc8.dist-info/METADATA +0 -272
  302. mlrun-1.6.4rc8.dist-info/RECORD +0 -314
  303. {mlrun-1.6.4rc8.dist-info → mlrun-1.7.0.dist-info}/LICENSE +0 -0
  304. {mlrun-1.6.4rc8.dist-info → mlrun-1.7.0.dist-info}/entry_points.txt +0 -0
  305. {mlrun-1.6.4rc8.dist-info → mlrun-1.7.0.dist-info}/top_level.txt +0 -0
@@ -28,6 +28,12 @@ class WebhookNotification(NotificationBase):
28
28
  API/Client notification for sending run statuses in a http request
29
29
  """
30
30
 
31
+ @classmethod
32
+ def validate_params(cls, params):
33
+ url = params.get("url", None)
34
+ if not url:
35
+ raise ValueError("Parameter 'url' is required for WebhookNotification")
36
+
31
37
  async def push(
32
38
  self,
33
39
  message: str,
@@ -36,6 +42,8 @@ class WebhookNotification(NotificationBase):
36
42
  ] = mlrun.common.schemas.NotificationSeverity.INFO,
37
43
  runs: typing.Union[mlrun.lists.RunList, list] = None,
38
44
  custom_html: str = None,
45
+ alert: mlrun.common.schemas.AlertConfig = None,
46
+ event_data: mlrun.common.schemas.Event = None,
39
47
  ):
40
48
  url = self.params.get("url", None)
41
49
  method = self.params.get("method", "post").lower()
@@ -46,14 +54,29 @@ class WebhookNotification(NotificationBase):
46
54
  request_body = {
47
55
  "message": message,
48
56
  "severity": severity,
49
- "runs": runs,
50
57
  }
51
58
 
59
+ if runs:
60
+ request_body["runs"] = runs
61
+
62
+ if alert:
63
+ request_body["name"] = alert.name
64
+ request_body["project"] = alert.project
65
+ request_body["severity"] = alert.severity
66
+ if alert.summary:
67
+ request_body["summary"] = mlrun.utils.helpers.format_alert_summary(
68
+ alert, event_data
69
+ )
70
+
71
+ if event_data:
72
+ request_body["value"] = event_data.value_dict
73
+ request_body["id"] = event_data.entity.ids[0]
74
+
52
75
  if custom_html:
53
76
  request_body["custom_html"] = custom_html
54
77
 
55
78
  if override_body:
56
- request_body = override_body
79
+ request_body = self._serialize_runs_in_request_body(override_body, runs)
57
80
 
58
81
  # Specify the `verify_ssl` parameter value only for HTTPS urls.
59
82
  # The `ClientSession` allows using `ssl=None` for the default SSL check,
@@ -67,3 +90,37 @@ class WebhookNotification(NotificationBase):
67
90
  url, headers=headers, json=request_body, ssl=verify_ssl
68
91
  )
69
92
  response.raise_for_status()
93
+
94
+ @staticmethod
95
+ def _serialize_runs_in_request_body(override_body, runs):
96
+ str_parsed_runs = ""
97
+ runs = runs or []
98
+
99
+ def parse_runs():
100
+ parsed_runs = []
101
+ for run in runs:
102
+ if hasattr(run, "to_dict"):
103
+ run = run.to_dict()
104
+ if isinstance(run, dict):
105
+ parsed_run = {
106
+ "project": run["metadata"]["project"],
107
+ "name": run["metadata"]["name"],
108
+ "host": run["metadata"]["labels"]["host"],
109
+ "status": {"state": run["status"]["state"]},
110
+ }
111
+ if run["status"].get("error", None):
112
+ parsed_run["status"]["error"] = run["status"]["error"]
113
+ elif run["status"].get("results", None):
114
+ parsed_run["status"]["results"] = run["status"]["results"]
115
+ parsed_runs.append(parsed_run)
116
+ return str(parsed_runs)
117
+
118
+ if isinstance(override_body, dict):
119
+ for key, value in override_body.items():
120
+ if "{{ runs }}" or "{{runs}}" in value:
121
+ if not str_parsed_runs:
122
+ str_parsed_runs = parse_runs()
123
+ override_body[key] = value.replace(
124
+ "{{ runs }}", str_parsed_runs
125
+ ).replace("{{runs}}", str_parsed_runs)
126
+ return override_body
@@ -15,10 +15,17 @@
15
15
  import asyncio
16
16
  import datetime
17
17
  import os
18
+ import re
18
19
  import traceback
19
20
  import typing
20
21
  from concurrent.futures import ThreadPoolExecutor
21
22
 
23
+ import mlrun_pipelines.common.ops
24
+ import mlrun_pipelines.models
25
+ import mlrun_pipelines.utils
26
+
27
+ import mlrun.common.constants as mlrun_constants
28
+ import mlrun.common.runtimes.constants
22
29
  import mlrun.common.schemas
23
30
  import mlrun.config
24
31
  import mlrun.db.base
@@ -32,7 +39,7 @@ from mlrun.utils.condition_evaluator import evaluate_condition_in_separate_proce
32
39
  from .notification import NotificationBase, NotificationTypes
33
40
 
34
41
 
35
- class _NotificationPusherBase(object):
42
+ class _NotificationPusherBase:
36
43
  def _push(
37
44
  self, sync_push_callback: typing.Callable, async_push_callback: typing.Callable
38
45
  ):
@@ -95,15 +102,11 @@ class NotificationPusher(_NotificationPusherBase):
95
102
 
96
103
  def __init__(self, runs: typing.Union[mlrun.lists.RunList, list]):
97
104
  self._runs = runs
98
- self._sync_notifications: typing.List[
99
- typing.Tuple[
100
- NotificationBase, mlrun.model.RunObject, mlrun.model.Notification
101
- ]
105
+ self._sync_notifications: list[
106
+ tuple[NotificationBase, mlrun.model.RunObject, mlrun.model.Notification]
102
107
  ] = []
103
- self._async_notifications: typing.List[
104
- typing.Tuple[
105
- NotificationBase, mlrun.model.RunObject, mlrun.model.Notification
106
- ]
108
+ self._async_notifications: list[
109
+ tuple[NotificationBase, mlrun.model.RunObject, mlrun.model.Notification]
107
110
  ] = []
108
111
 
109
112
  for run in self._runs:
@@ -237,25 +240,12 @@ class NotificationPusher(_NotificationPusherBase):
237
240
  resource = "Run"
238
241
  runs = [run.to_dict()]
239
242
 
240
- if "workflow" in run.metadata.labels:
241
- resource = "Workflow"
243
+ if mlrun_constants.MLRunInternalLabels.workflow in run.metadata.labels:
244
+ resource = mlrun_constants.MLRunInternalLabels.workflow
242
245
  custom_message = (
243
246
  f" (workflow: {run.metadata.labels['workflow']}){custom_message}"
244
247
  )
245
- db = mlrun.get_run_db()
246
-
247
- workflow_id = run.status.results.get("workflow_id", None)
248
- if workflow_id:
249
- workflow_runs = db.list_runs(
250
- project=run.metadata.project,
251
- labels=f"workflow={workflow_id}",
252
- )
253
- logger.debug(
254
- "Found workflow runs, extending notification runs",
255
- workflow_id=workflow_id,
256
- workflow_runs_amount=len(workflow_runs),
257
- )
258
- runs.extend(workflow_runs)
248
+ runs.extend(self.get_workflow_steps(run))
259
249
 
260
250
  message = (
261
251
  self.messages.get(run.state(), "").format(resource=resource)
@@ -399,9 +389,134 @@ class NotificationPusher(_NotificationPusherBase):
399
389
  mask_params=False,
400
390
  )
401
391
 
392
+ def get_workflow_steps(self, run: mlrun.model.RunObject) -> list:
393
+ steps = []
394
+ db = mlrun.get_run_db()
395
+
396
+ def _add_run_step(_step: mlrun_pipelines.models.PipelineStep):
397
+ try:
398
+ _run = db.list_runs(
399
+ project=run.metadata.project,
400
+ labels=f"{mlrun_constants.MLRunInternalLabels.runner_pod}={_step.node_name}",
401
+ )[0]
402
+ except IndexError:
403
+ _run = {
404
+ "metadata": {
405
+ "name": _step.display_name,
406
+ "project": run.metadata.project,
407
+ },
408
+ }
409
+ _run["step_kind"] = _step.step_type
410
+ if _step.skipped:
411
+ _run.setdefault("status", {})["state"] = (
412
+ mlrun.common.runtimes.constants.RunStates.skipped
413
+ )
414
+ steps.append(_run)
415
+
416
+ def _add_deploy_function_step(_step: mlrun_pipelines.models.PipelineStep):
417
+ project, name, hash_key = self._extract_function_uri(
418
+ _step.get_annotation("mlrun/function-uri")
419
+ )
420
+ if name:
421
+ try:
422
+ function = db.get_function(
423
+ project=project, name=name, hash_key=hash_key
424
+ )
425
+ except mlrun.errors.MLRunNotFoundError:
426
+ # If the function is not found (if build failed for example), we will create a dummy
427
+ # function object for the notification to display the function name
428
+ function = {
429
+ "metadata": {
430
+ "name": name,
431
+ "project": project,
432
+ "hash_key": hash_key,
433
+ },
434
+ }
435
+ pod_phase = _step.phase
436
+ if _step.skipped:
437
+ state = mlrun.common.schemas.FunctionState.skipped
438
+ else:
439
+ state = mlrun.common.runtimes.constants.PodPhases.pod_phase_to_run_state(
440
+ pod_phase
441
+ )
442
+ function["status"] = {"state": state}
443
+ if isinstance(function["metadata"].get("updated"), datetime.datetime):
444
+ function["metadata"]["updated"] = function["metadata"][
445
+ "updated"
446
+ ].isoformat()
447
+ function["step_kind"] = _step.step_type
448
+ steps.append(function)
449
+
450
+ step_methods = {
451
+ mlrun_pipelines.common.ops.PipelineRunType.run: _add_run_step,
452
+ mlrun_pipelines.common.ops.PipelineRunType.build: _add_deploy_function_step,
453
+ mlrun_pipelines.common.ops.PipelineRunType.deploy: _add_deploy_function_step,
454
+ }
455
+
456
+ workflow_id = run.status.results.get("workflow_id", None)
457
+ if not workflow_id:
458
+ return steps
459
+
460
+ workflow_manifest = self._get_workflow_manifest(workflow_id)
461
+ if not workflow_manifest:
462
+ return steps
463
+
464
+ try:
465
+ for step in workflow_manifest.get_steps():
466
+ step_method = step_methods.get(step.step_type)
467
+ if step_method:
468
+ step_method(step)
469
+ return steps
470
+ except Exception:
471
+ # If we fail to read the pipeline steps, we will return the list of runs that have the same workflow id
472
+ logger.warning(
473
+ "Failed to extract workflow steps from workflow manifest, "
474
+ "returning all runs with the workflow id label",
475
+ workflow_id=workflow_id,
476
+ traceback=traceback.format_exc(),
477
+ )
478
+ return db.list_runs(
479
+ project=run.metadata.project,
480
+ labels=f"workflow={workflow_id}",
481
+ )
482
+
483
+ @staticmethod
484
+ def _get_workflow_manifest(
485
+ workflow_id: str,
486
+ ) -> typing.Optional[mlrun_pipelines.models.PipelineManifest]:
487
+ kfp_client = mlrun_pipelines.utils.get_client(mlrun.mlconf.kfp_url)
488
+
489
+ # arbitrary timeout of 5 seconds, the workflow should be done by now
490
+ kfp_run = kfp_client.wait_for_run_completion(workflow_id, 5)
491
+ if not kfp_run:
492
+ return None
493
+
494
+ kfp_run = mlrun_pipelines.models.PipelineRun(kfp_run)
495
+ return kfp_run.workflow_manifest()
496
+
497
+ def _extract_function_uri(self, function_uri: str) -> tuple[str, str, str]:
498
+ """
499
+ Extract the project, name, and hash key from a function uri.
500
+ Examples:
501
+ - "project/name@hash_key" returns project, name, hash_key
502
+ - "project/name returns" project, name, ""
503
+ """
504
+ project, name, hash_key = None, None, None
505
+ hashed_pattern = r"^(.+)/(.+)@(.+)$"
506
+ pattern = r"^(.+)/(.+)$"
507
+ match = re.match(hashed_pattern, function_uri)
508
+ if match:
509
+ project, name, hash_key = match.groups()
510
+ else:
511
+ match = re.match(pattern, function_uri)
512
+ if match:
513
+ project, name = match.groups()
514
+ hash_key = ""
515
+ return project, name, hash_key
516
+
402
517
 
403
518
  class CustomNotificationPusher(_NotificationPusherBase):
404
- def __init__(self, notification_types: typing.List[str] = None):
519
+ def __init__(self, notification_types: list[str] = None):
405
520
  notifications = {
406
521
  notification_type: NotificationTypes(notification_type).get_notification()()
407
522
  for notification_type in notification_types
@@ -417,6 +532,12 @@ class CustomNotificationPusher(_NotificationPusherBase):
417
532
  if notification.is_async
418
533
  }
419
534
 
535
+ @property
536
+ def notifications(self):
537
+ notifications = self._sync_notifications.copy()
538
+ notifications.update(self._async_notifications)
539
+ return notifications
540
+
420
541
  def push(
421
542
  self,
422
543
  message: str,
@@ -446,7 +567,7 @@ class CustomNotificationPusher(_NotificationPusherBase):
446
567
  def add_notification(
447
568
  self,
448
569
  notification_type: str,
449
- params: typing.Dict[str, str] = None,
570
+ params: dict[str, str] = None,
450
571
  ):
451
572
  if notification_type in self._async_notifications:
452
573
  self._async_notifications[notification_type].load_notification(params)
@@ -471,9 +592,7 @@ class CustomNotificationPusher(_NotificationPusherBase):
471
592
  else:
472
593
  logger.warning(f"No notification of type {notification_type} in project")
473
594
 
474
- def edit_notification(
475
- self, notification_type: str, params: typing.Dict[str, str] = None
476
- ):
595
+ def edit_notification(self, notification_type: str, params: dict[str, str] = None):
477
596
  self.remove_notification(notification_type)
478
597
  self.add_notification(notification_type, params)
479
598
 
mlrun/utils/regex.py CHANGED
@@ -92,3 +92,12 @@ artifact_key = [r"[^\/\\]+$"]
92
92
  # must be alphanumeric or _
93
93
  # max 256 length
94
94
  v3io_stream_consumer_group = [r"^(?!_)[a-zA-Z0-9_]{1,256}$"]
95
+
96
+ # URI patterns
97
+ run_uri_pattern = r"^(?P<project>.*)@(?P<uid>.*)\#(?P<iteration>.*?)(:(?P<tag>.*))?$"
98
+
99
+ artifact_uri_pattern = r"^((?P<project>.*)/)?(?P<key>.*?)(\#(?P<iteration>.*?))?(:(?P<tag>.*?))?(@(?P<tree>.*))?$"
100
+
101
+ artifact_producer_uri_pattern = (
102
+ r"^((?P<project>.*)/)?(?P<uid>.*?)(\-(?P<iteration>.*?))?$"
103
+ )
mlrun/utils/retryer.py ADDED
@@ -0,0 +1,208 @@
1
+ # Copyright 2023 Iguazio
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
+ # http://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 or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import asyncio
16
+ import time
17
+
18
+ import mlrun.errors
19
+
20
+
21
+ def create_linear_backoff(base=2, coefficient=2, stop_value=120):
22
+ """
23
+ Create a generator of linear backoff. Check out usage example in test_helpers.py
24
+ """
25
+ x = 0
26
+ comparison = min if coefficient >= 0 else max
27
+
28
+ while True:
29
+ next_value = comparison(base + x * coefficient, stop_value)
30
+ yield next_value
31
+ x += 1
32
+
33
+
34
+ def create_step_backoff(steps=None):
35
+ """
36
+ Create a generator of steps backoff.
37
+ Example: steps = [[2, 5], [20, 10], [120, None]] will produce a generator in which the first 5
38
+ values will be 2, the next 10 values will be 20 and the rest will be 120.
39
+ :param steps: a list of lists [step_value, number_of_iteration_in_this_step]
40
+ """
41
+ steps = steps if steps is not None else [[2, 10], [10, 10], [120, None]]
42
+ steps = iter(steps)
43
+
44
+ # Get first step
45
+ step = next(steps)
46
+ while True:
47
+ current_step_value, current_step_remain = step
48
+ if current_step_remain == 0:
49
+ # No more in this step, moving on
50
+ step = next(steps)
51
+ elif current_step_remain is None:
52
+ # We are in the last step, staying here forever
53
+ yield current_step_value
54
+ elif current_step_remain > 0:
55
+ # Still more remains in this step, just reduce the remaining number
56
+ step[1] -= 1
57
+ yield current_step_value
58
+
59
+
60
+ def create_exponential_backoff(base=2, max_value=120, scale_factor=1):
61
+ """
62
+ Create a generator of exponential backoff. Check out usage example in test_helpers.py
63
+ :param base: exponent base
64
+ :param max_value: max limit on the result
65
+ :param scale_factor: factor to be used as linear scaling coefficient
66
+ """
67
+ exponent = 1
68
+ while True:
69
+ # This "complex" implementation (unlike the one in linear backoff) is to avoid exponent growing too fast and
70
+ # risking going behind max_int
71
+ next_value = scale_factor * (base**exponent)
72
+ if next_value < max_value:
73
+ exponent += 1
74
+ yield next_value
75
+ else:
76
+ yield max_value
77
+
78
+
79
+ class Retryer:
80
+ def __init__(self, backoff, timeout, logger, verbose, function, *args, **kwargs):
81
+ """
82
+ Initialize function retryer with given *args and **kwargs.
83
+ Tries to run it until success or timeout reached (timeout is optional)
84
+ :param backoff: can either be a:
85
+ - number (int / float) that will be used as interval.
86
+ - generator of waiting intervals. (support next())
87
+ :param timeout: pass None if timeout is not wanted, number of seconds if it is
88
+ :param logger: a logger so we can log the failures
89
+ :param verbose: whether to log the failure on each retry
90
+ :param _function: function to run
91
+ :param args: functions args
92
+ :param kwargs: functions kwargs
93
+ """
94
+ self.backoff = backoff
95
+ self.timeout = timeout
96
+ self.logger = logger
97
+ self.verbose = verbose
98
+ self.function = function
99
+ self.args = args
100
+ self.kwargs = kwargs
101
+ self.start_time = None
102
+ self.last_exception = None
103
+ self.first_interval = None
104
+
105
+ def run(self):
106
+ self._prepare()
107
+ while not self._timeout_exceeded():
108
+ next_interval = self.first_interval or next(self.backoff)
109
+ result, exc, retry = self._perform_call(next_interval)
110
+ if retry:
111
+ time.sleep(next_interval)
112
+ elif not exc:
113
+ return result
114
+ else:
115
+ break
116
+
117
+ self._raise_last_exception()
118
+
119
+ def _prepare(self):
120
+ self.start_time = time.monotonic()
121
+ self.last_exception = None
122
+
123
+ # Check if backoff is just a simple interval
124
+ if isinstance(self.backoff, int) or isinstance(self.backoff, float):
125
+ self.backoff = create_linear_backoff(base=self.backoff, coefficient=0)
126
+
127
+ self.first_interval = next(self.backoff)
128
+ if self.timeout and self.timeout <= self.first_interval:
129
+ self.logger.warning(
130
+ f"Timeout ({self.timeout}) must be higher than backoff ({self.first_interval})."
131
+ f" Set timeout to be higher than backoff."
132
+ )
133
+
134
+ def _perform_call(self, next_interval):
135
+ try:
136
+ result = self.function(*self.args, **self.kwargs)
137
+ return result, None, False
138
+ except mlrun.errors.MLRunFatalFailureError as exc:
139
+ raise exc.original_exception
140
+ except Exception as exc:
141
+ self.last_exception = exc
142
+ return (
143
+ None,
144
+ self.last_exception,
145
+ self._assert_failure_timeout(next_interval, exc),
146
+ )
147
+
148
+ def _assert_failure_timeout(self, next_interval, exc):
149
+ self.last_exception = exc
150
+
151
+ # If next interval is within allowed time period - wait on interval, abort otherwise
152
+ if not self._timeout_exceeded(next_interval):
153
+ if self.logger is not None and self.verbose:
154
+ self.logger.debug(
155
+ f"Operation not yet successful, Retrying in {next_interval} seconds."
156
+ f" exc: {mlrun.errors.err_to_str(exc)}"
157
+ )
158
+ return True
159
+ else:
160
+ return False
161
+
162
+ def _raise_last_exception(self):
163
+ if self.logger is not None:
164
+ self.logger.warning(
165
+ f"Operation did not complete on time. last exception: {self.last_exception}"
166
+ )
167
+
168
+ raise mlrun.errors.MLRunRetryExhaustedError(
169
+ f"Failed to execute command by the given deadline."
170
+ f" last_exception: {self.last_exception},"
171
+ f" function_name: {self.function.__name__},"
172
+ f" timeout: {self.timeout}"
173
+ ) from self.last_exception
174
+
175
+ def _timeout_exceeded(self, next_interval=None):
176
+ now = time.monotonic()
177
+ if next_interval:
178
+ now = now + next_interval
179
+ return self.timeout is not None and now >= self.start_time + self.timeout
180
+
181
+
182
+ class AsyncRetryer(Retryer):
183
+ async def run(self):
184
+ self._prepare()
185
+ while not self._timeout_exceeded():
186
+ next_interval = self.first_interval or next(self.backoff)
187
+ result, exc, retry = await self._perform_call(next_interval)
188
+ if retry:
189
+ await asyncio.sleep(next_interval)
190
+ elif not exc:
191
+ return result
192
+ else:
193
+ break
194
+
195
+ self._raise_last_exception()
196
+
197
+ async def _perform_call(self, next_interval):
198
+ try:
199
+ result = await self.function(*self.args, **self.kwargs)
200
+ return result, None, False
201
+ except mlrun.errors.MLRunFatalFailureError as exc:
202
+ raise exc.original_exception
203
+ except Exception as exc:
204
+ return (
205
+ None,
206
+ self.last_exception,
207
+ self._assert_failure_timeout(next_interval, exc),
208
+ )
mlrun/utils/singleton.py CHANGED
@@ -20,7 +20,7 @@ class Singleton(type):
20
20
 
21
21
  def __call__(cls, *args, **kwargs):
22
22
  if cls not in cls._instances:
23
- cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
23
+ cls._instances[cls] = super().__call__(*args, **kwargs)
24
24
  return cls._instances[cls]
25
25
 
26
26
 
@@ -11,22 +11,20 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- #
15
- from typing import Dict, FrozenSet
16
14
 
17
15
  from v3io.dataplane import Client as V3IOClient
18
- from v3io_frames import Client as get_client
16
+ from v3io_frames import Client as V3IOFramesClient
19
17
  from v3io_frames.client import ClientBase
20
18
 
21
- _v3io_clients: Dict[FrozenSet, V3IOClient] = {}
22
- _frames_clients: Dict[FrozenSet, ClientBase] = {}
19
+ _v3io_clients: dict[frozenset, V3IOClient] = {}
20
+ _frames_clients: dict[frozenset, ClientBase] = {}
23
21
 
24
22
 
25
23
  def get_frames_client(**kwargs) -> ClientBase:
26
24
  global _frames_clients
27
25
  kw_set = frozenset(kwargs.items())
28
26
  if kw_set not in _frames_clients:
29
- _frames_clients[kw_set] = get_client(**kwargs)
27
+ _frames_clients[kw_set] = V3IOFramesClient(**kwargs)
30
28
 
31
29
  return _frames_clients[kw_set]
32
30
 
@@ -1,4 +1,4 @@
1
1
  {
2
- "git_commit": "0ee0a62bfe73dbc8ad04a3a1c6ad958e40a6f56a",
3
- "version": "1.6.4-rc8"
2
+ "git_commit": "8b31f05f7defd779cd119acd2ba3f7ae66e81316",
3
+ "version": "1.7.0"
4
4
  }
@@ -14,15 +14,11 @@
14
14
  #
15
15
  import json
16
16
  import sys
17
+ from importlib.resources import read_text
17
18
 
18
19
  import mlrun.utils
19
20
  from mlrun.utils.singleton import Singleton
20
21
 
21
- if sys.version_info >= (3, 7):
22
- from importlib.resources import read_text
23
- else:
24
- from importlib_resources import read_text
25
-
26
22
 
27
23
  class _VersionInfo:
28
24
  def __init__(self, major, minor, patch):
@@ -56,5 +52,5 @@ class Version(metaclass=Singleton):
56
52
  return self.python_version
57
53
 
58
54
  @staticmethod
59
- def _resolve_python_version() -> sys.version_info:
55
+ def _resolve_python_version() -> _VersionInfo:
60
56
  return _VersionInfo(*sys.version_info[:3])