mlrun 1.7.0rc14__py3-none-any.whl → 1.7.0rc22__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 (160) hide show
  1. mlrun/__init__.py +10 -1
  2. mlrun/__main__.py +23 -111
  3. mlrun/alerts/__init__.py +15 -0
  4. mlrun/alerts/alert.py +169 -0
  5. mlrun/api/schemas/__init__.py +4 -3
  6. mlrun/artifacts/__init__.py +8 -3
  7. mlrun/artifacts/base.py +36 -253
  8. mlrun/artifacts/dataset.py +9 -190
  9. mlrun/artifacts/manager.py +46 -42
  10. mlrun/artifacts/model.py +9 -141
  11. mlrun/artifacts/plots.py +14 -375
  12. mlrun/common/constants.py +65 -3
  13. mlrun/common/formatters/__init__.py +19 -0
  14. mlrun/{runtimes/mpijob/v1alpha1.py → common/formatters/artifact.py} +6 -14
  15. mlrun/common/formatters/base.py +113 -0
  16. mlrun/common/formatters/function.py +46 -0
  17. mlrun/common/formatters/pipeline.py +53 -0
  18. mlrun/common/formatters/project.py +51 -0
  19. mlrun/{runtimes → common/runtimes}/constants.py +32 -4
  20. mlrun/common/schemas/__init__.py +10 -5
  21. mlrun/common/schemas/alert.py +92 -11
  22. mlrun/common/schemas/api_gateway.py +56 -0
  23. mlrun/common/schemas/artifact.py +15 -5
  24. mlrun/common/schemas/auth.py +2 -0
  25. mlrun/common/schemas/client_spec.py +1 -0
  26. mlrun/common/schemas/frontend_spec.py +1 -0
  27. mlrun/common/schemas/function.py +4 -0
  28. mlrun/common/schemas/model_monitoring/__init__.py +15 -3
  29. mlrun/common/schemas/model_monitoring/constants.py +58 -7
  30. mlrun/common/schemas/model_monitoring/grafana.py +9 -5
  31. mlrun/common/schemas/model_monitoring/model_endpoints.py +86 -2
  32. mlrun/common/schemas/pipeline.py +0 -9
  33. mlrun/common/schemas/project.py +5 -11
  34. mlrun/common/types.py +1 -0
  35. mlrun/config.py +30 -9
  36. mlrun/data_types/to_pandas.py +9 -9
  37. mlrun/datastore/base.py +41 -9
  38. mlrun/datastore/datastore.py +6 -2
  39. mlrun/datastore/datastore_profile.py +56 -4
  40. mlrun/datastore/inmem.py +2 -2
  41. mlrun/datastore/redis.py +2 -2
  42. mlrun/datastore/s3.py +5 -0
  43. mlrun/datastore/sources.py +147 -7
  44. mlrun/datastore/store_resources.py +7 -7
  45. mlrun/datastore/targets.py +110 -42
  46. mlrun/datastore/utils.py +42 -0
  47. mlrun/db/base.py +54 -10
  48. mlrun/db/httpdb.py +282 -79
  49. mlrun/db/nopdb.py +52 -10
  50. mlrun/errors.py +11 -0
  51. mlrun/execution.py +26 -9
  52. mlrun/feature_store/__init__.py +0 -2
  53. mlrun/feature_store/api.py +12 -47
  54. mlrun/feature_store/feature_set.py +9 -0
  55. mlrun/feature_store/feature_vector.py +8 -0
  56. mlrun/feature_store/ingestion.py +7 -6
  57. mlrun/feature_store/retrieval/base.py +9 -4
  58. mlrun/feature_store/retrieval/conversion.py +9 -9
  59. mlrun/feature_store/retrieval/dask_merger.py +2 -0
  60. mlrun/feature_store/retrieval/job.py +9 -3
  61. mlrun/feature_store/retrieval/local_merger.py +2 -0
  62. mlrun/feature_store/retrieval/spark_merger.py +16 -0
  63. mlrun/frameworks/__init__.py +6 -0
  64. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +7 -12
  65. mlrun/frameworks/parallel_coordinates.py +2 -1
  66. mlrun/frameworks/tf_keras/__init__.py +4 -1
  67. mlrun/k8s_utils.py +10 -11
  68. mlrun/launcher/base.py +4 -3
  69. mlrun/launcher/client.py +5 -3
  70. mlrun/launcher/local.py +12 -2
  71. mlrun/launcher/remote.py +9 -2
  72. mlrun/lists.py +6 -2
  73. mlrun/model.py +47 -21
  74. mlrun/model_monitoring/__init__.py +1 -1
  75. mlrun/model_monitoring/api.py +42 -18
  76. mlrun/model_monitoring/application.py +5 -305
  77. mlrun/model_monitoring/applications/__init__.py +11 -0
  78. mlrun/model_monitoring/applications/_application_steps.py +157 -0
  79. mlrun/model_monitoring/applications/base.py +280 -0
  80. mlrun/model_monitoring/applications/context.py +214 -0
  81. mlrun/model_monitoring/applications/evidently_base.py +211 -0
  82. mlrun/model_monitoring/applications/histogram_data_drift.py +132 -91
  83. mlrun/model_monitoring/applications/results.py +99 -0
  84. mlrun/model_monitoring/controller.py +3 -1
  85. mlrun/model_monitoring/db/__init__.py +2 -0
  86. mlrun/model_monitoring/db/stores/__init__.py +0 -2
  87. mlrun/model_monitoring/db/stores/base/store.py +22 -37
  88. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +43 -21
  89. mlrun/model_monitoring/db/stores/sqldb/models/base.py +39 -8
  90. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +27 -7
  91. mlrun/model_monitoring/db/stores/sqldb/models/sqlite.py +5 -0
  92. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +246 -224
  93. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +232 -216
  94. mlrun/model_monitoring/db/tsdb/__init__.py +100 -0
  95. mlrun/model_monitoring/db/tsdb/base.py +316 -0
  96. mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
  97. mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
  98. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +240 -0
  99. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +45 -0
  100. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +401 -0
  101. mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
  102. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +117 -0
  103. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +658 -0
  104. mlrun/model_monitoring/evidently_application.py +6 -118
  105. mlrun/model_monitoring/helpers.py +63 -1
  106. mlrun/model_monitoring/model_endpoint.py +3 -2
  107. mlrun/model_monitoring/stream_processing.py +57 -216
  108. mlrun/model_monitoring/writer.py +134 -124
  109. mlrun/package/__init__.py +13 -1
  110. mlrun/package/packagers/__init__.py +6 -1
  111. mlrun/package/utils/_formatter.py +2 -2
  112. mlrun/platforms/__init__.py +10 -9
  113. mlrun/platforms/iguazio.py +21 -202
  114. mlrun/projects/operations.py +24 -12
  115. mlrun/projects/pipelines.py +79 -102
  116. mlrun/projects/project.py +271 -103
  117. mlrun/render.py +15 -14
  118. mlrun/run.py +16 -46
  119. mlrun/runtimes/__init__.py +6 -3
  120. mlrun/runtimes/base.py +14 -7
  121. mlrun/runtimes/daskjob.py +1 -0
  122. mlrun/runtimes/databricks_job/databricks_runtime.py +1 -0
  123. mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
  124. mlrun/runtimes/funcdoc.py +0 -28
  125. mlrun/runtimes/kubejob.py +2 -1
  126. mlrun/runtimes/local.py +12 -3
  127. mlrun/runtimes/mpijob/__init__.py +0 -20
  128. mlrun/runtimes/mpijob/v1.py +1 -1
  129. mlrun/runtimes/nuclio/api_gateway.py +194 -84
  130. mlrun/runtimes/nuclio/application/application.py +170 -8
  131. mlrun/runtimes/nuclio/function.py +39 -49
  132. mlrun/runtimes/pod.py +16 -36
  133. mlrun/runtimes/remotesparkjob.py +9 -3
  134. mlrun/runtimes/sparkjob/spark3job.py +1 -1
  135. mlrun/runtimes/utils.py +6 -45
  136. mlrun/serving/__init__.py +8 -1
  137. mlrun/serving/server.py +2 -1
  138. mlrun/serving/states.py +51 -8
  139. mlrun/serving/utils.py +19 -11
  140. mlrun/serving/v2_serving.py +5 -1
  141. mlrun/track/tracker.py +2 -1
  142. mlrun/utils/async_http.py +25 -5
  143. mlrun/utils/helpers.py +157 -83
  144. mlrun/utils/logger.py +39 -7
  145. mlrun/utils/notifications/notification/__init__.py +14 -9
  146. mlrun/utils/notifications/notification/base.py +1 -1
  147. mlrun/utils/notifications/notification/slack.py +34 -7
  148. mlrun/utils/notifications/notification/webhook.py +1 -1
  149. mlrun/utils/notifications/notification_pusher.py +147 -16
  150. mlrun/utils/regex.py +9 -0
  151. mlrun/utils/v3io_clients.py +0 -1
  152. mlrun/utils/version/version.json +2 -2
  153. {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc22.dist-info}/METADATA +14 -6
  154. {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc22.dist-info}/RECORD +158 -138
  155. mlrun/kfpops.py +0 -865
  156. mlrun/platforms/other.py +0 -305
  157. {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc22.dist-info}/LICENSE +0 -0
  158. {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc22.dist-info}/WHEEL +0 -0
  159. {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc22.dist-info}/entry_points.txt +0 -0
  160. {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc22.dist-info}/top_level.txt +0 -0
