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
@@ -11,24 +11,50 @@
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, List, Tuple, Union
14
+
15
+ import functools
16
+ import sys
17
+ from typing import Callable, Union
16
18
 
17
19
  import numpy as np
18
20
  import plotly.graph_objects as go
19
21
  from plotly.subplots import make_subplots
20
22
 
21
23
  import mlrun.common.schemas.model_monitoring
22
- import mlrun.common.schemas.model_monitoring.constants as mm_constants
24
+ from mlrun.artifacts import PlotlyArtifact
23
25
 
24
26
  # A type for representing a drift result, a tuple of the status and the drift mean:
25
- DriftResultType = Tuple[mlrun.common.schemas.model_monitoring.DriftStatus, float]
27
+ DriftResultType = tuple[
28
+ mlrun.common.schemas.model_monitoring.constants.ResultStatusApp, float
29
+ ]
30
+
31
+
32
+ class _PlotlyTableArtifact(PlotlyArtifact):
33
+ """A custom class for plotly table artifacts"""
34
+
35
+ @staticmethod
36
+ def _disable_table_dragging(figure_html: str) -> str:
37
+ """
38
+ Disable the table columns dragging by adding the following
39
+ JavaScript code
40
+ """
41
+ start, end = figure_html.rsplit(";", 1)
42
+ middle = (
43
+ ';for (const element of document.getElementsByClassName("table")) '
44
+ '{element.style.pointerEvents = "none";}'
45
+ )
46
+ figure_html = start + middle + end
47
+ return figure_html
48
+
49
+ def get_body(self) -> str:
50
+ """Get the adjusted HTML representation of the figure"""
51
+ return self._disable_table_dragging(super().get_body())
26
52
 
27
53
 
28
54
  class FeaturesDriftTablePlot:
29
55
  """
30
56
  Class for producing a features drift table. The plot is a table with columns of all the statistics and metrics
31
- provided with two additional plot columns of the histograms and drift notification. The rows content will be drawn
57
+ provided with two additional plot columns of the histograms and drift status. The rows content will be drawn
32
58
  per feature.
33
59
 
34
60
  For example, if the statistics are 'mean', 'min', 'max' and one metric of 'tvd', for 3 features the table will be:
@@ -48,7 +74,7 @@ class FeaturesDriftTablePlot:
48
74
  70 # The width for the values of all the statistics and metrics columns.
49
75
  )
50
76
  _HISTOGRAMS_COLUMN_WIDTH = 180
51
- _NOTIFICATIONS_COLUMN_WIDTH = 20
77
+ _STATUS_COLUMN_WIDTH = 20
52
78
 
53
79
  # Table rows heights:
54
80
  _HEADER_ROW_HEIGHT = 25
@@ -57,12 +83,13 @@ class FeaturesDriftTablePlot:
57
83
  # Histograms configurations:
58
84
  _SAMPLE_SET_HISTOGRAM_COLOR = "rgb(0,112,192)" # Blue
59
85
  _INPUTS_HISTOGRAM_COLOR = "rgb(208,0,106)" # Magenta
86
+ _HISTOGRAM_OPACITY = 0.75
60
87
 
61
- # Notification configurations:
62
- _NOTIFICATION_COLORS = {
63
- mlrun.common.schemas.model_monitoring.DriftStatus.NO_DRIFT: "rgb(0,176,80)", # Green
64
- mlrun.common.schemas.model_monitoring.DriftStatus.POSSIBLE_DRIFT: "rgb(255,192,0)", # Orange
65
- mlrun.common.schemas.model_monitoring.DriftStatus.DRIFT_DETECTED: "rgb(208,0,106)", # Magenta
88
+ # Status configurations:
89
+ _STATUS_COLORS = {
90
+ mlrun.common.schemas.model_monitoring.constants.ResultStatusApp.no_detection: "rgb(0,176,80)", # Green
91
+ mlrun.common.schemas.model_monitoring.constants.ResultStatusApp.potential_detection: "rgb(255,192,0)", # Orange
92
+ mlrun.common.schemas.model_monitoring.constants.ResultStatusApp.detected: "rgb(208,0,106)", # Magenta
66
93
  }
67
94
 
68
95
  # Font configurations:
@@ -79,9 +106,6 @@ class FeaturesDriftTablePlot:
79
106
  _BACKGROUND_COLOR = "rgb(255,255,255)" # White
80
107
  _SEPARATORS_COLOR = "rgb(240,240,240)" # Light grey
81
108
 
82
- # File name:
83
- _FILE_NAME = "table_plot.html"
84
-
85
109
  def __init__(self):
86
110
  """
