mlrun 1.4.0rc25__py3-none-any.whl → 1.5.0rc2__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 (184) hide show
  1. mlrun/__init__.py +2 -35
  2. mlrun/__main__.py +3 -41
  3. mlrun/api/api/api.py +6 -0
  4. mlrun/api/api/endpoints/feature_store.py +0 -4
  5. mlrun/api/api/endpoints/files.py +14 -2
  6. mlrun/api/api/endpoints/frontend_spec.py +2 -1
  7. mlrun/api/api/endpoints/functions.py +95 -59
  8. mlrun/api/api/endpoints/grafana_proxy.py +9 -9
  9. mlrun/api/api/endpoints/logs.py +17 -3
  10. mlrun/api/api/endpoints/model_endpoints.py +3 -2
  11. mlrun/api/api/endpoints/pipelines.py +1 -5
  12. mlrun/api/api/endpoints/projects.py +88 -0
  13. mlrun/api/api/endpoints/runs.py +48 -6
  14. mlrun/api/api/endpoints/submit.py +2 -1
  15. mlrun/api/api/endpoints/workflows.py +355 -0
  16. mlrun/api/api/utils.py +3 -4
  17. mlrun/api/crud/__init__.py +1 -0
  18. mlrun/api/crud/client_spec.py +6 -2
  19. mlrun/api/crud/feature_store.py +5 -0
  20. mlrun/api/crud/model_monitoring/__init__.py +1 -0
  21. mlrun/api/crud/model_monitoring/deployment.py +497 -0
  22. mlrun/api/crud/model_monitoring/grafana.py +96 -42
  23. mlrun/api/crud/model_monitoring/helpers.py +159 -0
  24. mlrun/api/crud/model_monitoring/model_endpoints.py +202 -476
  25. mlrun/api/crud/notifications.py +9 -4
  26. mlrun/api/crud/pipelines.py +6 -11
  27. mlrun/api/crud/projects.py +2 -2
  28. mlrun/api/crud/runtime_resources.py +4 -3
  29. mlrun/api/crud/runtimes/nuclio/helpers.py +5 -1
  30. mlrun/api/crud/secrets.py +21 -0
  31. mlrun/api/crud/workflows.py +352 -0
  32. mlrun/api/db/base.py +16 -1
  33. mlrun/api/db/init_db.py +2 -4
  34. mlrun/api/db/session.py +1 -1
  35. mlrun/api/db/sqldb/db.py +129 -31
  36. mlrun/api/db/sqldb/models/models_mysql.py +15 -1
  37. mlrun/api/db/sqldb/models/models_sqlite.py +16 -2
  38. mlrun/api/launcher.py +38 -6
  39. mlrun/api/main.py +3 -2
  40. mlrun/api/rundb/__init__.py +13 -0
  41. mlrun/{db → api/rundb}/sqldb.py +36 -84
  42. mlrun/api/runtime_handlers/__init__.py +56 -0
  43. mlrun/api/runtime_handlers/base.py +1247 -0
  44. mlrun/api/runtime_handlers/daskjob.py +209 -0
  45. mlrun/api/runtime_handlers/kubejob.py +37 -0
  46. mlrun/api/runtime_handlers/mpijob.py +147 -0
  47. mlrun/api/runtime_handlers/remotesparkjob.py +29 -0
  48. mlrun/api/runtime_handlers/sparkjob.py +148 -0
  49. mlrun/api/schemas/__init__.py +17 -6
  50. mlrun/api/utils/builder.py +1 -4
  51. mlrun/api/utils/clients/chief.py +14 -0
  52. mlrun/api/utils/clients/iguazio.py +33 -33
  53. mlrun/api/utils/clients/nuclio.py +2 -2
  54. mlrun/api/utils/periodic.py +9 -2
  55. mlrun/api/utils/projects/follower.py +14 -7
  56. mlrun/api/utils/projects/leader.py +2 -1
  57. mlrun/api/utils/projects/remotes/nop_follower.py +2 -2
  58. mlrun/api/utils/projects/remotes/nop_leader.py +2 -2
  59. mlrun/api/utils/runtimes/__init__.py +14 -0
  60. mlrun/api/utils/runtimes/nuclio.py +43 -0
  61. mlrun/api/utils/scheduler.py +98 -15
  62. mlrun/api/utils/singletons/db.py +5 -1
  63. mlrun/api/utils/singletons/project_member.py +4 -1
  64. mlrun/api/utils/singletons/scheduler.py +1 -1
  65. mlrun/artifacts/base.py +6 -6
  66. mlrun/artifacts/dataset.py +4 -4
  67. mlrun/artifacts/manager.py +2 -3
  68. mlrun/artifacts/model.py +2 -2
  69. mlrun/artifacts/plots.py +8 -8
  70. mlrun/common/db/__init__.py +14 -0
  71. mlrun/common/helpers.py +37 -0
  72. mlrun/{mlutils → common/model_monitoring}/__init__.py +3 -2
  73. mlrun/common/model_monitoring/helpers.py +69 -0
  74. mlrun/common/schemas/__init__.py +13 -1
  75. mlrun/common/schemas/auth.py +4 -1
  76. mlrun/common/schemas/client_spec.py +1 -1
  77. mlrun/common/schemas/function.py +17 -0
  78. mlrun/common/schemas/model_monitoring/__init__.py +48 -0
  79. mlrun/common/{model_monitoring.py → schemas/model_monitoring/constants.py} +11 -23
  80. mlrun/common/schemas/model_monitoring/grafana.py +55 -0
  81. mlrun/common/schemas/{model_endpoints.py → model_monitoring/model_endpoints.py} +32 -65
  82. mlrun/common/schemas/notification.py +1 -0
  83. mlrun/common/schemas/object.py +4 -0
  84. mlrun/common/schemas/project.py +1 -0
  85. mlrun/common/schemas/regex.py +1 -1
  86. mlrun/common/schemas/runs.py +1 -8
  87. mlrun/common/schemas/schedule.py +1 -8
  88. mlrun/common/schemas/workflow.py +54 -0
  89. mlrun/config.py +45 -42
  90. mlrun/datastore/__init__.py +21 -0
  91. mlrun/datastore/base.py +1 -1
  92. mlrun/datastore/datastore.py +9 -0
  93. mlrun/datastore/dbfs_store.py +168 -0
  94. mlrun/datastore/helpers.py +18 -0
  95. mlrun/datastore/sources.py +1 -0
  96. mlrun/datastore/store_resources.py +2 -5
  97. mlrun/datastore/v3io.py +1 -2
  98. mlrun/db/__init__.py +4 -68
  99. mlrun/db/base.py +12 -0
  100. mlrun/db/factory.py +65 -0
  101. mlrun/db/httpdb.py +175 -20
  102. mlrun/db/nopdb.py +4 -2
  103. mlrun/execution.py +4 -2
  104. mlrun/feature_store/__init__.py +1 -0
  105. mlrun/feature_store/api.py +1 -2
  106. mlrun/feature_store/common.py +2 -1
  107. mlrun/feature_store/feature_set.py +1 -11
  108. mlrun/feature_store/feature_vector.py +340 -2
  109. mlrun/feature_store/ingestion.py +5 -10
  110. mlrun/feature_store/retrieval/base.py +118 -104
  111. mlrun/feature_store/retrieval/dask_merger.py +17 -10
  112. mlrun/feature_store/retrieval/job.py +4 -1
  113. mlrun/feature_store/retrieval/local_merger.py +18 -18
  114. mlrun/feature_store/retrieval/spark_merger.py +21 -14
  115. mlrun/feature_store/retrieval/storey_merger.py +22 -16
  116. mlrun/kfpops.py +3 -9
  117. mlrun/launcher/base.py +57 -53
  118. mlrun/launcher/client.py +5 -4
  119. mlrun/launcher/factory.py +24 -13
  120. mlrun/launcher/local.py +6 -6
  121. mlrun/launcher/remote.py +4 -4
  122. mlrun/lists.py +0 -11
  123. mlrun/model.py +11 -17
  124. mlrun/model_monitoring/__init__.py +2 -22
  125. mlrun/model_monitoring/features_drift_table.py +1 -1
  126. mlrun/model_monitoring/helpers.py +22 -210
  127. mlrun/model_monitoring/model_endpoint.py +1 -1
  128. mlrun/model_monitoring/model_monitoring_batch.py +127 -50
  129. mlrun/model_monitoring/prometheus.py +219 -0
  130. mlrun/model_monitoring/stores/__init__.py +16 -11
  131. mlrun/model_monitoring/stores/kv_model_endpoint_store.py +95 -23
  132. mlrun/model_monitoring/stores/models/mysql.py +47 -29
  133. mlrun/model_monitoring/stores/models/sqlite.py +47 -29
  134. mlrun/model_monitoring/stores/sql_model_endpoint_store.py +31 -19
  135. mlrun/model_monitoring/{stream_processing_fs.py → stream_processing.py} +206 -64
  136. mlrun/model_monitoring/tracking_policy.py +104 -0
  137. mlrun/package/packager.py +6 -8
  138. mlrun/package/packagers/default_packager.py +121 -10
  139. mlrun/package/packagers/numpy_packagers.py +1 -1
  140. mlrun/platforms/__init__.py +0 -2
  141. mlrun/platforms/iguazio.py +0 -56
  142. mlrun/projects/pipelines.py +53 -159
  143. mlrun/projects/project.py +10 -37
  144. mlrun/render.py +1 -1
  145. mlrun/run.py +8 -124
  146. mlrun/runtimes/__init__.py +6 -42
  147. mlrun/runtimes/base.py +29 -1249
  148. mlrun/runtimes/daskjob.py +2 -198
  149. mlrun/runtimes/funcdoc.py +0 -9
  150. mlrun/runtimes/function.py +25 -29
  151. mlrun/runtimes/kubejob.py +5 -29
  152. mlrun/runtimes/local.py +1 -1
  153. mlrun/runtimes/mpijob/__init__.py +2 -2
  154. mlrun/runtimes/mpijob/abstract.py +10 -1
  155. mlrun/runtimes/mpijob/v1.py +0 -76
  156. mlrun/runtimes/mpijob/v1alpha1.py +1 -74
  157. mlrun/runtimes/nuclio.py +3 -2
  158. mlrun/runtimes/pod.py +28 -18
  159. mlrun/runtimes/remotesparkjob.py +1 -15
  160. mlrun/runtimes/serving.py +14 -6
  161. mlrun/runtimes/sparkjob/__init__.py +0 -1
  162. mlrun/runtimes/sparkjob/abstract.py +4 -131
  163. mlrun/runtimes/utils.py +0 -26
  164. mlrun/serving/routers.py +7 -7
  165. mlrun/serving/server.py +11 -8
  166. mlrun/serving/states.py +7 -1
  167. mlrun/serving/v2_serving.py +6 -6
  168. mlrun/utils/helpers.py +23 -42
  169. mlrun/utils/notifications/notification/__init__.py +4 -0
  170. mlrun/utils/notifications/notification/webhook.py +61 -0
  171. mlrun/utils/notifications/notification_pusher.py +5 -25
  172. mlrun/utils/regex.py +7 -2
  173. mlrun/utils/version/version.json +2 -2
  174. {mlrun-1.4.0rc25.dist-info → mlrun-1.5.0rc2.dist-info}/METADATA +26 -25
  175. {mlrun-1.4.0rc25.dist-info → mlrun-1.5.0rc2.dist-info}/RECORD +180 -158
  176. {mlrun-1.4.0rc25.dist-info → mlrun-1.5.0rc2.dist-info}/WHEEL +1 -1
  177. mlrun/mlutils/data.py +0 -160
  178. mlrun/mlutils/models.py +0 -78
  179. mlrun/mlutils/plots.py +0 -902
  180. mlrun/utils/model_monitoring.py +0 -249
  181. /mlrun/{api/db/sqldb/session.py → common/db/sql_session.py} +0 -0
  182. {mlrun-1.4.0rc25.dist-info → mlrun-1.5.0rc2.dist-info}/LICENSE +0 -0
  183. {mlrun-1.4.0rc25.dist-info → mlrun-1.5.0rc2.dist-info}/entry_points.txt +0 -0
  184. {mlrun-1.4.0rc25.dist-info → mlrun-1.5.0rc2.dist-info}/top_level.txt +0 -0
