mlrun 1.7.2rc4__py3-none-any.whl → 1.8.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 (275) hide show
  1. mlrun/__init__.py +26 -22
  2. mlrun/__main__.py +15 -16
  3. mlrun/alerts/alert.py +150 -15
  4. mlrun/api/schemas/__init__.py +1 -9
  5. mlrun/artifacts/__init__.py +2 -3
  6. mlrun/artifacts/base.py +62 -19
  7. mlrun/artifacts/dataset.py +17 -17
  8. mlrun/artifacts/document.py +454 -0
  9. mlrun/artifacts/manager.py +28 -18
  10. mlrun/artifacts/model.py +91 -59
  11. mlrun/artifacts/plots.py +2 -2
  12. mlrun/common/constants.py +8 -0
  13. mlrun/common/formatters/__init__.py +1 -0
  14. mlrun/common/formatters/artifact.py +1 -1
  15. mlrun/common/formatters/feature_set.py +2 -0
  16. mlrun/common/formatters/function.py +1 -0
  17. mlrun/{model_monitoring/db/stores/v3io_kv/__init__.py → common/formatters/model_endpoint.py} +17 -0
  18. mlrun/common/formatters/pipeline.py +1 -2
  19. mlrun/common/formatters/project.py +9 -0
  20. mlrun/common/model_monitoring/__init__.py +0 -5
  21. mlrun/common/model_monitoring/helpers.py +12 -62
  22. mlrun/common/runtimes/constants.py +25 -4
  23. mlrun/common/schemas/__init__.py +9 -5
  24. mlrun/common/schemas/alert.py +114 -19
  25. mlrun/common/schemas/api_gateway.py +3 -3
  26. mlrun/common/schemas/artifact.py +22 -9
  27. mlrun/common/schemas/auth.py +8 -4
  28. mlrun/common/schemas/background_task.py +7 -7
  29. mlrun/common/schemas/client_spec.py +4 -4
  30. mlrun/common/schemas/clusterization_spec.py +2 -2
  31. mlrun/common/schemas/common.py +53 -3
  32. mlrun/common/schemas/constants.py +15 -0
  33. mlrun/common/schemas/datastore_profile.py +1 -1
  34. mlrun/common/schemas/feature_store.py +9 -9
  35. mlrun/common/schemas/frontend_spec.py +4 -4
  36. mlrun/common/schemas/function.py +10 -10
  37. mlrun/common/schemas/hub.py +1 -1
  38. mlrun/common/schemas/k8s.py +3 -3
  39. mlrun/common/schemas/memory_reports.py +3 -3
  40. mlrun/common/schemas/model_monitoring/__init__.py +4 -8
  41. mlrun/common/schemas/model_monitoring/constants.py +127 -46
  42. mlrun/common/schemas/model_monitoring/grafana.py +18 -12
  43. mlrun/common/schemas/model_monitoring/model_endpoints.py +154 -160
  44. mlrun/common/schemas/notification.py +24 -3
  45. mlrun/common/schemas/object.py +1 -1
  46. mlrun/common/schemas/pagination.py +4 -4
  47. mlrun/common/schemas/partition.py +142 -0
  48. mlrun/common/schemas/pipeline.py +3 -3
  49. mlrun/common/schemas/project.py +26 -18
  50. mlrun/common/schemas/runs.py +3 -3
  51. mlrun/common/schemas/runtime_resource.py +5 -5
  52. mlrun/common/schemas/schedule.py +1 -1
  53. mlrun/common/schemas/secret.py +1 -1
  54. mlrun/{model_monitoring/db/stores/sqldb/__init__.py → common/schemas/serving.py} +10 -1
  55. mlrun/common/schemas/tag.py +3 -3
  56. mlrun/common/schemas/workflow.py +6 -5
  57. mlrun/common/types.py +1 -0
  58. mlrun/config.py +157 -89
  59. mlrun/data_types/__init__.py +5 -3
  60. mlrun/data_types/infer.py +13 -3
  61. mlrun/data_types/spark.py +2 -1
  62. mlrun/datastore/__init__.py +59 -18
  63. mlrun/datastore/alibaba_oss.py +4 -1
  64. mlrun/datastore/azure_blob.py +4 -1
  65. mlrun/datastore/base.py +19 -24
  66. mlrun/datastore/datastore.py +10 -4
  67. mlrun/datastore/datastore_profile.py +178 -45
  68. mlrun/datastore/dbfs_store.py +4 -1
  69. mlrun/datastore/filestore.py +4 -1
  70. mlrun/datastore/google_cloud_storage.py +4 -1
  71. mlrun/datastore/hdfs.py +4 -1
  72. mlrun/datastore/inmem.py +4 -1
  73. mlrun/datastore/redis.py +4 -1
  74. mlrun/datastore/s3.py +14 -3
  75. mlrun/datastore/sources.py +89 -92
  76. mlrun/datastore/store_resources.py +7 -4
  77. mlrun/datastore/storeytargets.py +51 -16
  78. mlrun/datastore/targets.py +38 -31
  79. mlrun/datastore/utils.py +87 -4
  80. mlrun/datastore/v3io.py +4 -1
  81. mlrun/datastore/vectorstore.py +291 -0
  82. mlrun/datastore/wasbfs/fs.py +13 -12
  83. mlrun/db/base.py +286 -100
  84. mlrun/db/httpdb.py +1562 -490
  85. mlrun/db/nopdb.py +250 -83
  86. mlrun/errors.py +6 -2
  87. mlrun/execution.py +194 -50
  88. mlrun/feature_store/__init__.py +2 -10
  89. mlrun/feature_store/api.py +20 -458
  90. mlrun/feature_store/common.py +9 -9
  91. mlrun/feature_store/feature_set.py +20 -18
  92. mlrun/feature_store/feature_vector.py +105 -479
  93. mlrun/feature_store/feature_vector_utils.py +466 -0
  94. mlrun/feature_store/retrieval/base.py +15 -11
  95. mlrun/feature_store/retrieval/job.py +2 -1
  96. mlrun/feature_store/retrieval/storey_merger.py +1 -1
  97. mlrun/feature_store/steps.py +3 -3
  98. mlrun/features.py +30 -13
  99. mlrun/frameworks/__init__.py +1 -2
  100. mlrun/frameworks/_common/__init__.py +1 -2
  101. mlrun/frameworks/_common/artifacts_library.py +2 -2
  102. mlrun/frameworks/_common/mlrun_interface.py +10 -6
  103. mlrun/frameworks/_common/model_handler.py +31 -31
  104. mlrun/frameworks/_common/producer.py +3 -1
  105. mlrun/frameworks/_dl_common/__init__.py +1 -2
  106. mlrun/frameworks/_dl_common/loggers/__init__.py +1 -2
  107. mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +4 -4
  108. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +3 -3
  109. mlrun/frameworks/_ml_common/__init__.py +1 -2
  110. mlrun/frameworks/_ml_common/loggers/__init__.py +1 -2
  111. mlrun/frameworks/_ml_common/model_handler.py +21 -21
  112. mlrun/frameworks/_ml_common/plans/__init__.py +1 -2
  113. mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +3 -1
  114. mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
  115. mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
  116. mlrun/frameworks/auto_mlrun/__init__.py +1 -2
  117. mlrun/frameworks/auto_mlrun/auto_mlrun.py +22 -15
  118. mlrun/frameworks/huggingface/__init__.py +1 -2
  119. mlrun/frameworks/huggingface/model_server.py +9 -9
  120. mlrun/frameworks/lgbm/__init__.py +47 -44
  121. mlrun/frameworks/lgbm/callbacks/__init__.py +1 -2
  122. mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -2
  123. mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -2
  124. mlrun/frameworks/lgbm/mlrun_interfaces/__init__.py +1 -2
  125. mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +5 -5
  126. mlrun/frameworks/lgbm/model_handler.py +15 -11
  127. mlrun/frameworks/lgbm/model_server.py +11 -7
  128. mlrun/frameworks/lgbm/utils.py +2 -2
  129. mlrun/frameworks/onnx/__init__.py +1 -2
  130. mlrun/frameworks/onnx/dataset.py +3 -3
  131. mlrun/frameworks/onnx/mlrun_interface.py +2 -2
  132. mlrun/frameworks/onnx/model_handler.py +7 -5
  133. mlrun/frameworks/onnx/model_server.py +8 -6
  134. mlrun/frameworks/parallel_coordinates.py +11 -11
  135. mlrun/frameworks/pytorch/__init__.py +22 -23
  136. mlrun/frameworks/pytorch/callbacks/__init__.py +1 -2
  137. mlrun/frameworks/pytorch/callbacks/callback.py +2 -1
  138. mlrun/frameworks/pytorch/callbacks/logging_callback.py +15 -8
  139. mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +19 -12
  140. mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +22 -15
  141. mlrun/frameworks/pytorch/callbacks_handler.py +36 -30
  142. mlrun/frameworks/pytorch/mlrun_interface.py +17 -17
  143. mlrun/frameworks/pytorch/model_handler.py +21 -17
  144. mlrun/frameworks/pytorch/model_server.py +13 -9
  145. mlrun/frameworks/sklearn/__init__.py +19 -18
  146. mlrun/frameworks/sklearn/estimator.py +2 -2
  147. mlrun/frameworks/sklearn/metric.py +3 -3
  148. mlrun/frameworks/sklearn/metrics_library.py +8 -6
  149. mlrun/frameworks/sklearn/mlrun_interface.py +3 -2
  150. mlrun/frameworks/sklearn/model_handler.py +4 -3
  151. mlrun/frameworks/tf_keras/__init__.py +11 -12
  152. mlrun/frameworks/tf_keras/callbacks/__init__.py +1 -2
  153. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +17 -14
  154. mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +15 -12
  155. mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +21 -18
  156. mlrun/frameworks/tf_keras/model_handler.py +17 -13
  157. mlrun/frameworks/tf_keras/model_server.py +12 -8
  158. mlrun/frameworks/xgboost/__init__.py +19 -18
  159. mlrun/frameworks/xgboost/model_handler.py +13 -9
  160. mlrun/k8s_utils.py +2 -5
  161. mlrun/launcher/base.py +3 -4
  162. mlrun/launcher/client.py +2 -2
  163. mlrun/launcher/local.py +6 -2
  164. mlrun/launcher/remote.py +1 -1
  165. mlrun/lists.py +8 -4
  166. mlrun/model.py +132 -46
  167. mlrun/model_monitoring/__init__.py +3 -5
  168. mlrun/model_monitoring/api.py +113 -98
  169. mlrun/model_monitoring/applications/__init__.py +0 -5
  170. mlrun/model_monitoring/applications/_application_steps.py +81 -50
  171. mlrun/model_monitoring/applications/base.py +467 -14
  172. mlrun/model_monitoring/applications/context.py +212 -134
  173. mlrun/model_monitoring/{db/stores/base → applications/evidently}/__init__.py +6 -2
  174. mlrun/model_monitoring/applications/evidently/base.py +146 -0
  175. mlrun/model_monitoring/applications/histogram_data_drift.py +89 -56
  176. mlrun/model_monitoring/applications/results.py +67 -15
  177. mlrun/model_monitoring/controller.py +701 -315
  178. mlrun/model_monitoring/db/__init__.py +0 -2
  179. mlrun/model_monitoring/db/_schedules.py +242 -0
  180. mlrun/model_monitoring/db/_stats.py +189 -0
  181. mlrun/model_monitoring/db/tsdb/__init__.py +33 -22
  182. mlrun/model_monitoring/db/tsdb/base.py +243 -49
  183. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +76 -36
  184. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +33 -0
  185. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +213 -0
  186. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +534 -88
  187. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +1 -0
  188. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +436 -106
  189. mlrun/model_monitoring/helpers.py +356 -114
  190. mlrun/model_monitoring/stream_processing.py +190 -345
  191. mlrun/model_monitoring/tracking_policy.py +11 -4
  192. mlrun/model_monitoring/writer.py +49 -90
  193. mlrun/package/__init__.py +3 -6
  194. mlrun/package/context_handler.py +2 -2
  195. mlrun/package/packager.py +12 -9
  196. mlrun/package/packagers/__init__.py +0 -2
  197. mlrun/package/packagers/default_packager.py +14 -11
  198. mlrun/package/packagers/numpy_packagers.py +16 -7
  199. mlrun/package/packagers/pandas_packagers.py +18 -18
  200. mlrun/package/packagers/python_standard_library_packagers.py +25 -11
  201. mlrun/package/packagers_manager.py +35 -32
  202. mlrun/package/utils/__init__.py +0 -3
  203. mlrun/package/utils/_pickler.py +6 -6
  204. mlrun/platforms/__init__.py +47 -16
  205. mlrun/platforms/iguazio.py +4 -1
  206. mlrun/projects/operations.py +30 -30
  207. mlrun/projects/pipelines.py +116 -47
  208. mlrun/projects/project.py +1292 -329
  209. mlrun/render.py +5 -9
  210. mlrun/run.py +57 -14
  211. mlrun/runtimes/__init__.py +1 -3
  212. mlrun/runtimes/base.py +30 -22
  213. mlrun/runtimes/daskjob.py +9 -9
  214. mlrun/runtimes/databricks_job/databricks_runtime.py +6 -5
  215. mlrun/runtimes/function_reference.py +5 -2
  216. mlrun/runtimes/generators.py +3 -2
  217. mlrun/runtimes/kubejob.py +6 -7
  218. mlrun/runtimes/mounts.py +574 -0
  219. mlrun/runtimes/mpijob/__init__.py +0 -2
  220. mlrun/runtimes/mpijob/abstract.py +7 -6
  221. mlrun/runtimes/nuclio/api_gateway.py +7 -7
  222. mlrun/runtimes/nuclio/application/application.py +11 -13
  223. mlrun/runtimes/nuclio/application/reverse_proxy.go +66 -64
  224. mlrun/runtimes/nuclio/function.py +127 -70
  225. mlrun/runtimes/nuclio/serving.py +105 -37
  226. mlrun/runtimes/pod.py +159 -54
  227. mlrun/runtimes/remotesparkjob.py +3 -2
  228. mlrun/runtimes/sparkjob/__init__.py +0 -2
  229. mlrun/runtimes/sparkjob/spark3job.py +22 -12
  230. mlrun/runtimes/utils.py +7 -6
  231. mlrun/secrets.py +2 -2
  232. mlrun/serving/__init__.py +8 -0
  233. mlrun/serving/merger.py +7 -5
  234. mlrun/serving/remote.py +35 -22
  235. mlrun/serving/routers.py +186 -240
  236. mlrun/serving/server.py +41 -10
  237. mlrun/serving/states.py +432 -118
  238. mlrun/serving/utils.py +13 -2
  239. mlrun/serving/v1_serving.py +3 -2
  240. mlrun/serving/v2_serving.py +161 -203
  241. mlrun/track/__init__.py +1 -1
  242. mlrun/track/tracker.py +2 -2
  243. mlrun/track/trackers/mlflow_tracker.py +6 -5
  244. mlrun/utils/async_http.py +35 -22
  245. mlrun/utils/clones.py +7 -4
  246. mlrun/utils/helpers.py +511 -58
  247. mlrun/utils/logger.py +119 -13
  248. mlrun/utils/notifications/notification/__init__.py +22 -19
  249. mlrun/utils/notifications/notification/base.py +39 -15
  250. mlrun/utils/notifications/notification/console.py +6 -6
  251. mlrun/utils/notifications/notification/git.py +11 -11
  252. mlrun/utils/notifications/notification/ipython.py +10 -9
  253. mlrun/utils/notifications/notification/mail.py +176 -0
  254. mlrun/utils/notifications/notification/slack.py +16 -8
  255. mlrun/utils/notifications/notification/webhook.py +24 -8
  256. mlrun/utils/notifications/notification_pusher.py +191 -200
  257. mlrun/utils/regex.py +12 -2
  258. mlrun/utils/version/version.json +2 -2
  259. {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/METADATA +69 -54
  260. mlrun-1.8.0.dist-info/RECORD +351 -0
  261. {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/WHEEL +1 -1
  262. mlrun/model_monitoring/applications/evidently_base.py +0 -137
  263. mlrun/model_monitoring/db/stores/__init__.py +0 -136
  264. mlrun/model_monitoring/db/stores/base/store.py +0 -213
  265. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +0 -71
  266. mlrun/model_monitoring/db/stores/sqldb/models/base.py +0 -190
  267. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +0 -103
  268. mlrun/model_monitoring/db/stores/sqldb/models/sqlite.py +0 -40
  269. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +0 -659
  270. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +0 -726
  271. mlrun/model_monitoring/model_endpoint.py +0 -118
  272. mlrun-1.7.2rc4.dist-info/RECORD +0 -351
  273. {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/entry_points.txt +0 -0
  274. {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info/licenses}/LICENSE +0 -0
  275. {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/top_level.txt +0 -0
@@ -12,7 +12,5 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- from .stores import ObjectStoreFactory, get_store_object
16
- from .stores.base import StoreBase
17
15
  from .tsdb import get_tsdb_connector
18
16
  from .tsdb.base import TSDBConnector
@@ -0,0 +1,242 @@
1
+ # Copyright 2024 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 json
16
+ from abc import ABC, abstractmethod
17
+ from contextlib import AbstractContextManager
18
+ from types import TracebackType
19
+ from typing import Final, Optional
20
+
21
+ import botocore.exceptions
22
+
23
+ import mlrun.common.schemas as schemas
24
+ import mlrun.errors
25
+ import mlrun.model_monitoring.helpers
26
+ from mlrun.utils import logger
27
+
28
+
29
+ class ModelMonitoringSchedulesFileBase(AbstractContextManager, ABC):
30
+ DEFAULT_SCHEDULES: Final = {}
31
+ INITIAL_CONTENT = json.dumps(DEFAULT_SCHEDULES)
32
+ ENCODING = "utf-8"
33
+
34
+ def __init__(self):
35
+ self._item = self.get_data_item_object()
36
+ if self._item:
37
+ self._path = self._item.url
38
+ self._fs = self._item.store.filesystem
39
+ # `self._schedules` is an in-memory copy of the DB for all the applications for
40
+ # the same model endpoint.
41
+ self._schedules = self.DEFAULT_SCHEDULES.copy()
42
+ # Does `self._schedules` hold the content of `self._item`?
43
+ self._open_schedules = False
44
+
45
+ @abstractmethod
46
+ def get_data_item_object(self) -> mlrun.DataItem:
47
+ pass
48
+
49
+ def create(self) -> None:
50
+ """Create a schedules file with initial content - an empty dictionary"""
51
+ logger.debug("Creating model monitoring schedules file", path=self._item.url)
52
+ self._item.put(self.INITIAL_CONTENT)
53
+
54
+ def delete(self) -> None:
55
+ """Delete schedules file if it exists"""
56
+ if (
57
+ self._fs is None # In-memory store
58
+ or self._fs.exists(self._path)
59
+ ):
60
+ logger.debug(
61
+ "Deleting model monitoring schedules file", path=self._item.url
62
+ )
63
+ self._item.delete()
64
+ else:
65
+ logger.debug(
66
+ "Model monitoring schedules file does not exist, nothing to delete",
67
+ path=self._item.url,
68
+ )
69
+
70
+ def _open(self) -> None:
71
+ try:
72
+ content = self._item.get()
73
+ except (
74
+ mlrun.errors.MLRunNotFoundError,
75
+ # Different errors are raised for S3 or local storage, see ML-8042
76
+ botocore.exceptions.ClientError,
77
+ FileNotFoundError,
78
+ ) as err:
79
+ if (
80
+ isinstance(err, botocore.exceptions.ClientError)
81
+ # Add a log only to "NoSuchKey" errors codes - equivalent to `FileNotFoundError`
82
+ and err.response["Error"]["Code"] != "NoSuchKey"
83
+ ):
84
+ raise
85
+
86
+ logger.exception(
87
+ "The schedules file was not found. It should have been created "
88
+ "as a part of the model endpoint's creation",
89
+ path=self._path,
90
+ )
91
+ raise
92
+
93
+ if isinstance(content, bytes):
94
+ content = content.decode(encoding=self.ENCODING)
95
+ self._schedules = json.loads(content)
96
+ self._open_schedules = True
97
+
98
+ def _close(self) -> None:
99
+ self._item.put(json.dumps(self._schedules))
100
+ self._schedules = self.DEFAULT_SCHEDULES
101
+ self._open_schedules = False
102
+
103
+ def __enter__(self) -> "ModelMonitoringSchedulesFileBase":
104
+ self._open()
105
+ return super().__enter__()
106
+
107
+ def __exit__(
108
+ self,
109
+ exc_type: Optional[type[BaseException]],
110
+ exc_value: Optional[BaseException],
111
+ traceback: Optional[TracebackType],
112
+ ) -> Optional[bool]:
113
+ self._close()
114
+
115
+ def _check_open_schedules(self) -> None:
116
+ if not self._open_schedules:
117
+ raise mlrun.errors.MLRunValueError(
118
+ "Open the schedules file as a context manager first"
119
+ )
120
+
121
+
122
+ class ModelMonitoringSchedulesFileEndpoint(ModelMonitoringSchedulesFileBase):
123
+ def __init__(self, project: str, endpoint_id: str) -> None:
124
+ """
125
+ Initialize applications monitoring schedules file object.
126
+ The JSON file stores a dictionary of registered application name as key and Unix timestamp as value.
127
+ When working with the schedules data, use this class as a context manager to read and write the data.
128
+
129
+ :param project: The project name.
130
+ :param endpoint_id: The endpoint ID.
131
+ """
132
+ # `self._item` is the persistent version of the monitoring schedules.
133
+ self._project = project
134
+ self._endpoint_id = endpoint_id
135
+ super().__init__()
136
+
137
+ def get_data_item_object(self) -> mlrun.DataItem:
138
+ return mlrun.model_monitoring.helpers.get_monitoring_schedules_endpoint_data(
139
+ project=self._project, endpoint_id=self._endpoint_id
140
+ )
141
+
142
+ @classmethod
143
+ def from_model_endpoint(
144
+ cls, model_endpoint: schemas.ModelEndpoint
145
+ ) -> "ModelMonitoringSchedulesFileEndpoint":
146
+ return cls(
147
+ project=model_endpoint.metadata.project,
148
+ endpoint_id=model_endpoint.metadata.uid,
149
+ )
150
+
151
+ def get_application_time(self, application: str) -> Optional[int]:
152
+ self._check_open_schedules()
153
+ return self._schedules.get(application)
154
+
155
+ def update_application_time(self, application: str, timestamp: int) -> None:
156
+ self._check_open_schedules()
157
+ self._schedules[application] = timestamp
158
+
159
+ def get_application_list(self) -> set[str]:
160
+ self._check_open_schedules()
161
+ return set(self._schedules.keys())
162
+
163
+ def get_min_timestamp(self) -> Optional[int]:
164
+ self._check_open_schedules()
165
+ return min(self._schedules.values(), default=None)
166
+
167
+
168
+ class ModelMonitoringSchedulesFileChief(ModelMonitoringSchedulesFileBase):
169
+ def __init__(self, project: str) -> None:
170
+ """
171
+ Initialize applications monitoring schedules chief file object.
172
+ The JSON file stores a dictionary of registered model endpoints uid as key and point to a dictionary of
173
+ "last_request" and "last_analyzed" mapped to two Unix timestamps as values.
174
+ When working with the schedules data, use this class as a context manager to read and write the data.
175
+
176
+ :param project: The project name.
177
+ """
178
+ # `self._item` is the persistent version of the monitoring schedules.
179
+ self._project = project
180
+ super().__init__()
181
+
182
+ def get_data_item_object(self) -> mlrun.DataItem:
183
+ return mlrun.model_monitoring.helpers.get_monitoring_schedules_chief_data(
184
+ project=self._project
185
+ )
186
+
187
+ def get_endpoint_last_request(self, endpoint_uid: str) -> Optional[int]:
188
+ self._check_open_schedules()
189
+ if endpoint_uid in self._schedules:
190
+ return self._schedules[endpoint_uid].get(
191
+ schemas.model_monitoring.constants.ScheduleChiefFields.LAST_REQUEST
192
+ )
193
+ else:
194
+ return None
195
+
196
+ def update_endpoint_timestamps(
197
+ self, endpoint_uid: str, last_request: int, last_analyzed: int
198
+ ) -> None:
199
+ self._check_open_schedules()
200
+ self._schedules[endpoint_uid] = {
201
+ schemas.model_monitoring.constants.ScheduleChiefFields.LAST_REQUEST: last_request,
202
+ schemas.model_monitoring.constants.ScheduleChiefFields.LAST_ANALYZED: last_analyzed,
203
+ }
204
+
205
+ def get_endpoint_last_analyzed(self, endpoint_uid: str) -> Optional[int]:
206
+ self._check_open_schedules()
207
+ if endpoint_uid in self._schedules:
208
+ return self._schedules[endpoint_uid].get(
209
+ schemas.model_monitoring.constants.ScheduleChiefFields.LAST_ANALYZED
210
+ )
211
+ else:
212
+ return None
213
+
214
+ def get_endpoint_list(self) -> set[str]:
215
+ self._check_open_schedules()
216
+ return set(self._schedules.keys())
217
+
218
+ def get_or_create(self) -> None:
219
+ try:
220
+ self._open()
221
+ except (
222
+ mlrun.errors.MLRunNotFoundError,
223
+ # Different errors are raised for S3 or local storage, see ML-8042
224
+ botocore.exceptions.ClientError,
225
+ FileNotFoundError,
226
+ ):
227
+ self.create()
228
+
229
+
230
+ def delete_model_monitoring_schedules_folder(project: str) -> None:
231
+ """Delete the model monitoring schedules folder of the project"""
232
+ folder = mlrun.model_monitoring.helpers._get_monitoring_schedules_folder_path(
233
+ project
234
+ )
235
+ fs = mlrun.datastore.store_manager.object(folder).store.filesystem
236
+ if fs and fs.exists(folder):
237
+ logger.debug("Deleting model monitoring schedules folder", folder=folder)
238
+ fs.rm(folder, recursive=True)
239
+ elif fs is None: # In-memory store
240
+ raise mlrun.errors.MLRunValueError(
241
+ "Cannot delete a folder without a file-system"
242
+ )
@@ -0,0 +1,189 @@
1
+ # Copyright 2024 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
+ import abc
15
+ import json
16
+ from abc import abstractmethod
17
+ from datetime import datetime, timezone
18
+ from typing import cast
19
+
20
+ import botocore.exceptions
21
+ import fsspec
22
+
23
+ import mlrun.datastore.base
24
+ from mlrun.common.schemas.model_monitoring.constants import StatsKind
25
+ from mlrun.model_monitoring.helpers import (
26
+ get_monitoring_current_stats_data,
27
+ get_monitoring_drift_measures_data,
28
+ get_monitoring_stats_directory_path,
29
+ )
30
+ from mlrun.utils import logger
31
+
32
+
33
+ class ModelMonitoringStatsFile(abc.ABC):
34
+ """
35
+ Abstract class
36
+ Initialize applications monitoring stats file object.
37
+ The JSON file stores a dictionary of registered application name as key and Unix timestamp as value.
38
+ When working with the schedules data, use this class as a context manager to read and write the data.
39
+ """
40
+
41
+ def __init__(self, item: mlrun.datastore.base.DataItem, file_type: str):
42
+ self._path = item.url
43
+ self._item = item
44
+ self._file_type = file_type
45
+ self._fs = cast(fsspec.AbstractFileSystem, self._item.store.filesystem)
46
+
47
+ def create(self) -> None:
48
+ """Create a json file with initial content - an empty dictionary"""
49
+ logger.debug(
50
+ f"Creating model monitoring {self._file_type} file", path=self._item.url
51
+ )
52
+ self._item.put(
53
+ json.dumps(
54
+ {
55
+ "data": dict(),
56
+ "timestamp": mlrun.utils.datetime_now().isoformat(
57
+ sep=" ", timespec="microseconds"
58
+ ),
59
+ }
60
+ )
61
+ )
62
+
63
+ def delete(self) -> None:
64
+ """Delete json file if it exists"""
65
+ if self._fs.exists(self._path):
66
+ logger.debug(
67
+ f"Deleting model monitoring {self._file_type} file", path=self._item.url
68
+ )
69
+ self._item.delete()
70
+ else:
71
+ logger.debug(
72
+ f"Model monitoring {self._file_type} file does not exist, nothing to delete",
73
+ path=self._item.url,
74
+ )
75
+
76
+ def read(self) -> tuple[dict, datetime]:
77
+ """
78
+ Read the stats data and timestamp saved in file
79
+ :return: tuple[dict, str] dictionary with stats data and timestamp saved in file
80
+ """
81
+ try:
82
+ content = json.loads(self._item.get().decode())
83
+ timestamp = content.get("timestamp")
84
+ if timestamp is not None:
85
+ timestamp = datetime.fromisoformat(timestamp).astimezone(
86
+ tz=timezone.utc
87
+ )
88
+ return content.get("data"), timestamp
89
+ except (
90
+ mlrun.errors.MLRunNotFoundError,
91
+ # Different errors are raised for S3 or local storage, see ML-8042
92
+ botocore.exceptions.ClientError,
93
+ FileNotFoundError,
94
+ ) as err:
95
+ if (
96
+ isinstance(err, botocore.exceptions.ClientError)
97
+ # Add a log only to "NoSuchKey" errors codes - equivalent to `FileNotFoundError`
98
+ and err.response["Error"]["Code"] != "NoSuchKey"
99
+ ):
100
+ raise
101
+
102
+ logger.exception(
103
+ "The Stats file was not found. It should have been created "
104
+ "as a part of the model endpoint's creation",
105
+ path=self._path,
106
+ error=err,
107
+ )
108
+ raise
109
+
110
+ def write(self, stats: dict, timestamp: datetime) -> None:
111
+ """
112
+ Write stats data to file overwrite the existing file
113
+ :param stats: dictionary with the stats data
114
+ :param timestamp: datetime object with the timestamp of last entry point for the stats calculation
115
+ """
116
+ content = {
117
+ "data": stats,
118
+ "timestamp": timestamp.isoformat(sep=" ", timespec="microseconds"),
119
+ }
120
+ self._item.put(json.dumps(content))
121
+
122
+ @classmethod
123
+ @abstractmethod
124
+ def from_model_endpoint(
125
+ cls, model_endpoint: mlrun.common.schemas.ModelEndpoint
126
+ ) -> "ModelMonitoringStatsFile":
127
+ """
128
+ Return ModelMonitoringStatsFile child object using ModelEndpoint metadata
129
+ :param model_endpoint: The current model endpoint to get a stats object for
130
+ :return: ModelMonitoringStatsFile child object instance
131
+ """
132
+ pass
133
+
134
+
135
+ class ModelMonitoringCurrentStatsFile(ModelMonitoringStatsFile):
136
+ def __init__(self, project: str, endpoint_id: str) -> None:
137
+ """
138
+ Initialize File object specific for current stats.
139
+ :param project: (str) Project name
140
+ :param endpoint_id: (str) Endpoint name
141
+ """
142
+ super().__init__(
143
+ get_monitoring_current_stats_data(project, endpoint_id),
144
+ StatsKind.CURRENT_STATS.value,
145
+ )
146
+
147
+ @classmethod
148
+ def from_model_endpoint(
149
+ cls, model_endpoint: mlrun.common.schemas.ModelEndpoint
150
+ ) -> "ModelMonitoringCurrentStatsFile":
151
+ return cls(
152
+ project=model_endpoint.metadata.project,
153
+ endpoint_id=model_endpoint.metadata.uid,
154
+ )
155
+
156
+
157
+ class ModelMonitoringDriftMeasuresFile(ModelMonitoringStatsFile):
158
+ def __init__(self, project: str, endpoint_id: str) -> None:
159
+ """
160
+ Initialize File object specific for drift measures.
161
+ :param project: (str) Project name
162
+ :param endpoint_id: (str) Endpoint name
163
+ """
164
+ super().__init__(
165
+ get_monitoring_drift_measures_data(project, endpoint_id),
166
+ StatsKind.DRIFT_MEASURES.value,
167
+ )
168
+
169
+ @classmethod
170
+ def from_model_endpoint(
171
+ cls, model_endpoint: mlrun.common.schemas.ModelEndpoint
172
+ ) -> "ModelMonitoringDriftMeasuresFile":
173
+ return cls(
174
+ project=model_endpoint.metadata.project,
175
+ endpoint_id=model_endpoint.metadata.uid,
176
+ )
177
+
178
+
179
+ def delete_model_monitoring_stats_folder(project: str) -> None:
180
+ """Delete the model monitoring schedules folder of the project"""
181
+ folder = get_monitoring_stats_directory_path(project)
182
+ fs = mlrun.datastore.store_manager.object(folder).store.filesystem
183
+ if fs and fs.exists(folder):
184
+ logger.debug("Deleting model monitoring stats folder", folder=folder)
185
+ fs.rm(folder, recursive=True)
186
+ elif fs is None: # In-memory store
187
+ raise mlrun.errors.MLRunValueError(
188
+ "Cannot delete a folder without a file-system"
189
+ )
@@ -16,7 +16,10 @@ import enum
16
16
  import typing
17
17
 
18
18
  import mlrun.common.schemas.secret
19
+ import mlrun.datastore.datastore_profile
19
20
  import mlrun.errors
21
+ import mlrun.model_monitoring.helpers
22
+ from mlrun.datastore.datastore_profile import DatastoreProfile
20
23
 
21
24
  from .base import TSDBConnector
22
25
 
@@ -27,10 +30,13 @@ class ObjectTSDBFactory(enum.Enum):
27
30
  v3io_tsdb = "v3io-tsdb"
28
31
  tdengine = "tdengine"
29
32
 
30
- def to_tsdb_connector(self, project: str, **kwargs) -> TSDBConnector:
33
+ def to_tsdb_connector(
34
+ self, project: str, profile: DatastoreProfile, **kwargs
35
+ ) -> TSDBConnector:
31
36
  """
32
37
  Return a TSDBConnector object based on the provided enum value.
33
38
  :param project: The name of the project.
39
+ :param profile: Datastore profile containing DSN and credentials for TSDB connection
34
40
  :return: `TSDBConnector` object.
35
41
  """
36
42
 
@@ -49,7 +55,7 @@ class ObjectTSDBFactory(enum.Enum):
49
55
 
50
56
  from .tdengine.tdengine_connector import TDEngineConnector
51
57
 
52
- return TDEngineConnector(project=project, **kwargs)
58
+ return TDEngineConnector(project=project, profile=profile, **kwargs)
53
59
 
54
60
  @classmethod
55
61
  def _missing_(cls, value: typing.Any):
@@ -65,41 +71,46 @@ class ObjectTSDBFactory(enum.Enum):
65
71
  def get_tsdb_connector(
66
72
  project: str,
67
73
  secret_provider: typing.Optional[typing.Callable[[str], str]] = None,
68
- tsdb_connection_string: typing.Optional[str] = None,
69
- **kwargs,
74
+ profile: typing.Optional[mlrun.datastore.datastore_profile.DatastoreProfile] = None,
70
75
  ) -> TSDBConnector:
71
76
  """
72
77
  Get TSDB connector object.
73
78
  :param project: The name of the project.
74
79
  :param secret_provider: An optional secret provider to get the connection string secret.
75
- :param tsdb_connection_string: An optional explicit connection string to the TSDB.
80
+ :param profile: An optional profile to initialize the TSDB connector from.
76
81
 
77
- :return: `TSDBConnector` object. The main goal of this object is to handle different operations on the
82
+ :return: ``TSDBConnector`` object. The main goal of this object is to handle different operations on the
78
83
  TSDB connector such as updating drift metrics or write application record result.
79
- :raise: `MLRunInvalidMMStoreTypeError` if the user didn't provide TSDB connection
80
- or the provided TSDB connection is invalid.
84
+ :raise: ``MLRunNotFoundError`` if the user didn't set the TSDB datastore profile and didn't provide it through
85
+ the ``profile`` parameter.
86
+ :raise: ``MLRunInvalidMMStoreTypeError`` if the TSDB datastore profile is of an invalid type.
81
87
  """
82
-
83
- tsdb_connection_string = (
84
- tsdb_connection_string
85
- or mlrun.model_monitoring.helpers.get_tsdb_connection_string(
86
- secret_provider=secret_provider
87
- )
88
+ profile = profile or mlrun.model_monitoring.helpers._get_tsdb_profile(
89
+ project=project, secret_provider=secret_provider
88
90
  )
89
-
90
- if tsdb_connection_string and tsdb_connection_string.startswith("taosws"):
91
- tsdb_connector_type = mlrun.common.schemas.model_monitoring.TSDBTarget.TDEngine
92
- kwargs["connection_string"] = tsdb_connection_string
93
- elif tsdb_connection_string and tsdb_connection_string == "v3io":
91
+ kwargs = {}
92
+ if isinstance(profile, mlrun.datastore.datastore_profile.DatastoreProfileV3io):
94
93
  tsdb_connector_type = mlrun.common.schemas.model_monitoring.TSDBTarget.V3IO_TSDB
94
+ elif isinstance(
95
+ profile, mlrun.datastore.datastore_profile.DatastoreProfileTDEngine
96
+ ):
97
+ tsdb_connector_type = mlrun.common.schemas.model_monitoring.TSDBTarget.TDEngine
95
98
  else:
99
+ extra_message = (
100
+ ""
101
+ if profile
102
+ else " by using `project.set_model_monitoring_credentials` API"
103
+ )
96
104
  raise mlrun.errors.MLRunInvalidMMStoreTypeError(
97
- "You must provide a valid tsdb store connection by using "
98
- "set_model_monitoring_credentials API."
105
+ "You must provide a valid TSDB datastore profile"
106
+ f"{extra_message}. "
107
+ f"Found an unexpected profile of class: {type(profile)}"
99
108
  )
100
109
 
101
110
  # Get connector type value from ObjectTSDBFactory enum class
102
111
  tsdb_connector_factory = ObjectTSDBFactory(tsdb_connector_type)
103
112
 
104
113
  # Convert into TSDB connector object
105
- return tsdb_connector_factory.to_tsdb_connector(project=project, **kwargs)
114
+ return tsdb_connector_factory.to_tsdb_connector(
115
+ project=project, profile=profile, **kwargs
116
+ )