@@ -12,35 +12,29 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- import datetime
16
15
  import json
17
16
  from typing import Any, NewType
18
17
 
19
- import pandas as pd
20
- from v3io.dataplane import Client as V3IOClient
21
- from v3io_frames.client import ClientBase as V3IOFramesClient
22
- from v3io_frames.errors import Error as V3IOFramesError
23
- from v3io_frames.frames_pb2 import IGNORE
24
-
25
18
  import mlrun.common.model_monitoring
26
- import mlrun.common.schemas.alert as alert_constants
19
+ import mlrun.common.schemas
20
+ import mlrun.common.schemas.alert as alert_objects
27
21
  import mlrun.model_monitoring
28
- import mlrun.model_monitoring.db.stores
29
- import mlrun.utils.v3io_clients
30
22
  from mlrun.common.schemas.model_monitoring.constants import (
31
23
  EventFieldType,
24
+ HistogramDataDriftApplicationConstants,
25
+ MetricData,
26
+ ResultData,
27
+ ResultKindApp,
32
28
  ResultStatusApp,
33
29
  WriterEvent,
30
+ WriterEventKind,
34
31
  )
35
32
  from mlrun.common.schemas.notification import NotificationKind, NotificationSeverity
36
- from mlrun.model_monitoring.helpers import get_endpoint_record
33
+ from mlrun.model_monitoring.helpers import get_endpoint_record, get_result_instance_fqn
37
34
  from mlrun.serving.utils import StepToDict
