mlrun 1.6.4rc2__py3-none-any.whl → 1.7.0rc20__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 (291) hide show
  1. mlrun/__init__.py +11 -1
  2. mlrun/__main__.py +26 -112
  3. mlrun/alerts/__init__.py +15 -0
  4. mlrun/alerts/alert.py +144 -0
  5. mlrun/api/schemas/__init__.py +5 -4
  6. mlrun/artifacts/__init__.py +8 -3
  7. mlrun/artifacts/base.py +46 -257
  8. mlrun/artifacts/dataset.py +11 -192
  9. mlrun/artifacts/manager.py +47 -48
  10. mlrun/artifacts/model.py +31 -159
  11. mlrun/artifacts/plots.py +23 -380
  12. mlrun/common/constants.py +69 -0
  13. mlrun/common/db/sql_session.py +2 -3
  14. mlrun/common/formatters/__init__.py +19 -0
  15. mlrun/common/formatters/artifact.py +21 -0
  16. mlrun/common/formatters/base.py +78 -0
  17. mlrun/common/formatters/function.py +41 -0
  18. mlrun/common/formatters/pipeline.py +53 -0
  19. mlrun/common/formatters/project.py +51 -0
  20. mlrun/common/helpers.py +1 -2
  21. mlrun/common/model_monitoring/helpers.py +9 -5
  22. mlrun/{runtimes → common/runtimes}/constants.py +37 -9
  23. mlrun/common/schemas/__init__.py +24 -4
  24. mlrun/common/schemas/alert.py +203 -0
  25. mlrun/common/schemas/api_gateway.py +148 -0
  26. mlrun/common/schemas/artifact.py +18 -8
  27. mlrun/common/schemas/auth.py +11 -5
  28. mlrun/common/schemas/background_task.py +1 -1
  29. mlrun/common/schemas/client_spec.py +4 -1
  30. mlrun/common/schemas/feature_store.py +16 -16
  31. mlrun/common/schemas/frontend_spec.py +8 -7
  32. mlrun/common/schemas/function.py +5 -1
  33. mlrun/common/schemas/hub.py +11 -18
  34. mlrun/common/schemas/memory_reports.py +2 -2
  35. mlrun/common/schemas/model_monitoring/__init__.py +18 -3
  36. mlrun/common/schemas/model_monitoring/constants.py +83 -26
  37. mlrun/common/schemas/model_monitoring/grafana.py +13 -9
  38. mlrun/common/schemas/model_monitoring/model_endpoints.py +99 -16
  39. mlrun/common/schemas/notification.py +4 -4
  40. mlrun/common/schemas/object.py +2 -2
  41. mlrun/{runtimes/mpijob/v1alpha1.py → common/schemas/pagination.py} +10 -13
  42. mlrun/common/schemas/pipeline.py +1 -10
  43. mlrun/common/schemas/project.py +24 -23
  44. mlrun/common/schemas/runtime_resource.py +8 -12
  45. mlrun/common/schemas/schedule.py +3 -3
  46. mlrun/common/schemas/tag.py +1 -2
  47. mlrun/common/schemas/workflow.py +2 -2
  48. mlrun/common/types.py +7 -1
  49. mlrun/config.py +54 -17
  50. mlrun/data_types/to_pandas.py +10 -12
  51. mlrun/datastore/__init__.py +5 -8
  52. mlrun/datastore/alibaba_oss.py +130 -0
  53. mlrun/datastore/azure_blob.py +17 -5
  54. mlrun/datastore/base.py +62 -39
  55. mlrun/datastore/datastore.py +28 -9
  56. mlrun/datastore/datastore_profile.py +146 -20
  57. mlrun/datastore/filestore.py +0 -1
  58. mlrun/datastore/google_cloud_storage.py +6 -2
  59. mlrun/datastore/hdfs.py +56 -0
  60. mlrun/datastore/inmem.py +2 -2
  61. mlrun/datastore/redis.py +6 -2
  62. mlrun/datastore/s3.py +9 -0
  63. mlrun/datastore/snowflake_utils.py +43 -0
  64. mlrun/datastore/sources.py +201 -96
  65. mlrun/datastore/spark_utils.py +1 -2
  66. mlrun/datastore/store_resources.py +7 -7
  67. mlrun/datastore/targets.py +358 -104
  68. mlrun/datastore/utils.py +72 -58
  69. mlrun/datastore/v3io.py +5 -1
  70. mlrun/db/base.py +185 -35
  71. mlrun/db/factory.py +1 -1
  72. mlrun/db/httpdb.py +614 -179
  73. mlrun/db/nopdb.py +210 -26
  74. mlrun/errors.py +12 -1
  75. mlrun/execution.py +41 -24
  76. mlrun/feature_store/__init__.py +0 -2
  77. mlrun/feature_store/api.py +40 -72
  78. mlrun/feature_store/common.py +1 -1
  79. mlrun/feature_store/feature_set.py +76 -55
  80. mlrun/feature_store/feature_vector.py +28 -30
  81. mlrun/feature_store/ingestion.py +7 -6
  82. mlrun/feature_store/retrieval/base.py +16 -11
  83. mlrun/feature_store/retrieval/conversion.py +11 -13
  84. mlrun/feature_store/retrieval/dask_merger.py +2 -0
  85. mlrun/feature_store/retrieval/job.py +9 -3
  86. mlrun/feature_store/retrieval/local_merger.py +2 -0
  87. mlrun/feature_store/retrieval/spark_merger.py +34 -24
  88. mlrun/feature_store/steps.py +37 -34
  89. mlrun/features.py +9 -20
  90. mlrun/frameworks/_common/artifacts_library.py +9 -9
  91. mlrun/frameworks/_common/mlrun_interface.py +5 -5
  92. mlrun/frameworks/_common/model_handler.py +48 -48
  93. mlrun/frameworks/_common/plan.py +2 -3
  94. mlrun/frameworks/_common/producer.py +3 -4
  95. mlrun/frameworks/_common/utils.py +5 -5
  96. mlrun/frameworks/_dl_common/loggers/logger.py +6 -7
  97. mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +9 -9
  98. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +23 -47
  99. mlrun/frameworks/_ml_common/artifacts_library.py +1 -2
  100. mlrun/frameworks/_ml_common/loggers/logger.py +3 -4
  101. mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +4 -5
  102. mlrun/frameworks/_ml_common/model_handler.py +24 -24
  103. mlrun/frameworks/_ml_common/pkl_model_server.py +2 -2
  104. mlrun/frameworks/_ml_common/plan.py +1 -1
  105. mlrun/frameworks/_ml_common/plans/calibration_curve_plan.py +2 -3
  106. mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +2 -3
  107. mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
  108. mlrun/frameworks/_ml_common/plans/feature_importance_plan.py +3 -3
  109. mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
  110. mlrun/frameworks/_ml_common/utils.py +4 -4
  111. mlrun/frameworks/auto_mlrun/auto_mlrun.py +9 -9
  112. mlrun/frameworks/huggingface/model_server.py +4 -4
  113. mlrun/frameworks/lgbm/__init__.py +33 -33
  114. mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
  115. mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -5
  116. mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -5
  117. mlrun/frameworks/lgbm/mlrun_interfaces/booster_mlrun_interface.py +1 -3
  118. mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +6 -6
  119. mlrun/frameworks/lgbm/model_handler.py +10 -10
  120. mlrun/frameworks/lgbm/model_server.py +6 -6
  121. mlrun/frameworks/lgbm/utils.py +5 -5
  122. mlrun/frameworks/onnx/dataset.py +8 -8
  123. mlrun/frameworks/onnx/mlrun_interface.py +3 -3
  124. mlrun/frameworks/onnx/model_handler.py +6 -6
  125. mlrun/frameworks/onnx/model_server.py +7 -7
  126. mlrun/frameworks/parallel_coordinates.py +4 -3
  127. mlrun/frameworks/pytorch/__init__.py +18 -18
  128. mlrun/frameworks/pytorch/callbacks/callback.py +4 -5
  129. mlrun/frameworks/pytorch/callbacks/logging_callback.py +17 -17
  130. mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +11 -11
  131. mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +23 -29
  132. mlrun/frameworks/pytorch/callbacks_handler.py +38 -38
  133. mlrun/frameworks/pytorch/mlrun_interface.py +20 -20
  134. mlrun/frameworks/pytorch/model_handler.py +17 -17
  135. mlrun/frameworks/pytorch/model_server.py +7 -7
  136. mlrun/frameworks/sklearn/__init__.py +13 -13
  137. mlrun/frameworks/sklearn/estimator.py +4 -4
  138. mlrun/frameworks/sklearn/metrics_library.py +14 -14
  139. mlrun/frameworks/sklearn/mlrun_interface.py +3 -6
  140. mlrun/frameworks/sklearn/model_handler.py +2 -2
  141. mlrun/frameworks/tf_keras/__init__.py +10 -7
  142. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +15 -15
  143. mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +11 -11
  144. mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +19 -23
  145. mlrun/frameworks/tf_keras/mlrun_interface.py +9 -11
  146. mlrun/frameworks/tf_keras/model_handler.py +14 -14
  147. mlrun/frameworks/tf_keras/model_server.py +6 -6
  148. mlrun/frameworks/xgboost/__init__.py +13 -13
  149. mlrun/frameworks/xgboost/model_handler.py +6 -6
  150. mlrun/k8s_utils.py +14 -16
  151. mlrun/launcher/__init__.py +1 -1
  152. mlrun/launcher/base.py +16 -15
  153. mlrun/launcher/client.py +8 -6
  154. mlrun/launcher/factory.py +1 -1
  155. mlrun/launcher/local.py +17 -11
  156. mlrun/launcher/remote.py +16 -10
  157. mlrun/lists.py +7 -6
  158. mlrun/model.py +238 -73
  159. mlrun/model_monitoring/__init__.py +1 -1
  160. mlrun/model_monitoring/api.py +138 -315
  161. mlrun/model_monitoring/application.py +5 -296
  162. mlrun/model_monitoring/applications/__init__.py +24 -0
  163. mlrun/model_monitoring/applications/_application_steps.py +157 -0
  164. mlrun/model_monitoring/applications/base.py +282 -0
  165. mlrun/model_monitoring/applications/context.py +214 -0
  166. mlrun/model_monitoring/applications/evidently_base.py +211 -0
  167. mlrun/model_monitoring/applications/histogram_data_drift.py +349 -0
  168. mlrun/model_monitoring/applications/results.py +99 -0
  169. mlrun/model_monitoring/controller.py +104 -84
  170. mlrun/model_monitoring/controller_handler.py +13 -5
  171. mlrun/model_monitoring/db/__init__.py +18 -0
  172. mlrun/model_monitoring/{stores → db/stores}/__init__.py +43 -36
  173. mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
  174. mlrun/model_monitoring/{stores/model_endpoint_store.py → db/stores/base/store.py} +64 -40
  175. mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
  176. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
  177. mlrun/model_monitoring/{stores → db/stores/sqldb}/models/base.py +109 -5
  178. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +88 -0
  179. mlrun/model_monitoring/{stores/models/mysql.py → db/stores/sqldb/models/sqlite.py} +19 -13
  180. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +684 -0
  181. mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
  182. mlrun/model_monitoring/{stores/kv_model_endpoint_store.py → db/stores/v3io_kv/kv_store.py} +310 -165
  183. mlrun/model_monitoring/db/tsdb/__init__.py +100 -0
  184. mlrun/model_monitoring/db/tsdb/base.py +329 -0
  185. mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
  186. mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
  187. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +240 -0
  188. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +45 -0
  189. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +397 -0
  190. mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
  191. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +117 -0
  192. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +630 -0
  193. mlrun/model_monitoring/evidently_application.py +6 -118
  194. mlrun/model_monitoring/features_drift_table.py +134 -106
  195. mlrun/model_monitoring/helpers.py +127 -28
  196. mlrun/model_monitoring/metrics/__init__.py +13 -0
  197. mlrun/model_monitoring/metrics/histogram_distance.py +127 -0
  198. mlrun/model_monitoring/model_endpoint.py +3 -2
  199. mlrun/model_monitoring/prometheus.py +1 -4
  200. mlrun/model_monitoring/stream_processing.py +62 -231
  201. mlrun/model_monitoring/tracking_policy.py +9 -2
  202. mlrun/model_monitoring/writer.py +152 -124
  203. mlrun/package/__init__.py +6 -6
  204. mlrun/package/context_handler.py +5 -5
  205. mlrun/package/packager.py +7 -7
  206. mlrun/package/packagers/default_packager.py +6 -6
  207. mlrun/package/packagers/numpy_packagers.py +15 -15
  208. mlrun/package/packagers/pandas_packagers.py +5 -5
  209. mlrun/package/packagers/python_standard_library_packagers.py +10 -10
  210. mlrun/package/packagers_manager.py +19 -23
  211. mlrun/package/utils/_formatter.py +6 -6
  212. mlrun/package/utils/_pickler.py +2 -2
  213. mlrun/package/utils/_supported_format.py +4 -4
  214. mlrun/package/utils/log_hint_utils.py +2 -2
  215. mlrun/package/utils/type_hint_utils.py +4 -9
  216. mlrun/platforms/__init__.py +11 -10
  217. mlrun/platforms/iguazio.py +24 -203
  218. mlrun/projects/operations.py +35 -21
  219. mlrun/projects/pipelines.py +68 -99
  220. mlrun/projects/project.py +830 -266
  221. mlrun/render.py +3 -11
  222. mlrun/run.py +162 -166
  223. mlrun/runtimes/__init__.py +62 -7
  224. mlrun/runtimes/base.py +39 -32
  225. mlrun/runtimes/daskjob.py +8 -8
  226. mlrun/runtimes/databricks_job/databricks_cancel_task.py +1 -1
  227. mlrun/runtimes/databricks_job/databricks_runtime.py +7 -7
  228. mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
  229. mlrun/runtimes/funcdoc.py +0 -28
  230. mlrun/runtimes/function_reference.py +1 -1
  231. mlrun/runtimes/kubejob.py +28 -122
  232. mlrun/runtimes/local.py +6 -3
  233. mlrun/runtimes/mpijob/__init__.py +0 -20
  234. mlrun/runtimes/mpijob/abstract.py +9 -10
  235. mlrun/runtimes/mpijob/v1.py +1 -1
  236. mlrun/{model_monitoring/stores/models/sqlite.py → runtimes/nuclio/__init__.py} +7 -9
  237. mlrun/runtimes/nuclio/api_gateway.py +709 -0
  238. mlrun/runtimes/nuclio/application/__init__.py +15 -0
  239. mlrun/runtimes/nuclio/application/application.py +523 -0
  240. mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
  241. mlrun/runtimes/{function.py → nuclio/function.py} +112 -73
  242. mlrun/runtimes/{nuclio.py → nuclio/nuclio.py} +6 -6
  243. mlrun/runtimes/{serving.py → nuclio/serving.py} +45 -51
  244. mlrun/runtimes/pod.py +286 -88
  245. mlrun/runtimes/remotesparkjob.py +2 -2
  246. mlrun/runtimes/sparkjob/spark3job.py +51 -34
  247. mlrun/runtimes/utils.py +7 -75
  248. mlrun/secrets.py +9 -5
  249. mlrun/serving/remote.py +2 -7
  250. mlrun/serving/routers.py +13 -10
  251. mlrun/serving/server.py +22 -26
  252. mlrun/serving/states.py +99 -25
  253. mlrun/serving/utils.py +3 -3
  254. mlrun/serving/v1_serving.py +6 -7
  255. mlrun/serving/v2_serving.py +59 -20
  256. mlrun/track/tracker.py +2 -1
  257. mlrun/track/tracker_manager.py +3 -3
  258. mlrun/track/trackers/mlflow_tracker.py +1 -2
  259. mlrun/utils/async_http.py +5 -7
  260. mlrun/utils/azure_vault.py +1 -1
  261. mlrun/utils/clones.py +1 -2
  262. mlrun/utils/condition_evaluator.py +3 -3
  263. mlrun/utils/db.py +3 -3
  264. mlrun/utils/helpers.py +183 -197
  265. mlrun/utils/http.py +2 -5
  266. mlrun/utils/logger.py +76 -14
  267. mlrun/utils/notifications/notification/__init__.py +17 -12
  268. mlrun/utils/notifications/notification/base.py +14 -2
  269. mlrun/utils/notifications/notification/console.py +2 -0
  270. mlrun/utils/notifications/notification/git.py +3 -1
  271. mlrun/utils/notifications/notification/ipython.py +3 -1
  272. mlrun/utils/notifications/notification/slack.py +101 -21
  273. mlrun/utils/notifications/notification/webhook.py +11 -1
  274. mlrun/utils/notifications/notification_pusher.py +155 -30
  275. mlrun/utils/retryer.py +208 -0
  276. mlrun/utils/singleton.py +1 -1
  277. mlrun/utils/v3io_clients.py +2 -4
  278. mlrun/utils/version/version.json +2 -2
  279. mlrun/utils/version/version.py +2 -6
  280. {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/METADATA +31 -19
  281. mlrun-1.7.0rc20.dist-info/RECORD +353 -0
  282. mlrun/kfpops.py +0 -868
  283. mlrun/model_monitoring/batch.py +0 -1095
  284. mlrun/model_monitoring/stores/models/__init__.py +0 -27
  285. mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -384
  286. mlrun/platforms/other.py +0 -306
  287. mlrun-1.6.4rc2.dist-info/RECORD +0 -314
  288. {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/LICENSE +0 -0
  289. {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/WHEEL +0 -0
  290. {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/entry_points.txt +0 -0
  291. {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/top_level.txt +0 -0
mlrun/utils/logger.py CHANGED
@@ -12,7 +12,6 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- import json
16
15
  import logging
17
16
  import typing
18
17
  from enum import Enum
@@ -20,15 +19,42 @@ from sys import stdout
20
19
  from traceback import format_exception
21
20
  from typing import IO, Optional, Union
22
21
 
23
- from mlrun.config import config
22
+ import orjson
23
+ import pydantic
24
24
 
25
+ from mlrun.config import config
25
26
 
26
- class JSONFormatter(logging.Formatter):
27
- def __init__(self):
28
- super(JSONFormatter, self).__init__()
29
- self._json_encoder = json.JSONEncoder()
30
27
 
31
- def format(self, record):
28
+ class _BaseFormatter(logging.Formatter):
29
+ def _json_dump(self, json_object):
30
+ def default(obj):
31
+ if isinstance(obj, pydantic.BaseModel):
32
+ return obj.dict()
33
+
34
+ # EAFP all the way.
35
+ # Leave the unused "exc" in for debugging ease
36
+ try:
37
+ return obj.__log__()
38
+ except Exception as exc: # noqa
39
+ try:
40
+ return obj.__repr__()
41
+ except Exception as exc: # noqa
42
+ try:
43
+ return str(obj)
44
+ except Exception as exc:
45
+ raise TypeError from exc
46
+
47
+ return orjson.dumps(
48
+ json_object,
49
+ option=orjson.OPT_NAIVE_UTC
50
+ | orjson.OPT_SERIALIZE_NUMPY
51
+ | orjson.OPT_SORT_KEYS,
52
+ default=default,
53
+ ).decode()
54
+
55
+
56
+ class JSONFormatter(_BaseFormatter):
57
+ def format(self, record) -> str:
32
58
  record_with = getattr(record, "with", {})
33
59
  if record.exc_info:
34
60
  record_with.update(exc_info=format_exception(*record.exc_info))
@@ -39,14 +65,24 @@ class JSONFormatter(logging.Formatter):
39
65
  "with": record_with,
40
66
  }
41
67
 
42
- return self._json_encoder.encode(record_fields)
68
+ return self._json_dump(record_fields)
43
69
 
44
70
 
45
- class HumanReadableFormatter(logging.Formatter):
71
+ class HumanReadableFormatter(_BaseFormatter):
46
72
  def format(self, record) -> str:
73
+ more = self._resolve_more(record)
74
+ return (
75
+ f"> {self.formatTime(record, self.datefmt)} "
76
+ f"[{record.levelname.lower()}] "
77
+ f"{record.getMessage().rstrip()}"
78
+ f"{more}"
79
+ )
80
+
81
+ def _resolve_more(self, record):
47
82
  record_with = self._record_with(record)
48
- more = f": {record_with}" if record_with else ""
49
- return f"> {self.formatTime(record, self.datefmt)} [{record.levelname.lower()}] {record.getMessage()}{more}"
83
+ record_with_encoded = self._json_dump(record_with) if record_with else ""
84
+ more = f": {record_with_encoded}" if record_with_encoded else ""
85
+ return more
50
86
 
51
87
  def _record_with(self, record):
52
88
  record_with = getattr(record, "with", {})
@@ -57,8 +93,25 @@ class HumanReadableFormatter(logging.Formatter):
57
93
 
58
94
  class HumanReadableExtendedFormatter(HumanReadableFormatter):
59
95
  def format(self, record) -> str:
96
+ more = ""
60
97
  record_with = self._record_with(record)
61
- more = f": {record_with}" if record_with else ""
98
+ if record_with:
99
+
100
+ def _format_value(val):
101
+ formatted_val = (
102
+ val
103
+ if isinstance(val, str)
104
+ else str(orjson.loads(self._json_dump(val)))
105
+ )
106
+ return (
107
+ formatted_val.replace("\n", "\n\t\t")
108
+ if len(formatted_val) < 4096
109
+ else repr(formatted_val)
110
+ )
111
+
112
+ more = "\n\t" + "\n\t".join(
113
+ [f"{key}: {_format_value(val)}" for key, val in record_with.items()]
114
+ )
62
115
  return (
63
116
  "> "
64
117
  f"{self.formatTime(record, self.datefmt)} "
@@ -67,7 +120,7 @@ class HumanReadableExtendedFormatter(HumanReadableFormatter):
67
120
  )
68
121
 
69
122
 
70
- class Logger(object):
123
+ class Logger:
71
124
  def __init__(
72
125
  self,
73
126
  level,
@@ -189,7 +242,7 @@ class FormatterKinds(Enum):
189
242
 
190
243
  def resolve_formatter_by_kind(
191
244
  formatter_kind: FormatterKinds,
192
- ) -> typing.Type[
245
+ ) -> type[
193
246
  typing.Union[HumanReadableFormatter, HumanReadableExtendedFormatter, JSONFormatter]
194
247
  ]:
195
248
  return {
@@ -199,6 +252,15 @@ def resolve_formatter_by_kind(
199
252
  }[formatter_kind]
200
253
 
201
254
 
255
+ def create_test_logger(name: str = "mlrun", stream: IO[str] = stdout) -> Logger:
256
+ return create_logger(
257
+ level="debug",
258
+ formatter_kind=FormatterKinds.HUMAN_EXTENDED.name,
259
+ name=name,
260
+ stream=stream,
261
+ )
262
+
263
+
202
264
  def create_logger(
203
265
  level: Optional[str] = None,
204
266
  formatter_kind: str = FormatterKinds.HUMAN.name,
@@ -32,7 +32,7 @@ class NotificationTypes(str, enum.Enum):
32
32
  slack = NotificationKind.slack.value
33
33
  webhook = NotificationKind.webhook.value
34
34
 
35
- def get_notification(self) -> typing.Type[NotificationBase]:
35
+ def get_notification(self) -> type[NotificationBase]:
36
36
  return {
37
37
  self.console: ConsoleNotification,
38
38
  self.git: GitNotification,
@@ -41,7 +41,7 @@ class NotificationTypes(str, enum.Enum):
41
41
  self.webhook: WebhookNotification,
42
42
  }.get(self)
43
43
 
44
- def inverse_dependencies(self) -> typing.List[str]:
44
+ def inverse_dependencies(self) -> list[str]:
45
45
  """
46
46
  Some notifications should only run if another notification type didn't run.
47
47
  Per given notification type, return a list of notification types that should not run in order for this
@@ -52,13 +52,18 @@ class NotificationTypes(str, enum.Enum):
52
52
  }.get(self, [])
53
53
 
54
54
  @classmethod
55
- def all(cls) -> typing.List[str]:
56
- return list(
57
- [
58
- cls.console,
59
- cls.git,
60
- cls.ipython,
61
- cls.slack,
62
- cls.webhook,
63
- ]
64
- )
55
+ def local(cls) -> list[str]:
56
+ return [
57
+ cls.console,
58
+ cls.ipython,
59
+ ]
60
+
61
+ @classmethod
62
+ def all(cls) -> list[str]:
63
+ return [
64
+ cls.console,
65
+ cls.git,
66
+ cls.ipython,
67
+ cls.slack,
68
+ cls.webhook,
69
+ ]
@@ -23,7 +23,7 @@ class NotificationBase:
23
23
  def __init__(
24
24
  self,
25
25
  name: str = None,
26
- params: typing.Dict[str, str] = None,
26
+ params: dict[str, str] = None,
27
27
  ):
28
28
  self.name = name
29
29
  self.params = params or {}
@@ -44,12 +44,14 @@ class NotificationBase:
44
44
  ] = mlrun.common.schemas.NotificationSeverity.INFO,
45
45
  runs: typing.Union[mlrun.lists.RunList, list] = None,
46
46
  custom_html: str = None,
47
+ alert: mlrun.common.schemas.AlertConfig = None,
48
+ event_data: mlrun.common.schemas.Event = None,
47
49
  ):
48
50
  raise NotImplementedError()
49
51
 
50
52
  def load_notification(
51
53
  self,
52
- params: typing.Dict[str, str],
54
+ params: dict[str, str],
53
55
  ) -> None:
54
56
  self.params = params or {}
55
57
 
@@ -61,6 +63,8 @@ class NotificationBase:
61
63
  ] = mlrun.common.schemas.NotificationSeverity.INFO,
62
64
  runs: typing.Union[mlrun.lists.RunList, list] = None,
63
65
  custom_html: str = None,
66
+ alert: mlrun.common.schemas.AlertConfig = None,
67
+ event_data: mlrun.common.schemas.Event = None,
64
68
  ) -> str:
65
69
  if custom_html:
66
70
  return custom_html
@@ -68,6 +72,14 @@ class NotificationBase:
68
72
  if self.name:
69
73
  message = f"{self.name}: {message}"
70
74
 
75
+ if alert:
76
+ if not event_data:
77
+ return f"[{severity}] {message}"
78
+ return (
79
+ f"[{severity}] {message} for project {alert.project} "
80
+ f"UID {event_data.entity.ids[0]}. Values {event_data.value_dict}"
81
+ )
82
+
71
83
  if not runs:
72
84
  return f"[{severity}] {message}"
73
85
 
@@ -36,6 +36,8 @@ class ConsoleNotification(NotificationBase):
36
36
  ] = mlrun.common.schemas.NotificationSeverity.INFO,
37
37
  runs: typing.Union[mlrun.lists.RunList, list] = None,
38
38
  custom_html: str = None,
39
+ alert: mlrun.common.schemas.AlertConfig = None,
40
+ event_data: mlrun.common.schemas.Event = None,
39
41
  ):
40
42
  severity = self._resolve_severity(severity)
41
43
  print(f"[{severity}] {message}")
@@ -38,6 +38,8 @@ class GitNotification(NotificationBase):
38
38
  ] = mlrun.common.schemas.NotificationSeverity.INFO,
39
39
  runs: typing.Union[mlrun.lists.RunList, list] = None,
40
40
  custom_html: str = None,
41
+ alert: mlrun.common.schemas.AlertConfig = None,
42
+ event_data: mlrun.common.schemas.Event = None,
41
43
  ):
42
44
  git_repo = self.params.get("repo", None)
43
45
  git_issue = self.params.get("issue", None)
@@ -50,7 +52,7 @@ class GitNotification(NotificationBase):
50
52
  server = self.params.get("server", None)
51
53
  gitlab = self.params.get("gitlab", False)
52
54
  await self._pr_comment(
53
- self._get_html(message, severity, runs, custom_html),
55
+ self._get_html(message, severity, runs, custom_html, alert, event_data),
54
56
  git_repo,
55
57
  git_issue,
56
58
  merge_request=git_merge_request,
@@ -29,7 +29,7 @@ class IPythonNotification(NotificationBase):
29
29
  def __init__(
30
30
  self,
31
31
  name: str = None,
32
- params: typing.Dict[str, str] = None,
32
+ params: dict[str, str] = None,
33
33
  ):
34
34
  super().__init__(name, params)
35
35
  self._ipython = None
@@ -53,6 +53,8 @@ class IPythonNotification(NotificationBase):
53
53
  ] = mlrun.common.schemas.NotificationSeverity.INFO,
54
54
  runs: typing.Union[mlrun.lists.RunList, list] = None,
55
55
  custom_html: str = None,
56
+ alert: mlrun.common.schemas.AlertConfig = None,
57
+ event_data: mlrun.common.schemas.Event = None,
56
58
  ):
57
59
  if not self._ipython:
58
60
  mlrun.utils.helpers.logger.debug(
@@ -32,6 +32,7 @@ class SlackNotification(NotificationBase):
32
32
  "completed": ":smiley:",
33
33
  "running": ":man-running:",
34
34
  "error": ":x:",
35
+ "skipped": ":zzz:",
35
36
  }
36
37
 
37
38
  async def push(
@@ -42,6 +43,8 @@ class SlackNotification(NotificationBase):
42
43
  ] = mlrun.common.schemas.NotificationSeverity.INFO,
43
44
  runs: typing.Union[mlrun.lists.RunList, list] = None,
44
45
  custom_html: str = None,
46
+ alert: mlrun.common.schemas.AlertConfig = None,
47
+ event_data: mlrun.common.schemas.Event = None,
45
48
  ):
46
49
  webhook = self.params.get("webhook", None) or mlrun.get_secret_or_env(
47
50
  "SLACK_WEBHOOK"
@@ -53,7 +56,7 @@ class SlackNotification(NotificationBase):
53
56
  )
54
57
  return
55
58
 
56
- data = self._generate_slack_data(message, severity, runs)
59
+ data = self._generate_slack_data(message, severity, runs, alert, event_data)
57
60
 
58
61
  async with aiohttp.ClientSession() as session:
59
62
  async with session.post(webhook, json=data) as response:
@@ -66,57 +69,134 @@ class SlackNotification(NotificationBase):
66
69
  mlrun.common.schemas.NotificationSeverity, str
67
70
  ] = mlrun.common.schemas.NotificationSeverity.INFO,
68
71
  runs: typing.Union[mlrun.lists.RunList, list] = None,
72
+ alert: mlrun.common.schemas.AlertConfig = None,
73
+ event_data: mlrun.common.schemas.Event = None,
69
74
  ) -> dict:
70
75
  data = {
71
- "blocks": [
72
- {
73
- "type": "section",
74
- "text": self._get_slack_row(f"[{severity}] {message}"),
75
- },
76
- ]
76
+ "blocks": self._generate_slack_header_blocks(severity, message),
77
77
  }
78
78
  if self.name:
79
79
  data["blocks"].append(
80
80
  {"type": "section", "text": self._get_slack_row(self.name)}
81
81
  )
82
82
 
83
- if not runs:
84
- return data
83
+ if alert:
84
+ fields = self._get_alert_fields(alert, event_data)
85
85
 
86
- if isinstance(runs, list):
87
- runs = mlrun.lists.RunList(runs)
86
+ for i in range(len(fields)):
87
+ data["blocks"].append({"type": "section", "text": fields[i]})
88
+ else:
89
+ if not runs:
90
+ return data
88
91
 
89
- fields = [self._get_slack_row("*Runs*"), self._get_slack_row("*Results*")]
90
- for run in runs:
91
- fields.append(self._get_run_line(run))
92
- fields.append(self._get_run_result(run))
92
+ if isinstance(runs, list):
93
+ runs = mlrun.lists.RunList(runs)
93
94
 
94
- for i in range(0, len(fields), 8):
95
- data["blocks"].append({"type": "section", "fields": fields[i : i + 8]})
95
+ fields = [self._get_slack_row("*Runs*"), self._get_slack_row("*Results*")]
96
+ for run in runs:
97
+ fields.append(self._get_run_line(run))
98
+ fields.append(self._get_run_result(run))
99
+
100
+ for i in range(0, len(fields), 8):
101
+ data["blocks"].append({"type": "section", "fields": fields[i : i + 8]})
96
102
 
97
103
  return data
98
104
 
105
+ def _generate_slack_header_blocks(self, severity: str, message: str):
106
+ header_text = block_text = f"[{severity}] {message}"
107
+ section_text = None
108
+
109
+ # Slack doesn't allow headers to be longer than 150 characters
110
+ # If there's a comma in the message, split the message at the comma
111
+ # Otherwise, split the message at 150 characters
112
+ if len(block_text) > 150:
113
+ if ", " in block_text and block_text.index(", ") < 149:
114
+ header_text = block_text.split(",")[0]
115
+ section_text = block_text[len(header_text) + 2 :]
116
+ else:
117
+ header_text = block_text[:150]
118
+ section_text = block_text[150:]
119
+ blocks = [
120
+ {"type": "header", "text": {"type": "plain_text", "text": header_text}}
121
+ ]
122
+ if section_text:
123
+ blocks.append(
124
+ {
125
+ "type": "section",
126
+ "text": self._get_slack_row(section_text),
127
+ }
128
+ )
129
+ return blocks
130
+
131
+ def _get_alert_fields(
132
+ self,
133
+ alert: mlrun.common.schemas.AlertConfig,
134
+ event_data: mlrun.common.schemas.Event,
135
+ ) -> list:
136
+ line = [
137
+ self._get_slack_row(f":bell: {alert.name} alert has occurred"),
138
+ self._get_slack_row(f"*Project:*\n{alert.project}"),
139
+ self._get_slack_row(f"*ID:*\n{event_data.entity.ids[0]}"),
140
+ ]
141
+
142
+ if alert.summary:
143
+ line.append(
144
+ self._get_slack_row(
145
+ f"*Summary:*\n{mlrun.utils.helpers.format_alert_summary(alert, event_data)}"
146
+ )
147
+ )
148
+
149
+ if event_data.value_dict:
150
+ data_lines = []
151
+ for key, value in event_data.value_dict.items():
152
+ data_lines.append(f"{key}: {value}")
153
+ data_text = "\n".join(data_lines)
154
+ line.append(self._get_slack_row(f"*Event data:*\n{data_text}"))
155
+
156
+ if (
157
+ event_data.entity.kind == mlrun.common.schemas.alert.EventEntityKind.JOB
158
+ ): # JOB entity
159
+ uid = event_data.value_dict.get("uid")
160
+ url = mlrun.utils.helpers.get_ui_url(alert.project, uid)
161
+ overview_type = "Job overview"
162
+ else: # MODEL entity
163
+ model_name = event_data.value_dict.get("model")
164
+ model_endpoint_id = event_data.value_dict.get("model_endpoint_id")
165
+ url = mlrun.utils.helpers.get_model_endpoint_url(
166
+ alert.project, model_name, model_endpoint_id
167
+ )
168
+ overview_type = "Model endpoint"
169
+
170
+ line.append(self._get_slack_row(f"*Overview:*\n<{url}|*{overview_type}*>"))
171
+
172
+ return line
173
+
99
174
  def _get_run_line(self, run: dict) -> dict:
100
175
  meta = run["metadata"]
101
176
  url = mlrun.utils.helpers.get_ui_url(meta.get("project"), meta.get("uid"))
102
- if url:
177
+
178
+ # Only show the URL if the run is not a function (serving or mlrun function)
179
+ kind = run.get("step_kind")
180
+ state = run["status"].get("state", "")
181
+ if state != "skipped" and (url and not kind or kind == "run"):
103
182
  line = f'<{url}|*{meta.get("name")}*>'
104
183
  else:
105
184
  line = meta.get("name")
106
- state = run["status"].get("state", "")
185
+ if kind:
186
+ line = f'{line} *({run.get("step_kind", run.get("kind", ""))})*'
107
187
  line = f'{self.emojis.get(state, ":question:")} {line}'
108
188
  return self._get_slack_row(line)
109
189
 
110
190
  def _get_run_result(self, run: dict) -> dict:
111
191
  state = run["status"].get("state", "")
112
192
  if state == "error":
113
- error_status = run["status"].get("error", "")
193
+ error_status = run["status"].get("error", "") or state
114
194
  result = f"*{error_status}*"
115
195
  else:
116
196
  result = mlrun.utils.helpers.dict_to_str(
117
197
  run["status"].get("results", {}), ", "
118
198
  )
119
- return self._get_slack_row(result or "None")
199
+ return self._get_slack_row(result or state)
120
200
 
121
201
  @staticmethod
122
202
  def _get_slack_row(text: str) -> dict:
@@ -36,6 +36,8 @@ class WebhookNotification(NotificationBase):
36
36
  ] = mlrun.common.schemas.NotificationSeverity.INFO,
37
37
  runs: typing.Union[mlrun.lists.RunList, list] = None,
38
38
  custom_html: str = None,
39
+ alert: mlrun.common.schemas.AlertConfig = None,
40
+ event_data: mlrun.common.schemas.Event = None,
39
41
  ):
40
42
  url = self.params.get("url", None)
41
43
  method = self.params.get("method", "post").lower()
@@ -46,9 +48,17 @@ class WebhookNotification(NotificationBase):
46
48
  request_body = {
47
49
  "message": message,
48
50
  "severity": severity,
49
- "runs": runs,
50
51
  }
51
52
 
53
+ if runs:
54
+ request_body["runs"] = runs
55
+
56
+ if alert:
57
+ request_body["alert"] = alert.dict()
58
+ if event_data:
59
+ request_body["value"] = event_data.value_dict
60
+ request_body["id"] = event_data.entity.ids[0]
61
+
52
62
  if custom_html:
53
63
  request_body["custom_html"] = custom_html
54
64