@@ -12,29 +12,22 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  #
15
- import json
15
+
16
16
  import os
17
17
  import typing
18
18
  import warnings
19
19
 
20
20
  import sqlalchemy.orm
21
21
 
22
- import mlrun.api.api.endpoints.functions
23
22
  import mlrun.api.api.utils
24
- import mlrun.api.crud.runtimes.nuclio.function
25
- import mlrun.api.utils.singletons.k8s
23
+ import mlrun.api.crud.model_monitoring.deployment
24
+ import mlrun.api.crud.model_monitoring.helpers
25
+ import mlrun.api.crud.secrets
26
+ import mlrun.api.rundb.sqldb
26
27
  import mlrun.artifacts
27
- import mlrun.common.model_monitoring as model_monitoring_constants
28
- import mlrun.common.schemas
29
- import mlrun.common.schemas.model_endpoints
30
- import mlrun.config
31
- import mlrun.datastore.store_resources
32
- import mlrun.errors
28
+ import mlrun.common.helpers
29
+ import mlrun.common.schemas.model_monitoring
33
30
  import mlrun.feature_store
34
- import mlrun.model_monitoring.helpers
35
- import mlrun.utils.helpers
36
- import mlrun.utils.model_monitoring
37
- import mlrun.utils.v3io_clients
38
31
  from mlrun.model_monitoring.stores import get_model_endpoint_store