38
35
  from mlrun.utils import logger
39
36
  from mlrun.utils.notifications.notification_pusher import CustomNotificationPusher
40
37
 
41
- _TSDB_BE = "tsdb"
42
- _TSDB_RATE = "1/s"
43
- _TSDB_TABLE = "app-results"
44
38
  _RawEvent = dict[str, Any]
45
39
  _AppResultEvent = NewType("_AppResultEvent", _RawEvent)
46
40
 
@@ -75,20 +69,20 @@ class _Notifier:
75
69
  self._severity = severity
76
70
 
77
71
  def _should_send_event(self) -> bool:
78
- return self._event[WriterEvent.RESULT_STATUS] >= ResultStatusApp.detected
72
+ return self._event[ResultData.RESULT_STATUS] >= ResultStatusApp.detected.value
79
73
 
80
74
  def _generate_message(self) -> str:
81
75
  return f"""\
82
76
  The monitoring app `{self._event[WriterEvent.APPLICATION_NAME]}` \
83
- of kind `{self._event[WriterEvent.RESULT_KIND]}` \
77
+ of kind `{self._event[ResultData.RESULT_KIND]}` \
84
78
  detected a problem in model endpoint ID `{self._event[WriterEvent.ENDPOINT_ID]}` \
85
79
  at time `{self._event[WriterEvent.START_INFER_TIME]}`.
86
80
 
87
81
  Result data:
88
- Name: `{self._event[WriterEvent.RESULT_NAME]}`
89
- Value: `{self._event[WriterEvent.RESULT_VALUE]}`
90
- Status: `{self._event[WriterEvent.RESULT_STATUS]}`
91
- Extra data: `{self._event[WriterEvent.RESULT_EXTRA_DATA]}`\
82
+ Name: `{self._event[ResultData.RESULT_NAME]}`
83
+ Value: `{self._event[ResultData.RESULT_VALUE]}`
84
+ Status: `{self._event[ResultData.RESULT_STATUS]}`
85
+ Extra data: `{self._event[ResultData.RESULT_EXTRA_DATA]}`\
92
86
  """
93
87
 
94
88
  def notify(self) -> None:
@@ -103,136 +97,129 @@ Extra data: `{self._event[WriterEvent.RESULT_EXTRA_DATA]}`\
103
97
 
104
98
  class ModelMonitoringWriter(StepToDict):
105
99
  """
106
- Write monitoring app events to V3IO KV storage
100
+ Write monitoring application results to the target databases
107
101
  """
108
102
 
109
103
  kind = "monitoring_application_stream_pusher"
110
104
 
111
- def __init__(self, project: str) -> None:
105
+ def __init__(self, project: str, tsdb_secret_provider=None) -> None:
112
106
  self.project = project
113
107
  self.name = project # required for the deployment process
114
- self._v3io_container = self.get_v3io_container(self.name)
115
- self._tsdb_client = self._get_v3io_frames_client(self._v3io_container)
108
+
116
109
  self._custom_notifier = CustomNotificationPusher(
117
110
  notification_types=[NotificationKind.slack]
118
111
  )
119
- self._create_tsdb_table()
120
- self._endpoints_records = {}
121
112
 