87
111
  Initialize the plot producer for later calling the `produce` method.
@@ -94,50 +118,29 @@ class FeaturesDriftTablePlot:
94
118
 
95
119
  def produce(
96
120
  self,
97
- features: List[str],
98
121
  sample_set_statistics: dict,
99
122
  inputs_statistics: dict,
100
- metrics: Dict[str, Union[dict, float]],
101
- drift_results: Dict[str, DriftResultType],
102
- ) -> str:
123
+ metrics: dict[str, Union[dict, float]],
124
+ drift_results: dict[str, DriftResultType],
125
+ ) -> _PlotlyTableArtifact:
103
126
  """
104
127
  Produce the html code of the table plot with the given information and the stored configurations in the class.
105
128
 
106
- :param features: List of all the features names to include in the table. These names expected to be
107
- in the statistics and metrics dictionaries.
108
129
  :param sample_set_statistics: The sample set calculated statistics dictionary.
109
130
  :param inputs_statistics: The inputs calculated statistics dictionary.
110
131
  :param metrics: The drift detection metrics calculated on the sample set and inputs.
111
132
  :param drift_results: The drift results per feature according to the rules of the monitor.
112
133
 
113
- :return: The full path to the html file of the plot.
134
+ :return: The drift table as a plotly artifact.
114
135
  """
115
- # Plot the drift table:
116
- features = [
117
- feature
118
- for feature in features
119
- if feature not in mm_constants.FeatureSetFeatures.list()
120
- ]
121
136
  figure = self._plot(
122
- features=features,
137
+ features=list(inputs_statistics.keys()),
123
138
  sample_set_statistics=sample_set_statistics,
124
139
  inputs_statistics=inputs_statistics,
125
140
  metrics=metrics,
126
141
  drift_results=drift_results,
127
142
  )
128
-
129
- # Get its HTML representation:
130
- figure_html = figure.to_html()
131
-
132
- # Turn off the table columns dragging by injecting the following JavaScript code:
133
- start, end = figure_html.rsplit(";", 1)
134
- middle = (
135
- ';for (const element of document.getElementsByClassName("table")) '
136
- '{element.style.pointerEvents = "none";}'
137
- )
138
- figure_html = start + middle + end
139
-
140
- return figure_html
143
+ return _PlotlyTableArtifact(figure=figure, key="drift_table_plot")
141
144
 
142
145
  def _read_columns_names(self, statistics_dictionary: dict, drift_metrics: dict):
143
146
  """
@@ -171,7 +174,7 @@ class FeaturesDriftTablePlot:
171
174
  self._metrics_columns
172
175
  )
173
176
 
174
- def _plot_headers_tables(self) -> Tuple[go.Table, go.Table]:
177
+ def _plot_headers_tables(self) -> tuple[go.Table, go.Table]:
175
178
  """
176
179
  Plot the headers of the table:
177
180
 
@@ -207,7 +210,7 @@ class FeaturesDriftTablePlot:
207
210
  self._FEATURE_NAME_COLUMN_WIDTH,
208
211
  *self._value_columns_widths,
209
212
  self._HISTOGRAMS_COLUMN_WIDTH,
210
- self._NOTIFICATIONS_COLUMN_WIDTH,
213
+ self._STATUS_COLUMN_WIDTH,
211
214
  ],
212
215
  header_fill_color=self._BACKGROUND_COLOR,
213
216
  )
@@ -231,14 +234,14 @@ class FeaturesDriftTablePlot:
231
234
  [self._FEATURE_NAME_COLUMN_WIDTH]
232
235
  + [self._VALUE_COLUMN_WIDTH]
233
236
  * (2 * len(self._statistics_columns) + len(self._metrics_columns))
234
- + [self._HISTOGRAMS_COLUMN_WIDTH, self._NOTIFICATIONS_COLUMN_WIDTH]
237
+ + [self._HISTOGRAMS_COLUMN_WIDTH, self._STATUS_COLUMN_WIDTH]
235
238
  ),
236
239
  header_fill_color=self._BACKGROUND_COLOR,
237
240
  )
238
241
 
239
242
  return header_table, sub_header_table
240
243
 