39
32
  from mlrun.utils import logger
40
33
 
@@ -115,7 +108,8 @@ class ModelEndpoints:
115
108
  # Get labels from model object if not found in model endpoint object
116
109
  if not model_endpoint.spec.label_names and model_obj.spec.outputs:
117
110
  model_label_names = [
118
- self._clean_feature_name(f.name) for f in model_obj.spec.outputs
111
+ mlrun.api.crud.model_monitoring.helpers.clean_feature_name(f.name)
112
+ for f in model_obj.spec.outputs
119
113
  ]
120
114
  model_endpoint.spec.label_names = model_label_names
121
115
 
@@ -126,7 +120,7 @@ class ModelEndpoints:
126
120
  # Create monitoring feature set if monitoring found in model endpoint object
127
121
  if (
128
122
  model_endpoint.spec.monitoring_mode
129
- == mlrun.common.model_monitoring.ModelMonitoringMode.enabled.value
123
+ == mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled.value
130
124
  ):
131
125
  monitoring_feature_set = self.create_monitoring_feature_set(
132
126
  model_endpoint, model_obj, db_session, run_db
@@ -143,7 +137,7 @@ class ModelEndpoints:
143
137
  logger.info("Feature stats found, cleaning feature names")
144
138
  if model_endpoint.spec.feature_names:
145
139
  # Validate that the length of feature_stats is equal to the length of feature_names and label_names
146
- self._validate_length_features_and_labels(model_endpoint)
140
+ self._validate_length_features_and_labels(model_endpoint=model_endpoint)
147
141
 
148
142
  # Clean feature names in both feature_stats and feature_names
149
143
  (
@@ -163,6 +157,9 @@ class ModelEndpoints:
163
157
  # Write the new model endpoint
164
158
  model_endpoint_store = get_model_endpoint_store(
165
159
  project=model_endpoint.metadata.project,
160
+ secret_provider=mlrun.api.crud.secrets.get_project_secret_provider(
161
+ project=model_endpoint.metadata.project
162
+ ),
166
163
  )
167
164
  model_endpoint_store.write_model_endpoint(endpoint=model_endpoint.flat_dict())
168
165
 
@@ -170,12 +167,51 @@ class ModelEndpoints:
170
167
 
171
168
  return model_endpoint
172
169
 
173
- def create_monitoring_feature_set(
170
+ def patch_model_endpoint(
174
171
  self,
172
+ project: str,
173
+ endpoint_id: str,
174
+ attributes: dict,
175
+ ) -> mlrun.common.schemas.ModelEndpoint:
176
+ """
177
+ Update a model endpoint record with a given attributes.
178
+
179
+ :param project: The name of the project.
180
+ :param endpoint_id: The unique id of the model endpoint.
181
+ :param attributes: Dictionary of attributes that will be used for update the model endpoint. Note that the keys
182
+ of the attributes dictionary should exist in the DB table. More details about the model
183
+ endpoint available attributes can be found under
184
+ :py:class:`~mlrun.common.schemas.ModelEndpoint`.
185
+
186
+ :return: A patched `ModelEndpoint` object.
187
+ """
188
+
189
+ # Generate a model endpoint store object and apply the update process
190
+ model_endpoint_store = get_model_endpoint_store(
191
+ project=project,
192
+ secret_provider=mlrun.api.crud.secrets.get_project_secret_provider(
193
+ project=project
194
+ ),
195
+ )
196
+ model_endpoint_store.update_model_endpoint(
197
+ endpoint_id=endpoint_id, attributes=attributes
198
+ )
199
+
200
+ logger.info("Model endpoint table updated", endpoint_id=endpoint_id)
201
+
202
+ # Get the patched model endpoint record
203
+ model_endpoint_record = model_endpoint_store.get_model_endpoint(
204
+ endpoint_id=endpoint_id,
205
+ )
206
+
207
+ return self._convert_into_model_endpoint_object(endpoint=model_endpoint_record)
208
+
209
+ @staticmethod
210
+ def create_monitoring_feature_set(
175
211
  model_endpoint: mlrun.common.schemas.ModelEndpoint,
176
212
  model_obj: mlrun.artifacts.ModelArtifact,
177
213
  db_session: sqlalchemy.orm.Session,
178
- run_db: mlrun.db.sqldb.SQLDB,
214
+ run_db: mlrun.api.rundb.sqldb.SQLRunDB,
179
215
  ):
180
216
  """
181
217
  Create monitoring feature set with the relevant parquet target.
@@ -190,7 +226,12 @@ class ModelEndpoints:
190
226
  """
191
227
 
192
228
  # Define a new feature set
193
- _, serving_function_name, _, _ = mlrun.utils.helpers.parse_versioned_object_uri(
229
+ (
230
+ _,
231
+ serving_function_name,
232
+ _,
233
+ _,
234
+ ) = mlrun.common.helpers.parse_versioned_object_uri(
194
235
  model_endpoint.spec.function_uri
195
236
  )
196
237
 
@@ -198,15 +239,15 @@ class ModelEndpoints:
198
239
 
199
240
  feature_set = mlrun.feature_store.FeatureSet(
200
241
  f"monitoring-{serving_function_name}-{model_name}",
201
- entities=[model_monitoring_constants.EventFieldType.ENDPOINT_ID],
202
- timestamp_key=model_monitoring_constants.EventFieldType.TIMESTAMP,
242
+ entities=[mlrun.common.schemas.model_monitoring.EventFieldType.ENDPOINT_ID],
243
+ timestamp_key=mlrun.common.schemas.model_monitoring.EventFieldType.TIMESTAMP,
203
244
  description=f"Monitoring feature set for endpoint: {model_endpoint.spec.model}",
204
245
  )
205
246
  feature_set.metadata.project = model_endpoint.metadata.project
206
247
 
207
248
  feature_set.metadata.labels = {
208
- model_monitoring_constants.EventFieldType.ENDPOINT_ID: model_endpoint.metadata.uid,
209
- model_monitoring_constants.EventFieldType.MODEL_CLASS: model_endpoint.spec.model_class,
249
+ mlrun.common.schemas.model_monitoring.EventFieldType.ENDPOINT_ID: model_endpoint.metadata.uid,
250
+ mlrun.common.schemas.model_monitoring.EventFieldType.MODEL_CLASS: model_endpoint.spec.model_class,
210
251
  }
211
252
 
212
253
  # Add features to the feature set according to the model object
@@ -239,14 +280,14 @@ class ModelEndpoints:
239
280
 
240
281
  # Define parquet target for this feature set
241
282
  parquet_path = (
242
- self._get_monitoring_parquet_path(
283
+ mlrun.api.crud.model_monitoring.helpers.get_monitoring_parquet_path(
243
284
  db_session=db_session, project=model_endpoint.metadata.project
244
285
  )
245
286
  + f"/key={model_endpoint.metadata.uid}"
246
287
  )
247
288
 
248
289
  parquet_target = mlrun.datastore.targets.ParquetTarget(
249
- model_monitoring_constants.FileTargetKind.PARQUET, parquet_path
290
+ mlrun.common.schemas.model_monitoring.FileTargetKind.PARQUET, parquet_path
250
291
  )
251
292
  driver = mlrun.datastore.targets.get_target_driver(parquet_target, feature_set)
252
293
 
@@ -257,7 +298,6 @@ class ModelEndpoints:
257
298
  driver.update_resource_status("created")
258
299
 
259
300
  # Save the new feature set
260
- feature_set._override_run_db(db_session)
261
301
  feature_set.save()
262
302
  logger.info(
263
303
  "Monitoring feature set created",
@@ -267,125 +307,6 @@ class ModelEndpoints:
267
307
 
268
308
  return feature_set
269
309
 
270
- @staticmethod
271
- def _get_monitoring_parquet_path(
272
- db_session: sqlalchemy.orm.Session, project: str
273
- ) -> str:
274
- """Getting model monitoring parquet target for the current project. The parquet target path is based on the
275
- project artifact path. If project artifact path is not defined, the parquet target path will be based on MLRun
276
- artifact path.
277
-
278
- :param db_session: A session that manages the current dialog with the database. Will be used in this function
279
- to get the project record from DB.
280
- :param project: Project name.
281
-
282
- :return: Monitoring parquet target path.
283
- """
284
-
285
- # Get the artifact path from the project record that was stored in the DB
286
- project_obj = mlrun.api.crud.projects.Projects().get_project(
287
- session=db_session, name=project
288
- )
289
- artifact_path = project_obj.spec.artifact_path
290
- # Generate monitoring parquet path value
291
- parquet_path = mlrun.mlconf.get_model_monitoring_file_target_path(
292
- project=project,
293
- kind=model_monitoring_constants.FileTargetKind.PARQUET,
294
- target="offline",
295
- artifact_path=artifact_path,
296
- )
297
- return parquet_path
298
-
299
- @staticmethod
300
- def _validate_length_features_and_labels(model_endpoint):
301
- """
302
- Validate that the length of feature_stats is equal to the length of `feature_names` and `label_names`
303
-
304
- :param model_endpoint: An object representing the model endpoint.
305
- """
306
-
307
- # Getting the length of label names, feature_names and feature_stats
308
- len_of_label_names = (
309
- 0
310
- if not model_endpoint.spec.label_names
311
- else len(model_endpoint.spec.label_names)
312
- )
313
- len_of_feature_names = len(model_endpoint.spec.feature_names)
314
- len_of_feature_stats = len(model_endpoint.status.feature_stats)
315
-
316
- if len_of_feature_stats != len_of_feature_names + len_of_label_names:
317
- raise mlrun.errors.MLRunInvalidArgumentError(
318
- f"The length of model endpoint feature_stats is not equal to the "
319
- f"length of model endpoint feature names and labels "
320
- f"feature_stats({len_of_feature_stats}), "
321
- f"feature_names({len_of_feature_names}),"
322
- f"label_names({len_of_label_names}"
323
- )
324
-
325
- def _adjust_feature_names_and_stats(
326
- self, model_endpoint
327
- ) -> typing.Tuple[typing.Dict, typing.List]:
328
- """
329
- Create a clean matching version of feature names for both `feature_stats` and `feature_names`. Please note that
330
- label names exist only in `feature_stats` and `label_names`.
331
-
332
- :param model_endpoint: An object representing the model endpoint.
333
- :return: A tuple of:
334
- [0] = Dictionary of feature stats with cleaned names
335
- [1] = List of cleaned feature names
336
- """
337
- clean_feature_stats = {}
338
- clean_feature_names = []
339
- for i, (feature, stats) in enumerate(
340
- model_endpoint.status.feature_stats.items()
341
- ):
342
- clean_name = self._clean_feature_name(feature)
343
- clean_feature_stats[clean_name] = stats
344
- # Exclude the label columns from the feature names
345
- if (
346
- model_endpoint.spec.label_names
347
- and clean_name in model_endpoint.spec.label_names
348
- ):
349
- continue
350
- clean_feature_names.append(clean_name)
351
- return clean_feature_stats, clean_feature_names
352
-
353
- def patch_model_endpoint(
354
- self,
355
- project: str,
356
- endpoint_id: str,
357
- attributes: dict,
358
- ) -> mlrun.common.schemas.ModelEndpoint:
359
- """
360
- Update a model endpoint record with a given attributes.
361
-
362
- :param project: The name of the project.
363
- :param endpoint_id: The unique id of the model endpoint.
364
- :param attributes: Dictionary of attributes that will be used for update the model endpoint. Note that the keys
365
- of the attributes dictionary should exist in the DB table. More details about the model
366
- endpoint available attributes can be found under
367
- :py:class:`~mlrun.common.schemas.ModelEndpoint`.
368
-
369
- :return: A patched `ModelEndpoint` object.
370
- """
371
-
372
- # Generate a model endpoint store object and apply the update process
373
- model_endpoint_store = get_model_endpoint_store(
374
- project=project,
375
- )
376
- model_endpoint_store.update_model_endpoint(
377
- endpoint_id=endpoint_id, attributes=attributes
378
- )
379
-
380
- logger.info("Model endpoint table updated", endpoint_id=endpoint_id)
381
-
382
- # Get the patched model endpoint record
383
- model_endpoint_record = model_endpoint_store.get_model_endpoint(
384
- endpoint_id=endpoint_id,
385
- )
386
-
387
- return self._convert_into_model_endpoint_object(endpoint=model_endpoint_record)
388
-
389
310
  @staticmethod
390
311
  def delete_model_endpoint(
391
312
  project: str,
@@ -399,6 +320,9 @@ class ModelEndpoints:
399
320
  """
400
321
  model_endpoint_store = get_model_endpoint_store(
401
322
  project=project,
323
+ secret_provider=mlrun.api.crud.secrets.get_project_secret_provider(
324
+ project=project
325
+ ),
402
326
  )
403
327
 
404
328
  model_endpoint_store.delete_model_endpoint(endpoint_id=endpoint_id)
@@ -447,7 +371,11 @@ class ModelEndpoints:
447
371
 
448
372
  # Generate a model endpoint store object and get the model endpoint record as a dictionary
449
373
  model_endpoint_store = get_model_endpoint_store(
450
- project=project, access_key=auth_info.data_session
374
+ project=project,
375
+ access_key=auth_info.data_session,
376
+ secret_provider=mlrun.api.crud.secrets.get_project_secret_provider(
377
+ project=project
378
+ ),
451
379
  )
452
380
 
453
381
  model_endpoint_record = model_endpoint_store.get_model_endpoint(
@@ -536,13 +464,15 @@ class ModelEndpoints:
536
464
  )
537
465
 
538
466
  # Initialize an empty model endpoints list
539
- endpoint_list = mlrun.common.schemas.model_endpoints.ModelEndpointList(
540
- endpoints=[]
541
- )
467
+ endpoint_list = mlrun.common.schemas.ModelEndpointList(endpoints=[])
542
468
 
543
469
  # Generate a model endpoint store object and get a list of model endpoint dictionaries
544
470
  endpoint_store = get_model_endpoint_store(
545
- access_key=auth_info.data_session, project=project
471
+ access_key=auth_info.data_session,
472
+ project=project,
473
+ secret_provider=mlrun.api.crud.secrets.get_project_secret_provider(
474
+ project=project
475
+ ),
546
476
  )
547
477
 
548
478
  endpoint_dictionary_list = endpoint_store.list_model_endpoints(
@@ -554,7 +484,6 @@ class ModelEndpoints:
554
484
  )
555
485
 
556
486
  for endpoint_dict in endpoint_dictionary_list:
557
-
558
487
  # Convert to `ModelEndpoint` object
559
488
  endpoint_obj = self._convert_into_model_endpoint_object(
560
489
  endpoint=endpoint_dict
@@ -575,9 +504,116 @@ class ModelEndpoints:
575
504
 
576
505
  return endpoint_list
577
506
 
507
+ def verify_project_has_no_model_endpoints(self, project_name: str):
508
+ """Verify that there no model endpoint records in the DB by trying to list all of the project model endpoints.
509
+ This method is usually being used during the process of deleting a project.
510
+
511
+ :param project_name: project name.
512
+ """
513
+ auth_info = mlrun.common.schemas.AuthInfo(
514
+ data_session=os.getenv("V3IO_ACCESS_KEY")
515
+ )
516
+
517
+ if not mlrun.mlconf.igz_version or not mlrun.mlconf.v3io_api:
518
+ return
519
+
520
+ endpoints = self.list_model_endpoints(auth_info, project_name)
521
+ if endpoints.endpoints:
522
+ raise mlrun.errors.MLRunPreconditionFailedError(
523
+ f"Project {project_name} can not be deleted since related resources found: model endpoints"
524
+ )
525
+
526
+ @staticmethod
527
+ def delete_model_endpoints_resources(project_name: str):
528
+ """
529
+ Delete all model endpoints resources.
530
+
531
+ :param project_name: The name of the project.
532
+ """
533
+ auth_info = mlrun.common.schemas.AuthInfo(
534
+ data_session=os.getenv("V3IO_ACCESS_KEY")
535
+ )
536
+
537
+ # We would ideally base on config.v3io_api but can't for backwards compatibility reasons,
538
+ # we're using the igz version heuristic
539
+ if not mlrun.mlconf.igz_version or not mlrun.mlconf.v3io_api:
540
+ return
541
+
542
+ # Generate a model endpoint store object and get a list of model endpoint dictionaries
543
+ endpoint_store = get_model_endpoint_store(
544
+ access_key=auth_info.data_session,
545
+ project=project_name,
546
+ secret_provider=mlrun.api.crud.secrets.get_project_secret_provider(
547
+ project=project_name
548
+ ),
549
+ )
550
+ endpoints = endpoint_store.list_model_endpoints()
551
+
552
+ # Delete model endpoints resources from databases using the model endpoint store object
553
+ endpoint_store.delete_model_endpoints_resources(endpoints)
554
+
555
+ @staticmethod
556
+ def _validate_length_features_and_labels(
557
+ model_endpoint: mlrun.common.schemas.ModelEndpoint,
558
+ ):
559
+ """
560
+ Validate that the length of feature_stats is equal to the length of `feature_names` and `label_names`
561
+
562
+ :param model_endpoint: An object representing the model endpoint.
563
+ """
564
+
565
+ # Getting the length of label names, feature_names and feature_stats
566
+ len_of_label_names = (
567
+ 0
568
+ if not model_endpoint.spec.label_names
569
+ else len(model_endpoint.spec.label_names)
570
+ )
571
+ len_of_feature_names = len(model_endpoint.spec.feature_names)
572
+ len_of_feature_stats = len(model_endpoint.status.feature_stats)
573
+
574
+ if len_of_feature_stats != len_of_feature_names + len_of_label_names:
575
+ raise mlrun.errors.MLRunInvalidArgumentError(
576
+ f"The length of model endpoint feature_stats is not equal to the "
577
+ f"length of model endpoint feature names and labels "
578
+ f"feature_stats({len_of_feature_stats}), "
579
+ f"feature_names({len_of_feature_names}),"
580
+ f"label_names({len_of_label_names}"
581
+ )
582
+
583
+ @staticmethod
584
+ def _adjust_feature_names_and_stats(
585
+ model_endpoint,
586
+ ) -> typing.Tuple[typing.Dict, typing.List]:
587
+ """
588
+ Create a clean matching version of feature names for both `feature_stats` and `feature_names`. Please note that
589
+ label names exist only in `feature_stats` and `label_names`.
590
+
591
+ :param model_endpoint: An object representing the model endpoint.
592
+ :return: A tuple of:
593
+ [0] = Dictionary of feature stats with cleaned names
594
+ [1] = List of cleaned feature names
595
+ """
596
+ clean_feature_stats = {}
597
+ clean_feature_names = []
598
+ for i, (feature, stats) in enumerate(
599
+ model_endpoint.status.feature_stats.items()
600
+ ):
601
+ clean_name = mlrun.api.crud.model_monitoring.helpers.clean_feature_name(
602
+ feature
603
+ )
604
+ clean_feature_stats[clean_name] = stats
605
+ # Exclude the label columns from the feature names
606
+ if (
607
+ model_endpoint.spec.label_names
608
+ and clean_name in model_endpoint.spec.label_names
609
+ ):
610
+ continue
611
+ clean_feature_names.append(clean_name)
612
+ return clean_feature_stats, clean_feature_names
613
+
578
614
  @staticmethod
579
615
  def _add_real_time_metrics(
580
- model_endpoint_store: mlrun.model_monitoring.stores.ModelEndpointStore,
616
+ model_endpoint_store: mlrun.model_monitoring.ModelEndpointStore,
581
617
  model_endpoint_object: mlrun.common.schemas.ModelEndpoint,
582
618
  metrics: typing.List[str] = None,
583
619
  start: str = "now-1h",
@@ -616,22 +652,23 @@ class ModelEndpoints:
616
652
  )
617
653
  if endpoint_metrics:
618
654
  model_endpoint_object.status.metrics[
619
- model_monitoring_constants.EventKeyMetrics.REAL_TIME
655
+ mlrun.common.schemas.model_monitoring.EventKeyMetrics.REAL_TIME
620
656
  ] = endpoint_metrics
621
657
  return model_endpoint_object
622
658
 
659
+ @staticmethod
623
660
  def _convert_into_model_endpoint_object(
624
- self, endpoint: typing.Dict[str, typing.Any], feature_analysis: bool = False
661
+ endpoint: typing.Dict[str, typing.Any], feature_analysis: bool = False
625
662
  ) -> mlrun.common.schemas.ModelEndpoint:
626
663
  """
627
664
  Create a `ModelEndpoint` object according to a provided model endpoint dictionary.
628
665
 
629
- :param endpoint: Dictinoary that represents a DB record of a model endpoint which need to be converted
666
+ :param endpoint: Dictionary that represents a DB record of a model endpoint which need to be converted
630
667
  into a valid `ModelEndpoint` object.
631
668
  :param feature_analysis: When True, the base feature statistics and current feature statistics will be added to
632
669
  the output of the resulting object.
633
670
 
634
- :return: A `ModelEndpoint` object.
671
+ :return: A `~mlrun.common.schemas.ModelEndpoint` object.
635
672
  """
636
673
 
637
674
  # Convert into `ModelEndpoint` object
@@ -639,332 +676,21 @@ class ModelEndpoints:
639
676
 
640
677
  # If feature analysis was applied, add feature stats and current stats to the model endpoint result
641
678
  if feature_analysis and endpoint_obj.spec.feature_names:
642
-
643
- endpoint_features = self.get_endpoint_features(
644
- feature_names=endpoint_obj.spec.feature_names,
645
- feature_stats=endpoint_obj.status.feature_stats,
646
- current_stats=endpoint_obj.status.current_stats,
679
+ endpoint_features = (
680
+ mlrun.api.crud.model_monitoring.deployment.get_endpoint_features(
681
+ feature_names=endpoint_obj.spec.feature_names,
682
+ feature_stats=endpoint_obj.status.feature_stats,
683
+ current_stats=endpoint_obj.status.current_stats,
684
+ )
647
685
  )
648
686
  if endpoint_features:
649
687
  endpoint_obj.status.features = endpoint_features
650
688
  # Add the latest drift measures results (calculated by the model monitoring batch)
651
- drift_measures = self._json_loads_if_not_none(
689
+ drift_measures = mlrun.api.crud.model_monitoring.helpers.json_loads_if_not_none(
652
690
  endpoint.get(
653
- model_monitoring_constants.EventFieldType.DRIFT_MEASURES
691
+ mlrun.common.schemas.model_monitoring.EventFieldType.DRIFT_MEASURES
654
692
  )
655
693
  )
656
694
  endpoint_obj.status.drift_measures = drift_measures
657
695
 
658
696
  return endpoint_obj
659
-
660
- @staticmethod
661
- def get_endpoint_features(
662
- feature_names: typing.List[str],
663
- feature_stats: dict = None,
664
- current_stats: dict = None,
665
- ) -> typing.List[mlrun.common.schemas.Features]:
666
- """
667
- Getting a new list of features that exist in feature_names along with their expected (feature_stats) and
668
- actual (current_stats) stats. The expected stats were calculated during the creation of the model endpoint,
669
- usually based on the data from the Model Artifact. The actual stats are based on the results from the latest
670
- model monitoring batch job.
671
-
672
- param feature_names: List of feature names.
673
- param feature_stats: Dictionary of feature stats that were stored during the creation of the model endpoint
674
- object.
675
- param current_stats: Dictionary of the latest stats that were stored during the last run of the model monitoring
676
- batch job.
677
-
678
- return: List of feature objects. Each feature has a name, weight, expected values, and actual values. More info
679
- can be found under `mlrun.common.schemas.Features`.
680
- """
681
-
682
- # Initialize feature and current stats dictionaries
683
- safe_feature_stats = feature_stats or {}
684
- safe_current_stats = current_stats or {}
685
-
686
- # Create feature object and add it to a general features list
687
- features = []
688
- for name in feature_names:
689
- if feature_stats is not None and name not in feature_stats:
690
- logger.warn("Feature missing from 'feature_stats'", name=name)
691
- if current_stats is not None and name not in current_stats:
692
- logger.warn("Feature missing from 'current_stats'", name=name)
693
- f = mlrun.common.schemas.Features.new(
694
- name, safe_feature_stats.get(name), safe_current_stats.get(name)
695
- )
696
- features.append(f)
697
- return features
698
-
699
- @staticmethod
700
- def _json_loads_if_not_none(field: typing.Any) -> typing.Any:
701
- return (
702
- json.loads(field)
703
- if field and field != "null" and field is not None
704
- else None
705
- )
706
-
707
- def deploy_monitoring_functions(
708
- self,
709
- project: str,
710
- model_monitoring_access_key: str,
711
- db_session: sqlalchemy.orm.Session,
712
- auth_info: mlrun.common.schemas.AuthInfo,
713
- tracking_policy: mlrun.utils.model_monitoring.TrackingPolicy,
714
- ):
715
- """
716
- Invoking monitoring deploying functions.
717
-
718
- :param project: The name of the project.
719
- :param model_monitoring_access_key: Access key to apply the model monitoring process.
720
- :param db_session: A session that manages the current dialog with the database.
721
- :param auth_info: The auth info of the request.
722
- :param tracking_policy: Model monitoring configurations.
723
- """
724
- self.deploy_model_monitoring_stream_processing(
725
- project=project,
726
- model_monitoring_access_key=model_monitoring_access_key,
727
- db_session=db_session,
728
- auth_info=auth_info,
729
- tracking_policy=tracking_policy,
730
- )
731
- self.deploy_model_monitoring_batch_processing(
732
- project=project,
733
- model_monitoring_access_key=model_monitoring_access_key,
734
- db_session=db_session,
735
- auth_info=auth_info,
736
- tracking_policy=tracking_policy,
737
- )
738
-
739
- def verify_project_has_no_model_endpoints(self, project_name: str):
740
- auth_info = mlrun.common.schemas.AuthInfo(
741
- data_session=os.getenv("V3IO_ACCESS_KEY")
742
- )
743
-
744
- if not mlrun.mlconf.igz_version or not mlrun.mlconf.v3io_api:
745
- return
746
-
747
- endpoints = self.list_model_endpoints(auth_info, project_name)
748
- if endpoints.endpoints:
749
- raise mlrun.errors.MLRunPreconditionFailedError(
750
- f"Project {project_name} can not be deleted since related resources found: model endpoints"
751
- )
752
-
753
- @staticmethod
754
- def delete_model_endpoints_resources(project_name: str):
755
- """
756
- Delete all model endpoints resources.
757
-
758
- :param project_name: The name of the project.
759
- """
760
- auth_info = mlrun.common.schemas.AuthInfo(
761
- data_session=os.getenv("V3IO_ACCESS_KEY")
762
- )
763
-
764
- # We would ideally base on config.v3io_api but can't for backwards compatibility reasons,
765
- # we're using the igz version heuristic
766
- if not mlrun.mlconf.igz_version or not mlrun.mlconf.v3io_api:
767
- return
768
-
769
- # Generate a model endpoint store object and get a list of model endpoint dictionaries
770
- endpoint_store = get_model_endpoint_store(
771
- access_key=auth_info.data_session, project=project_name
772
- )
773
- endpoints = endpoint_store.list_model_endpoints()
774
-
775
- # Delete model endpoints resources from databases using the model endpoint store object
776
- endpoint_store.delete_model_endpoints_resources(endpoints)
777
-
778
- def deploy_model_monitoring_stream_processing(
779
- self,
780
- project: str,
781
- model_monitoring_access_key: str,
782
- db_session: sqlalchemy.orm.Session,
783
- auth_info: mlrun.common.schemas.AuthInfo,
784
- tracking_policy: mlrun.utils.model_monitoring.TrackingPolicy,
785
- ):
786
- """
787
- Deploying model monitoring stream real time nuclio function. The goal of this real time function is
788
- to monitor the log of the data stream. It is triggered when a new log entry is detected.
789
- It processes the new events into statistics that are then written to statistics databases.
790
-
791
- :param project: The name of the project.
792
- :param model_monitoring_access_key: Access key to apply the model monitoring process.
793
- :param db_session: A session that manages the current dialog with the database.
794
- :param auth_info: The auth info of the request.
795
- :param tracking_policy: Model monitoring configurations.
796
- """
797
-
798
- logger.info(
799
- "Checking if model monitoring stream is already deployed",
800
- project=project,
801
- )
802
- try:
803
- # validate that the model monitoring stream has not yet been deployed
804
- mlrun.api.crud.runtimes.nuclio.function.get_nuclio_deploy_status(
805
- name="model-monitoring-stream",
806
- project=project,
807
- tag="",
808
- auth_info=auth_info,
809
- )
810
- logger.info(
811
- "Detected model monitoring stream processing function already deployed",
812
- project=project,
813
- )
814
- return
815
- except mlrun.errors.MLRunNotFoundError:
816
- logger.info(
817
- "Deploying model monitoring stream processing function", project=project
818
- )
819
-
820
- # Get parquet target value for model monitoring stream function
821
- parquet_target = self._get_monitoring_parquet_path(
822
- db_session=db_session, project=project
823
- )
824
-
825
- fn = mlrun.model_monitoring.helpers.initial_model_monitoring_stream_processing_function(
826
- project=project,
827
- model_monitoring_access_key=model_monitoring_access_key,
828
- tracking_policy=tracking_policy,
829
- auth_info=auth_info,
830
- parquet_target=parquet_target,
831
- )
832
-
833
- mlrun.api.api.endpoints.functions._build_function(
834
- db_session=db_session, auth_info=auth_info, function=fn
835
- )
836
-
837
- def deploy_model_monitoring_batch_processing(
838
- self,
839
- project: str,
840
- model_monitoring_access_key: str,
841
- db_session: sqlalchemy.orm.Session,
842
- auth_info: mlrun.common.schemas.AuthInfo,
843
- tracking_policy: mlrun.utils.model_monitoring.TrackingPolicy,
844
- ):
845
- """
846
- Deploying model monitoring batch job. The goal of this job is to identify drift in the data
847
- based on the latest batch of events. By default, this job is executed on the hour every hour.
848
- Note that if the monitoring batch job was already deployed then you will have to delete the
849
- old monitoring batch job before deploying a new one.
850
-
851
- :param project: The name of the project.
852
- :param model_monitoring_access_key: Access key to apply the model monitoring process.
853
- :param db_session: A session that manages the current dialog with the database.
854
- :param auth_info: The auth info of the request.
855
- :param tracking_policy: Model monitoring configurations.
856
- """
857
-
858
- logger.info(
859
- "Checking if model monitoring batch processing function is already deployed",
860
- project=project,
861
- )
862
-
863
- # Try to list functions that named model monitoring batch
864
- # to make sure that this job has not yet been deployed
865
- function_list = mlrun.api.utils.singletons.db.get_db().list_functions(
866
- session=db_session, name="model-monitoring-batch", project=project
867
- )
868
-
869
- if function_list:
870
- logger.info(
871
- "Detected model monitoring batch processing function already deployed",
872
- project=project,
873
- )
874
- return
875
-
876
- # Create a monitoring batch job function object
877
- fn = mlrun.model_monitoring.helpers.get_model_monitoring_batch_function(
878
- project=project,
879
- model_monitoring_access_key=model_monitoring_access_key,
880
- db_session=db_session,
881
- auth_info=auth_info,
882
- tracking_policy=tracking_policy,
883
- )
884
-
885
- # Get the function uri
886
- function_uri = fn.save(versioned=True)
887
- function_uri = function_uri.replace("db://", "")
888
-
889
- task = mlrun.new_task(name="model-monitoring-batch", project=project)
890
- task.spec.function = function_uri
891
-
892
- # Apply batching interval params
893
- interval_list = [
894
- tracking_policy.default_batch_intervals.minute,
895
- tracking_policy.default_batch_intervals.hour,
896
- tracking_policy.default_batch_intervals.day,
897
- ]
898
- minutes, hours, days = self._get_batching_interval_param(interval_list)
899
- batch_dict = {"minutes": minutes, "hours": hours, "days": days}
900
-
901
- task.spec.parameters[
902
- model_monitoring_constants.EventFieldType.BATCH_INTERVALS_DICT
903
- ] = batch_dict
904
-
905
- data = {
906
- "task": task.to_dict(),
907
- "schedule": self._convert_to_cron_string(
908
- tracking_policy.default_batch_intervals
909
- ),
910
- }
911
-
912
- logger.info(
913
- "Deploying model monitoring batch processing function", project=project
914
- )
915
-
916
- # Add job schedule policy (every hour by default)
917
- mlrun.api.api.utils.submit_run_sync(
918
- db_session=db_session, auth_info=auth_info, data=data
919
- )
920
-
921
- @staticmethod
922
- def _clean_feature_name(feature_name):
923
- return feature_name.replace(" ", "_").replace("(", "").replace(")", "")
924
-
925
- @staticmethod
926
- def get_access_key(auth_info: mlrun.common.schemas.AuthInfo):
927
- """
928
- Getting access key from the current data session. This method is usually used to verify that the session
929
- is valid and contains an access key.
930
-
931
- param auth_info: The auth info of the request.
932
-
933
- :return: Access key as a string.
934
- """
935
- access_key = auth_info.data_session
936
- if not access_key:
937
- raise mlrun.errors.MLRunBadRequestError("Data session is missing")
938
- return access_key
939
-
940
- @staticmethod
941
- def _get_batching_interval_param(intervals_list: typing.List):
942
- """Converting each value in the intervals list into a float number. None
943
- Values will be converted into 0.0.
944
-
945
- param intervals_list: A list of values based on the ScheduleCronTrigger expression. Note that at the moment
946
- it supports minutes, hours, and days. e.g. [0, '*/1', None] represents on the hour
947
- every hour.
948
-
949
- :return: A tuple of:
950
- [0] = minutes interval as a float
951
- [1] = hours interval as a float
952
- [2] = days interval as a float
953
- """
954
- return tuple(
955
- [
956
- 0.0
957
- if isinstance(interval, (float, int)) or interval is None
958
- else float(f"0{interval.partition('/')[-1]}")
959
- for interval in intervals_list
960
- ]
961
- )
962
-
963
- @staticmethod
964
- def _convert_to_cron_string(
965
- cron_trigger: mlrun.common.schemas.schedule.ScheduleCronTrigger,
966
- ):
967
- """Converting the batch interval `ScheduleCronTrigger` into a cron trigger expression"""
968
- return "{} {} {} * *".format(
969
- cron_trigger.minute, cron_trigger.hour, cron_trigger.day
970
- ).replace("None", "*")