122
- @staticmethod
123
- def get_v3io_container(project_name: str) -> str:
124
- return f"users/pipelines/{project_name}/monitoring-apps"
125
-
126
- @staticmethod
127
- def _get_v3io_client() -> V3IOClient:
128
- return mlrun.utils.v3io_clients.get_v3io_client(
129
- endpoint=mlrun.mlconf.v3io_api,
113
+ self._app_result_store = mlrun.model_monitoring.get_store_object(
114
+ project=self.project
130
115
  )
131
-
132
- @staticmethod
133
- def _get_v3io_frames_client(v3io_container: str) -> V3IOFramesClient:
134
- return mlrun.utils.v3io_clients.get_frames_client(
135
- address=mlrun.mlconf.v3io_framesd,
136
- container=v3io_container,
116
+ self._tsdb_connector = mlrun.model_monitoring.get_tsdb_connector(
117
+ project=self.project, secret_provider=tsdb_secret_provider
137
118
  )
119
+ self._endpoints_records = {}
138
120
 
139
- def _create_tsdb_table(self) -> None:
140
- self._tsdb_client.create(
141
- backend=_TSDB_BE,
142
- table=_TSDB_TABLE,
143
- if_exists=IGNORE,
144
- rate=_TSDB_RATE,
121
+ def _generate_event_on_drift(
122
+ self,
123
+ entity_id: str,
124
+ result_status: int,
125
+ event_value: dict,
126
+ project_name: str,
127
+ result_kind: int,
128
+ ) -> None:
129
+ logger.info("Sending an event")
130
+ entity = mlrun.common.schemas.alert.EventEntities(
131
+ kind=alert_objects.EventEntityKind.MODEL_ENDPOINT_RESULT,
132
+ project=project_name,
133
+ ids=[entity_id],
145
134
  )
146
135
 
147
- def _update_kv_db(self, event: _AppResultEvent) -> None:
148
- event = _AppResultEvent(event.copy())
149
- application_result_store = mlrun.model_monitoring.get_store_object(
150
- project=self.project
136
+ event_kind = self._generate_alert_event_kind(
137
+ result_status=result_status, result_kind=result_kind
151
138
  )
152
- application_result_store.write_application_result(event=event)
153
139
 
154
- def _update_tsdb(self, event: _AppResultEvent) -> None:
155
- event = _AppResultEvent(event.copy())
156
- event[WriterEvent.END_INFER_TIME] = datetime.datetime.fromisoformat(
157
- event[WriterEvent.END_INFER_TIME]
140
+ event_data = mlrun.common.schemas.Event(
141
+ kind=alert_objects.EventKind(value=event_kind),
142
+ entity=entity,
143
+ value_dict=event_value,
158
144
  )
159
- del event[WriterEvent.RESULT_EXTRA_DATA]
160
- try:
161
- self._tsdb_client.write(
162
- backend=_TSDB_BE,
163
- table=_TSDB_TABLE,
164
- dfs=pd.DataFrame.from_records([event]),
165
- index_cols=[
166
- WriterEvent.END_INFER_TIME,
167
- WriterEvent.ENDPOINT_ID,
168
- WriterEvent.APPLICATION_NAME,
169
- WriterEvent.RESULT_NAME,
170
- ],
171
- )
172
- logger.info("Updated V3IO TSDB successfully", table=_TSDB_TABLE)
173
- except V3IOFramesError as err:
174
- logger.warn(
175
- "Could not write drift measures to TSDB",
176
- err=err,
177
- table=_TSDB_TABLE,
178
- event=event,
179
- )
145
+ mlrun.get_run_db().generate_event(event_kind, event_data)
180
146
 
181
147
  @staticmethod
182
- def _generate_event_on_drift(
183
- uid: str, drift_status: str, event_value: dict, project_name: str
184
- ):
185
- if (
186
- drift_status == ResultStatusApp.detected
187
- or drift_status == ResultStatusApp.potential_detection
188
- ):
189
- entity = {
190
- "kind": alert_constants.EventEntityKind.MODEL,
191
- "project": project_name,
192
- "id": uid,
193
- }
194
- event_kind = (
195
- alert_constants.EventKind.DRIFT_DETECTED
196
- if drift_status == ResultStatusApp.detected
197
- else alert_constants.EventKind.DRIFT_SUSPECTED
198
- )
199
- event_data = mlrun.common.schemas.Event(
200
- kind=event_kind, entity=entity, value_dict=event_value
201
- )
202
- mlrun.get_run_db().generate_event(event_kind, event_data)
148
+ def _generate_alert_event_kind(
149
+ result_kind: int, result_status: int
150
+ ) -> alert_objects.EventKind:
151
+ """Generate the required Event Kind format for the alerting system"""
152
+ if result_kind == ResultKindApp.custom.value:
153
+ # Custom kind is represented as an anomaly detection
154
+ event_kind = "mm_app_anomaly"
155
+ else:
156
+ event_kind = ResultKindApp(value=result_kind).name
157
+
158
+ if result_status == ResultStatusApp.detected.value:
159
+ event_kind = f"{event_kind}_detected"
160
+ else:
161
+ event_kind = f"{event_kind}_suspected"
162
+ return alert_objects.EventKind(value=event_kind)
203
163
 
204
164
  @staticmethod
205
- def _reconstruct_event(event: _RawEvent) -> _AppResultEvent:
165
+ def _reconstruct_event(event: _RawEvent) -> tuple[_AppResultEvent, WriterEventKind]:
206
166
  """
207
167
  Modify the raw event into the expected monitoring application event
208
168
  schema as defined in `mlrun.common.schemas.model_monitoring.constants.WriterEvent`
209
169
  """
210
- try:
211
- result_event = _AppResultEvent(
212
- {key: event[key] for key in WriterEvent.list()}
170
+ if not isinstance(event, dict):
171
+ raise _WriterEventTypeError(
172
+ f"The event is of type: {type(event)}, expected a dictionary"
213
173
  )
214
- result_event[WriterEvent.CURRENT_STATS] = json.loads(
215
- event[WriterEvent.CURRENT_STATS]
174
+ kind = event.pop(WriterEvent.EVENT_KIND, WriterEventKind.RESULT)
175
+ result_event = _AppResultEvent(json.loads(event.pop(WriterEvent.DATA, "{}")))
176
+ if not result_event: # BC for < 1.7.0, can be removed in 1.9.0
177
+ result_event = _AppResultEvent(event)
178
+ else:
179
+ result_event.update(_AppResultEvent(event))
180
+
181
+ expected_keys = list(
182
+ set(WriterEvent.list()).difference(
183
+ [WriterEvent.EVENT_KIND, WriterEvent.DATA]
216
184
  )
217
- return result_event
218
- except KeyError as err:
185
+ )
186
+ if kind == WriterEventKind.METRIC:
187
+ expected_keys.extend(MetricData.list())
188
+ elif kind == WriterEventKind.RESULT:
189
+ expected_keys.extend(ResultData.list())
190
+ else:
219
191
  raise _WriterEventValueError(
220
- "The received event misses some keys compared to the expected "
221
- "monitoring application event schema"
222
- ) from err
223
- except TypeError as err:
224
- raise _WriterEventTypeError(
225
- f"The event is of type: {type(event)}, expected a dictionary"
226
- ) from err
192
+ f"Unknown event kind: {kind}, expected one of: {WriterEventKind.list()}"
193
+ )
194
+ missing_keys = [key for key in expected_keys if key not in result_event]
195
+ if missing_keys:
196
+ raise _WriterEventValueError(
197
+ f"The received event misses some keys compared to the expected "
198
+ f"monitoring application event schema: {missing_keys}"
199
+ )
200
+
201
+ return result_event, kind
227
202
 
228
203
  def do(self, event: _RawEvent) -> None:
229
- event = self._reconstruct_event(event)
204
+ event, kind = self._reconstruct_event(event)
230
205
  logger.info("Starting to write event", event=event)
231
- self._update_tsdb(event)
232
- self._update_kv_db(event)
233
- _Notifier(event=event, notification_pusher=self._custom_notifier).notify()
206
+ self._tsdb_connector.write_application_event(event=event.copy(), kind=kind)
207
+ self._app_result_store.write_application_event(event=event.copy(), kind=kind)
208
+
209
+ logger.info("Completed event DB writes")
234
210
 
235
- if mlrun.mlconf.alerts.mode == mlrun.common.schemas.alert.AlertsModes.enabled:
211
+ if kind == WriterEventKind.RESULT:
212
+ _Notifier(event=event, notification_pusher=self._custom_notifier).notify()
213
+
214
+ if (
215
+ mlrun.mlconf.alerts.mode == mlrun.common.schemas.alert.AlertsModes.enabled
216
+ and kind == WriterEventKind.RESULT
217
+ and (
218
+ event[ResultData.RESULT_STATUS] == ResultStatusApp.detected.value
219
+ or event[ResultData.RESULT_STATUS]
220
+ == ResultStatusApp.potential_detection.value
221
+ )
222
+ ):
236
223
  endpoint_id = event[WriterEvent.ENDPOINT_ID]
237
224
  endpoint_record = self._endpoints_records.setdefault(
238
225
  endpoint_id,
@@ -242,13 +229,36 @@ class ModelMonitoringWriter(StepToDict):
242
229
  "app_name": event[WriterEvent.APPLICATION_NAME],
243
230
  "model": endpoint_record.get(EventFieldType.MODEL),
244
231
  "model_endpoint_id": event[WriterEvent.ENDPOINT_ID],
245
- "result_name": event[WriterEvent.RESULT_NAME],
246
- "result_value": event[WriterEvent.RESULT_VALUE],
232
+ "result_name": event[ResultData.RESULT_NAME],
233
+ "result_value": event[ResultData.RESULT_VALUE],
247
234
  }
248
235
  self._generate_event_on_drift(
249
- event[WriterEvent.ENDPOINT_ID],
250
- event[WriterEvent.RESULT_STATUS],
251
- event_value,
252
- self.project,
236
+ entity_id=get_result_instance_fqn(
237
+ event[WriterEvent.ENDPOINT_ID],
238
+ event[WriterEvent.APPLICATION_NAME],
239
+ event[ResultData.RESULT_NAME],
240
+ ),
241
+ result_status=event[ResultData.RESULT_STATUS],
242
+ event_value=event_value,
243
+ project_name=self.project,
244
+ result_kind=event[ResultData.RESULT_KIND],
245
+ )
246
+
247
+ if (
248
+ kind == WriterEventKind.RESULT
249
+ and event[WriterEvent.APPLICATION_NAME]
250
+ == HistogramDataDriftApplicationConstants.NAME
251
+ and event[ResultData.RESULT_NAME]
252
+ == HistogramDataDriftApplicationConstants.GENERAL_RESULT_NAME
253
+ ):
254
+ endpoint_id = event[WriterEvent.ENDPOINT_ID]
255
+ logger.info(
256
+ "Updating the model endpoint with metadata specific to the histogram "
257
+ "data drift app",
258
+ endpoint_id=endpoint_id,
259
+ )
260
+ store = mlrun.model_monitoring.get_store_object(project=self.project)
261
+ store.update_model_endpoint(
262
+ endpoint_id=endpoint_id,
263
+ attributes=json.loads(event[ResultData.RESULT_EXTRA_DATA]),
253
264
  )
254
- logger.info("Completed event DB writes")
mlrun/package/__init__.py CHANGED
@@ -12,7 +12,19 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  #
15
-
15
+ """
16
+ MLRun package enables fully-automated experiment and pipeline tracking and reproducibility, and easy passing of
17
+ python objects between remote jobs, while not requiring any form of editing to the actual function original code.
18
+ Simply set the function code in a project and run it, MLRun takes care of the rest.
19
+
20
+ MLRun uses packagers: classes that perform 2 tasks:
21
+
22
+ #. **Parsing inputs** - automatically cast the runtime's inputs (user's input passed to the function via
23
+ the ``inputs`` parameter of the ``run`` method) to the relevant hinted type. (Does not require handling of data items.)
24
+ #. **Logging outputs** - automatically save, log, and upload the function's returned objects by the provided
25
+ log hints (user's input passed to the function via the ``returns`` parameter of the ``run`` method).
26
+ (Does not require handling of files and artifacts.)
27
+ """
16
28
  # flake8: noqa - this is until we take care of the F401 violations with respect to __all__ & sphinx
17
29
 
18
30
  import functools
@@ -12,7 +12,12 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  #
15
-
15
+ r"""
16
+ MLRun comes with the following list of modules, out of the box. All of the packagers listed here
17
+ use the implementation of :ref:`DefaultPackager <mlrun.package.packagers.default\_packager.DefaultPackager>` and are
18
+ available by default at the start of each run.
19
+ """
16
20
  # flake8: noqa - this is until we take care of the F401 violations with respect to __all__ & sphinx
21
+
17
22
  from .default_packager import DefaultPackager
18
23
  from .numpy_packagers import NumPySupportedFormat
@@ -142,11 +142,11 @@ class _YAMLFormatter(_Formatter):
142
142
 
143
143
  :param obj: The object to write.
144
144
  :param file_path: The file path to write to.
145
- :param dump_kwargs: Additional keyword arguments to pass to the `yaml.dump` method of the formatter in use.
145
+ :param dump_kwargs: Additional keyword arguments to pass to the `yaml.safe_dump` method of the formatter in use.
146
146
  """
147
147
  dump_kwargs = dump_kwargs or cls.DEFAULT_DUMP_KWARGS
148
148
  with open(file_path, "w") as file:
149
- yaml.dump(obj, file, **dump_kwargs)
149
+ yaml.safe_dump(obj, file, **dump_kwargs)
150
150
 
151
151
  @classmethod
152
152
  def read(cls, file_path: str) -> Union[list, dict]:
@@ -17,22 +17,23 @@ import json
17
17
  from pprint import pprint
18
18
  from time import sleep
19
19
 
20
- from .iguazio import (
21
- V3ioStreamClient,
22
- VolumeMount,
23
- add_or_refresh_credentials,
24
- is_iguazio_session_cookie,
25
- mount_v3io,
26
- v3io_cred,
27
- )
28
- from .other import (
20
+ from mlrun_pipelines.common.mounts import VolumeMount
21
+ from mlrun_pipelines.mounts import (
29
22
  auto_mount,
30
23
  mount_configmap,
31
24
  mount_hostpath,
32
25
  mount_pvc,
33
26
  mount_s3,
34
27
  mount_secret,
28
+ mount_v3io,
35
29
  set_env_variables,
30
+ v3io_cred,
31
+ )
32
+
33
+ from .iguazio import (
34
+ V3ioStreamClient,
35
+ add_or_refresh_credentials,
36
+ is_iguazio_session_cookie,
36
37
  )
37
38
 
38
39
 
@@ -15,12 +15,9 @@
15
15
  import json
16
16
  import os
17
17
  import urllib
18
- from collections import namedtuple
19
18
  from urllib.parse import urlparse
20
19
 
21
- import kfp.dsl
22
20
  import requests
23
- import semver
24
21
  import v3io
25
22
 
26
23
  import mlrun.errors
@@ -29,203 +26,6 @@ from mlrun.utils import dict_to_json
29
26
 
30
27
  _cached_control_session = None
31
28
 
32
- VolumeMount = namedtuple("Mount", ["path", "sub_path"])
33
-
34
-
35
- def mount_v3io(
36
- name="v3io",
37
- remote="",
38
- access_key="",
39
- user="",
40
- secret=None,
41
- volume_mounts=None,
42
- ):
43
- """Modifier function to apply to a Container Op to volume mount a v3io path
44
-
45
- :param name: the volume name
46
- :param remote: the v3io path to use for the volume. ~/ prefix will be replaced with /users/<username>/
47
- :param access_key: the access key used to auth against v3io. if not given V3IO_ACCESS_KEY env var will be used
48
- :param user: the username used to auth against v3io. if not given V3IO_USERNAME env var will be used
49
- :param secret: k8s secret name which would be used to get the username and access key to auth against v3io.
50
- :param volume_mounts: list of VolumeMount. empty volume mounts & remote will default to mount /v3io & /User.
51
- """
52
- volume_mounts, user = _enrich_and_validate_v3io_mounts(
53
- remote=remote,
54
- volume_mounts=volume_mounts,
55
- user=user,
56
- )
57
-
58
- def _attach_volume_mounts_and_creds(container_op: kfp.dsl.ContainerOp):
59
- from kubernetes import client as k8s_client
60
-
61
- vol = v3io_to_vol(name, remote, access_key, user, secret=secret)
62
- container_op.add_volume(vol)
63
- for volume_mount in volume_mounts:
64
- container_op.container.add_volume_mount(
65
- k8s_client.V1VolumeMount(
66
- mount_path=volume_mount.path,
67
- sub_path=volume_mount.sub_path,
68
- name=name,
69
- )
70
- )
71
-
72
- if not secret:
73
- container_op = v3io_cred(access_key=access_key, user=user)(container_op)
74
- return container_op
75
-
76
- return _attach_volume_mounts_and_creds
77
-
78
-
79
- def _enrich_and_validate_v3io_mounts(remote="", volume_mounts=None, user=""):
80
- if remote and not volume_mounts:
81
- raise mlrun.errors.MLRunInvalidArgumentError(
82
- "volume_mounts must be specified when remote is given"
83
- )
84
-
85
- # Empty remote & volume_mounts defaults are volume mounts of /v3io and /User
86
- if not remote and not volume_mounts:
87
- user = _resolve_mount_user(user)
88
- if not user:
89
- raise mlrun.errors.MLRunInvalidArgumentError(
90
- "user name/env must be specified when using empty remote and volume_mounts"
91
- )
92
- volume_mounts = [
93
- VolumeMount(path="/v3io", sub_path=""),
94
- VolumeMount(path="/User", sub_path="users/" + user),
95
- ]
96
-
97
- if not isinstance(volume_mounts, list) and any(
98
- [not isinstance(x, VolumeMount) for x in volume_mounts]
99
- ):
100
- raise TypeError("mounts should be a list of Mount")
101
-
102
- return volume_mounts, user
103
-
104
-
105
- def _resolve_mount_user(user=None):
106
- return user or os.environ.get("V3IO_USERNAME")
107
-
108
-
109
- def mount_spark_conf():
110
- def _mount_spark(container_op: kfp.dsl.ContainerOp):
111
- from kubernetes import client as k8s_client
112
-
113
- container_op.container.add_volume_mount(
114
- k8s_client.V1VolumeMount(
115
- name="spark-master-config", mount_path="/etc/config/spark"
116
- )
117
- )
118
- return container_op
119
-
120
- return _mount_spark
121
-
122
-
123
- def mount_v3iod(namespace, v3io_config_configmap):
124
- def _mount_v3iod(container_op: kfp.dsl.ContainerOp):
125
- from kubernetes import client as k8s_client
126
-
127
- def add_vol(name, mount_path, host_path):
128
- vol = k8s_client.V1Volume(
129
- name=name,
130
- host_path=k8s_client.V1HostPathVolumeSource(path=host_path, type=""),
131
- )
132
- container_op.add_volume(vol)
133
- container_op.container.add_volume_mount(
134
- k8s_client.V1VolumeMount(mount_path=mount_path, name=name)
135
- )
136
-
137
- # this is a legacy path for the daemon shared memory
138
- host_path = "/dev/shm/"
139
-
140
- # path to shared memory for daemon was changed in Iguazio 3.2.3-b1
141
- igz_version = mlrun.mlconf.get_parsed_igz_version()
142
- if igz_version and igz_version >= semver.VersionInfo.parse("3.2.3-b1"):
143
- host_path = "/var/run/iguazio/dayman-shm/"
144
- add_vol(name="shm", mount_path="/dev/shm", host_path=host_path + namespace)
145
-
146
- add_vol(
147
- name="v3iod-comm",
148
- mount_path="/var/run/iguazio/dayman",
149
- host_path="/var/run/iguazio/dayman/" + namespace,
150
- )
151
-
152
- vol = k8s_client.V1Volume(
153
- name="daemon-health", empty_dir=k8s_client.V1EmptyDirVolumeSource()
154
- )
155
- container_op.add_volume(vol)
156
- container_op.container.add_volume_mount(
157
- k8s_client.V1VolumeMount(
158
- mount_path="/var/run/iguazio/daemon_health", name="daemon-health"
159
- )
160
- )
161
-
162
- vol = k8s_client.V1Volume(
163
- name="v3io-config",
164
- config_map=k8s_client.V1ConfigMapVolumeSource(
165
- name=v3io_config_configmap, default_mode=420
166
- ),
167
- )
168
- container_op.add_volume(vol)
169
- container_op.container.add_volume_mount(
170
- k8s_client.V1VolumeMount(mount_path="/etc/config/v3io", name="v3io-config")
171
- )
172
-
173
- container_op.container.add_env_variable(
174
- k8s_client.V1EnvVar(
175
- name="CURRENT_NODE_IP",
176
- value_from=k8s_client.V1EnvVarSource(
177
- field_ref=k8s_client.V1ObjectFieldSelector(
178
- api_version="v1", field_path="status.hostIP"
179
- )
180
- ),
181
- )
182
- )
183
- container_op.container.add_env_variable(
184
- k8s_client.V1EnvVar(
185
- name="IGZ_DATA_CONFIG_FILE", value="/igz/java/conf/v3io.conf"
186
- )
187
- )
188
-
189
- return container_op
190
-
191
- return _mount_v3iod
192
-
193
-
194
- def v3io_cred(api="", user="", access_key=""):
195
- """
196
- Modifier function to copy local v3io env vars to container
197
-
198
- Usage::
199
-
200
- train = train_op(...)
201
- train.apply(use_v3io_cred())
202
- """
203
-
204
- def _use_v3io_cred(container_op: kfp.dsl.ContainerOp):
205
- from os import environ
206
-
207
- from kubernetes import client as k8s_client
208
-
209
- web_api = api or environ.get("V3IO_API") or mlconf.v3io_api
210
- _user = user or environ.get("V3IO_USERNAME")
211
- _access_key = access_key or environ.get("V3IO_ACCESS_KEY")
212
- v3io_framesd = mlconf.v3io_framesd or environ.get("V3IO_FRAMESD")
213
-
214
- return (
215
- container_op.container.add_env_variable(
216
- k8s_client.V1EnvVar(name="V3IO_API", value=web_api)
217
- )
218
- .add_env_variable(k8s_client.V1EnvVar(name="V3IO_USERNAME", value=_user))
219
- .add_env_variable(
220
- k8s_client.V1EnvVar(name="V3IO_ACCESS_KEY", value=_access_key)
221
- )
222
- .add_env_variable(
223
- k8s_client.V1EnvVar(name="V3IO_FRAMESD", value=v3io_framesd)
224
- )
225
- )
226
-
227
- return _use_v3io_cred
228
-
229
29
 
230
30
  def split_path(mntpath=""):
231
31
  if mntpath[0] == "/":
@@ -525,8 +325,8 @@ def add_or_refresh_credentials(
525
325
  # different access keys for the 2 usages
526
326
  token = (
527
327
  token
528
- # can't use mlrun.runtimes.constants.FunctionEnvironmentVariables.auth_session cause this is running in the
529
- # import execution path (when we're initializing the run db) and therefore we can't import mlrun.runtimes
328
+ # can't use mlrun.common.runtimes.constants.FunctionEnvironmentVariables.auth_session cause this is running
329
+ # in the import execution path (when we're initializing the run db) and therefore we can't import mlrun.runtimes
530
330
  or os.environ.get("MLRUN_AUTH_SESSION")
531
331
  or os.environ.get("V3IO_ACCESS_KEY")
532
332
  )
@@ -582,3 +382,22 @@ def sanitize_username(username: str):
582
382
  So simply replace it with dash
583
383
  """
584
384
  return username.replace("_", "-")
385
+
386
+
387
+ def min_iguazio_versions(*versions):
388
+ def decorator(function):
389
+ def wrapper(*args, **kwargs):
390
+ if mlrun.utils.helpers.validate_component_version_compatibility(
391
+ "iguazio", *versions
392
+ ):
393
+ return function(*args, **kwargs)
394
+
395
+ message = (
396
+ f"{function.__name__} is supported since Iguazio {' or '.join(versions)}, currently using "
397
+ f"Iguazio {mlconf.igz_version}."
398
+ )
399
+ raise mlrun.errors.MLRunIncompatibleVersionError(message)
400
+
401
+ return wrapper
402
+
403
+ return decorator