241
- def _separate_feature_name(self, feature_name: str) -> List[str]:
244
+ def _separate_feature_name(self, feature_name: str) -> list[str]:
242
245
  """
243
246
  Separate the given feature name by the maximum length configured in the class. Used for calculating the amount
244
247
  of lines required to represent the longest feature name in the table, so the row heights will fit accordingly.
@@ -299,15 +302,22 @@ class FeaturesDriftTablePlot:
299
302
  :return: The feature row - `Table` trace.
300
303
  """
301
304
  # Add '\n' to the feature name in order to make it fit into its cell:
302
- feature_name = "<br>".join(self._separate_feature_name(feature_name))
305
+ html_feature_name = "<br>".join(self._separate_feature_name(feature_name))
303
306
 
304
307
  # Initialize the cells values list with the bold feature name as the first value:
305
- cells_values = [f"<b>{feature_name}</b>"]
308
+ cells_values = [f"<b>{html_feature_name}</b>"]
306
309
 
307
310
  # Add the statistics columns:
308
311
  for column in self._statistics_columns:
309
312
  cells_values.append(sample_statistics[column])
310
- cells_values.append(input_statistics[column])
313
+ try:
314
+ cells_values.append(input_statistics[column])
315
+ except KeyError:
316
+ raise ValueError(
317
+ f"The `input_statistics['{feature_name}']` dictionary "
318
+ f"does not include the expected key '{column}'. "
319
+ "Please check the current data."
320
+ )
311
321
 
312
322
  # Add the metrics columns:
313
323
  for column in self._metrics_columns:
@@ -334,25 +344,25 @@ class FeaturesDriftTablePlot:
334
344
 
335
345
  return feature_row_table
336
346
 
337
- def _plot_histogram_scatters(
338
- self, sample_hist: Tuple[list, list], input_hist: Tuple[list, list]
339
- ) -> Tuple[go.Scatter, go.Scatter]:
347
+ def _plot_histogram_bars(
348
+ self,
349
+ figure_add_trace: Callable,
350
+ sample_hist: tuple[list, list],
351
+ input_hist: tuple[list, list],
352
+ showlegend: bool = False,
353
+ ) -> None:
340
354
  """
341
- Plot the feature's histograms to include in the "histograms" column. Both histograms are returned to later be
342
- added in the same figure, so they will be on top of each other and not separated. Both histograms are rescaled
355
+ Plot the feature's histograms to include in the "histograms" column. Both histograms are rescaled
343
356
  to be from 0.0 to 1.0, so they will be drawn in the same scale regardless the amount of elements they were
344
357
  calculated upon.
345
358
 
346
- :param sample_hist: The sample set histogram data.
347
- :param input_hist: The input histogram data.
359
+ :param figure_add_trace: The figure's method that get the histogram and adds it to the figure.
360
+ :param sample_hist: The sample set histogram data.
361
+ :param input_hist: The input histogram data.
362
+ :param showlegend: Show the legend for each histogram or not.
348
363
 
349
- :return: A tuple with both histograms - `Scatter` traces:
350
- [0] - Sample set histogram.
351
- [1] - Input histogram.
364
+ :return: None
352
365
  """
353
- # Initialize a list to collect the scatters:
354
- scatters = []
355
-
356
366
  # Plot the histograms:
357
367
  for name, color, histogram in zip(
358
368
  ["sample", "input"],
@@ -363,25 +373,31 @@ class FeaturesDriftTablePlot:
363
373
  counts, bins = histogram
364
374
  # Rescale the counts to be in percentages (between 0.0 to 1.0):
365
375
  counts = np.array(counts) / sum(counts)
376
+ hovertext = [""] * len(counts)
366
377
  # Convert to NumPy for vectorization:
367
378
  bins = np.array(bins)
379
+ if bins[0] == -sys.float_info.max:
380
+ bins[0] = bins[1] - (bins[2] - bins[1])
381
+ hovertext[0] = f"(-inf, {bins[1]})"
382
+ if bins[-1] == sys.float_info.max:
383
+ bins[-1] = bins[-2] + (bins[-2] - bins[-3])
384
+ hovertext[-1] = f"({bins[-2]}, inf)"
368
385
  # Center the bins (leave the first one):
369
386
  bins = 0.5 * (bins[:-1] + bins[1:])
370
387
  # Plot the histogram as a line with filled background below it:
371
- histogram_scatter = go.Scatter(
388
+ histogram_bar = go.Bar(
372
389
  x=bins,
373
390
  y=counts,
374
- fill="tozeroy",
375
391
  name=name,
376
- line_shape="spline", # Make the line rounder.
377
- line={"color": color},
392
+ marker_color=color,
393
+ opacity=self._HISTOGRAM_OPACITY,
378
394
  legendgroup=name,
395
+ hovertext=hovertext,
396
+ showlegend=showlegend,
379
397
  )
380
- scatters.append(histogram_scatter)
398
+ figure_add_trace(histogram_bar)
381
399
 
382
- return scatters[0], scatters[1]
383
-
384
- def _calculate_row_height(self, features: List[str]) -> int:
400
+ def _calculate_row_height(self, features: list[str]) -> int:
385
401
  """
386
402
  Calculate the feature row height according to the given features. The longest feature will set the height to all
387
403
  the rows. The height depends on the separations amount of the longest feature name - more '\n' means more pixels
@@ -401,7 +417,7 @@ class FeaturesDriftTablePlot:
401
417
  self._FEATURE_ROW_HEIGHT, 1.5 * self._FONT_SIZE * feature_name_seperations
402
418
  )
403
419
 
404
- def _plot_notification_circle(
420
+ def _plot_status_circle(
405
421
  self,
406
422
  figure: go.Figure,
407
423
  row: int,
@@ -409,8 +425,8 @@ class FeaturesDriftTablePlot:
409
425
  drift_result: DriftResultType,
410
426
  ):
411
427
  """
412
- Plot the drift notification - a little circle with color as configured in the class. The color will beb chosen
413
- according to the drift status given.
428
+ Plot the drift status - a little circle with color as configured in the
429
+ class. The color will be chosen according to the drift status given.
414
430
 
415
431
  :param figure: The figure (feature row cell) to draw the circle in.
416
432
  :param row: The row number.
@@ -422,12 +438,12 @@ class FeaturesDriftTablePlot:
422
438
  # row 3) times the plot columns (2 columns has axes in each row) + 2 (to get to the column of the notification):
423
439
  axis_number = (row - 3) * 2 + 2
424
440
  figure["layout"][f"xaxis{axis_number}"].update(
425
- range=[0, self._NOTIFICATIONS_COLUMN_WIDTH]
441
+ range=[0, self._STATUS_COLUMN_WIDTH]
426
442
  )
427
443
  figure["layout"][f"yaxis{axis_number}"].update(range=[0, row_height])
428
444
 
429
445
  # Get the color:
430
- notification_color = self._NOTIFICATION_COLORS[drift_result[0]]
446
+ notification_color = self._STATUS_COLORS[drift_result[0]]
431
447
  half_transparent_notification_color = notification_color.replace(
432
448
  "rgb", "rgba"
433
449
  ).replace(")", ",0.5)")
@@ -436,8 +452,8 @@ class FeaturesDriftTablePlot:
436
452
  # size of the text as well):
437
453
  y0 = 36 + (row_height - self._FEATURE_ROW_HEIGHT)
438
454
  y1 = y0 + self._FONT_SIZE
439
- x0 = (self._NOTIFICATIONS_COLUMN_WIDTH / 2) - ((y1 - y0) / 2)
440
- x1 = (self._NOTIFICATIONS_COLUMN_WIDTH / 2) + ((y1 - y0) / 2)
455
+ x0 = (self._STATUS_COLUMN_WIDTH / 2) - ((y1 - y0) / 2)
456
+ x1 = (self._STATUS_COLUMN_WIDTH / 2) + ((y1 - y0) / 2)
441
457
 
442
458
  # Draw the circle on top of the figure:
443
459
  figure.add_shape(
@@ -456,11 +472,11 @@ class FeaturesDriftTablePlot:
456
472
 
457
473
  def _plot(
458
474
  self,
459
- features: List[str],
475
+ features: list[str],
460
476
  sample_set_statistics: dict,
461
477
  inputs_statistics: dict,
462
- metrics: Dict[str, Union[dict, float]],
463
- drift_results: Dict[str, DriftResultType],
478
+ metrics: dict[str, Union[dict, float]],
479
+ drift_results: dict[str, DriftResultType],
464
480
  ) -> go.Figure:
465
481
  """
466
482
  Plot the drift table using the given data and stored configurations of the class.
@@ -488,7 +504,7 @@ class FeaturesDriftTablePlot:
488
504
  self._FEATURE_NAME_COLUMN_WIDTH
489
505
  + sum(self._value_columns_widths)
490
506
  + self._HISTOGRAMS_COLUMN_WIDTH
491
- + self._NOTIFICATIONS_COLUMN_WIDTH
507
+ + self._STATUS_COLUMN_WIDTH
492
508
  )
493
509
  height = 2 * self._HEADER_ROW_HEIGHT + len(features) * row_height
494
510
 
@@ -509,7 +525,7 @@ class FeaturesDriftTablePlot:
509
525
  (self._FEATURE_NAME_COLUMN_WIDTH + sum(self._value_columns_widths))
510
526
  / width,
511
527
  self._HISTOGRAMS_COLUMN_WIDTH / width,
512
- self._NOTIFICATIONS_COLUMN_WIDTH / width,
528
+ self._STATUS_COLUMN_WIDTH / width,
513
529
  ],
514
530
  horizontal_spacing=0,
515
531
  vertical_spacing=0,
@@ -520,39 +536,49 @@ class FeaturesDriftTablePlot:
520
536
  main_figure.add_trace(header_trace, row=1, col=1)
521
537
  main_figure.add_trace(sub_header_trace, row=2, col=1)
522
538
 
523
- # Start going over the features and plot each row, histogram and notification:
524
- row = 3 # We are currently at row 3 counting the headers.
525
- for feature in features:
526
- # Add the feature values:
527
- main_figure.add_trace(
528
- self._plot_feature_row_table(
529
- feature_name=feature,
530
- sample_statistics=sample_set_statistics[feature],
531
- input_statistics=inputs_statistics[feature],
532
- metrics=metrics[feature],
533
- row_height=row_height,
534
- ),
535
- row=row,
536
- col=1,
537
- )
539
+ # Start going over the features and plot each row, histogram and status
540
+ for row, feature in enumerate(
541
+ features,
542
+ start=3, # starting from row 3 after the headers
543
+ ):
544
+ try:
545
+ # Add the feature values:
546
+ main_figure.add_trace(
547
+ self._plot_feature_row_table(
548
+ feature_name=feature,
549
+ sample_statistics=sample_set_statistics[feature],
550
+ input_statistics=inputs_statistics[feature],
551
+ metrics=metrics[feature],
552
+ row_height=row_height,
553
+ ),
554
+ row=row,
555
+ col=1,
556
+ )
557
+ except KeyError:
558
+ raise ValueError(
559
+ "`sample_set_statistics` does not contain the expected "
560
+ f"key '{feature}' from `inputs_statistics`. Please verify "
561
+ "the data integrity.\n"
562
+ f"{sample_set_statistics.keys() = }\n"
563
+ f"{inputs_statistics.keys() = }\n"
564
+ )
538
565
  # Add the histograms (both traces are added to the same subplot figure):
539
- sample_hist, input_hist = self._plot_histogram_scatters(
566
+ self._plot_histogram_bars(
567
+ figure_add_trace=functools.partial(
568
+ main_figure.add_trace, row=row, col=2
569
+ ),
540
570
  sample_hist=sample_set_statistics[feature]["hist"],
541
571
  input_hist=inputs_statistics[feature]["hist"],
572
+ # Only the first row should have its legend visible
573
+ showlegend=(row == 3),
542
574
  )
543
- if row != 3: # Only the first row should have its legend visible:
544
- sample_hist.showlegend = False
545
- input_hist.showlegend = False
546
- main_figure.add_trace(sample_hist, row=row, col=2)
547
- main_figure.add_trace(input_hist, row=row, col=2)
548
- # Add the notification (a circle with color according to the drift alert):
549
- self._plot_notification_circle(
575
+ # Add the status (a circle with color according to the drift status)
576
+ self._plot_status_circle(
550
577
  figure=main_figure,
551
578
  row=row,
552
579
  row_height=row_height,
553
580
  drift_result=drift_results[feature],
554
581
  )
555
- row += 1
556
582
 
557
583
  # Configure the layout and axes for height and widths:
558
584
  main_figure.update_layout(
@@ -569,9 +595,11 @@ class FeaturesDriftTablePlot:
569
595
  "yanchor": "top",
570
596
  "y": 1.0 - (self._HEADER_ROW_HEIGHT / height) + 0.002,
571
597
  "xanchor": "right",
572
- "x": 1.0 - (self._NOTIFICATIONS_COLUMN_WIDTH / width) - 0.01,
598
+ "x": 1.0 - (self._STATUS_COLUMN_WIDTH / width) - 0.01,
573
599
  "bgcolor": "rgba(0,0,0,0)",
574
600
  },
601
+ barmode="overlay",
602
+ bargap=0,
575
603
  )
576
604
  main_figure.update_xaxes(
577
605
  showticklabels=False,