mlrun 1.7.0rc22__py3-none-any.whl → 1.7.0rc28__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.
- mlrun/__main__.py +10 -8
- mlrun/alerts/alert.py +13 -1
- mlrun/artifacts/manager.py +5 -0
- mlrun/common/constants.py +2 -2
- mlrun/common/formatters/__init__.py +1 -0
- mlrun/common/formatters/artifact.py +26 -3
- mlrun/common/formatters/base.py +9 -9
- mlrun/common/formatters/run.py +26 -0
- mlrun/common/helpers.py +11 -0
- mlrun/common/schemas/__init__.py +4 -0
- mlrun/common/schemas/alert.py +5 -9
- mlrun/common/schemas/api_gateway.py +64 -16
- mlrun/common/schemas/artifact.py +11 -0
- mlrun/common/schemas/constants.py +3 -0
- mlrun/common/schemas/feature_store.py +58 -28
- mlrun/common/schemas/model_monitoring/constants.py +21 -12
- mlrun/common/schemas/model_monitoring/model_endpoints.py +0 -12
- mlrun/common/schemas/pipeline.py +16 -0
- mlrun/common/schemas/project.py +17 -0
- mlrun/common/schemas/runs.py +17 -0
- mlrun/common/schemas/schedule.py +1 -1
- mlrun/common/types.py +5 -0
- mlrun/config.py +10 -25
- mlrun/datastore/azure_blob.py +2 -1
- mlrun/datastore/datastore.py +3 -3
- mlrun/datastore/google_cloud_storage.py +6 -2
- mlrun/datastore/snowflake_utils.py +3 -1
- mlrun/datastore/sources.py +26 -11
- mlrun/datastore/store_resources.py +2 -0
- mlrun/datastore/targets.py +68 -16
- mlrun/db/base.py +64 -2
- mlrun/db/httpdb.py +129 -41
- mlrun/db/nopdb.py +44 -3
- mlrun/errors.py +5 -3
- mlrun/execution.py +18 -10
- mlrun/feature_store/retrieval/spark_merger.py +2 -1
- mlrun/frameworks/__init__.py +0 -6
- mlrun/model.py +23 -0
- mlrun/model_monitoring/api.py +6 -52
- mlrun/model_monitoring/applications/histogram_data_drift.py +1 -1
- mlrun/model_monitoring/db/stores/__init__.py +37 -24
- mlrun/model_monitoring/db/stores/base/store.py +40 -1
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +42 -87
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +27 -35
- mlrun/model_monitoring/db/tsdb/__init__.py +15 -15
- mlrun/model_monitoring/db/tsdb/base.py +1 -1
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +6 -4
- mlrun/model_monitoring/helpers.py +17 -9
- mlrun/model_monitoring/stream_processing.py +9 -11
- mlrun/model_monitoring/writer.py +11 -11
- mlrun/package/__init__.py +1 -13
- mlrun/package/packagers/__init__.py +1 -6
- mlrun/projects/pipelines.py +10 -9
- mlrun/projects/project.py +95 -81
- mlrun/render.py +10 -5
- mlrun/run.py +13 -8
- mlrun/runtimes/base.py +11 -4
- mlrun/runtimes/daskjob.py +7 -1
- mlrun/runtimes/local.py +16 -3
- mlrun/runtimes/nuclio/application/application.py +0 -2
- mlrun/runtimes/nuclio/function.py +20 -0
- mlrun/runtimes/nuclio/serving.py +9 -6
- mlrun/runtimes/pod.py +5 -29
- mlrun/serving/routers.py +75 -59
- mlrun/serving/server.py +11 -0
- mlrun/serving/states.py +29 -0
- mlrun/serving/v2_serving.py +62 -39
- mlrun/utils/helpers.py +39 -1
- mlrun/utils/logger.py +36 -2
- mlrun/utils/notifications/notification/base.py +43 -7
- mlrun/utils/notifications/notification/git.py +21 -0
- mlrun/utils/notifications/notification/slack.py +9 -14
- mlrun/utils/notifications/notification/webhook.py +41 -1
- mlrun/utils/notifications/notification_pusher.py +3 -9
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc22.dist-info → mlrun-1.7.0rc28.dist-info}/METADATA +12 -7
- {mlrun-1.7.0rc22.dist-info → mlrun-1.7.0rc28.dist-info}/RECORD +81 -80
- {mlrun-1.7.0rc22.dist-info → mlrun-1.7.0rc28.dist-info}/WHEEL +1 -1
- {mlrun-1.7.0rc22.dist-info → mlrun-1.7.0rc28.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc22.dist-info → mlrun-1.7.0rc28.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc22.dist-info → mlrun-1.7.0rc28.dist-info}/top_level.txt +0 -0
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
import datetime
|
|
16
|
-
import json
|
|
17
16
|
import typing
|
|
18
17
|
import uuid
|
|
19
18
|
|
|
@@ -21,18 +20,20 @@ import pandas as pd
|
|
|
21
20
|
import sqlalchemy
|
|
22
21
|
import sqlalchemy.exc
|
|
23
22
|
import sqlalchemy.orm
|
|
23
|
+
from sqlalchemy.engine import make_url
|
|
24
24
|
from sqlalchemy.sql.elements import BinaryExpression
|
|
25
25
|
|
|
26
26
|
import mlrun.common.model_monitoring.helpers
|
|
27
27
|
import mlrun.common.schemas.model_monitoring as mm_schemas
|
|
28
|
-
import mlrun.model_monitoring.db
|
|
29
28
|
import mlrun.model_monitoring.db.stores.sqldb.models
|
|
30
29
|
import mlrun.model_monitoring.helpers
|
|
31
30
|
from mlrun.common.db.sql_session import create_session, get_engine
|
|
31
|
+
from mlrun.model_monitoring.db import StoreBase
|
|
32
32
|
from mlrun.utils import datetime_now, logger
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
class SQLStoreBase(
|
|
35
|
+
class SQLStoreBase(StoreBase):
|
|
36
|
+
type: typing.ClassVar[str] = mm_schemas.ModelEndpointTarget.SQL
|
|
36
37
|
"""
|
|
37
38
|
Handles the DB operations when the DB target is from type SQL. For the SQL operations, we use SQLAlchemy, a Python
|
|
38
39
|
SQL toolkit that handles the communication with the database. When using SQL for storing the model monitoring
|
|
@@ -44,24 +45,27 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
44
45
|
def __init__(
|
|
45
46
|
self,
|
|
46
47
|
project: str,
|
|
47
|
-
|
|
48
|
+
**kwargs,
|
|
48
49
|
):
|
|
49
50
|
"""
|
|
50
51
|
Initialize SQL store target object.
|
|
51
52
|
|
|
52
53
|
:param project: The name of the project.
|
|
53
|
-
:param secret_provider: An optional secret provider to get the connection string secret.
|
|
54
54
|
"""
|
|
55
55
|
|
|
56
56
|
super().__init__(project=project)
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
mlrun.
|
|
60
|
-
|
|
58
|
+
if "store_connection_string" not in kwargs:
|
|
59
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
60
|
+
"connection_string is a required parameter for SQLStoreBase."
|
|
61
61
|
)
|
|
62
|
-
)
|
|
63
62
|
|
|
63
|
+
self._sql_connection_string = kwargs.get("store_connection_string")
|
|
64
64
|
self._engine = get_engine(dsn=self._sql_connection_string)
|
|
65
|
+
self._init_tables()
|
|
66
|
+
|
|
67
|
+
def create_tables(self):
|
|
68
|
+
self._create_tables_if_not_exist()
|
|
65
69
|
|
|
66
70
|
def _init_tables(self):
|
|
67
71
|
self._init_model_endpoints_table()
|
|
@@ -70,13 +74,13 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
70
74
|
self._init_monitoring_schedules_table()
|
|
71
75
|
|
|
72
76
|
def _init_model_endpoints_table(self):
|
|
73
|
-
self.
|
|
77
|
+
self.model_endpoints_table = (
|
|
74
78
|
mlrun.model_monitoring.db.stores.sqldb.models._get_model_endpoints_table(
|
|
75
79
|
connection_string=self._sql_connection_string
|
|
76
80
|
)
|
|
77
81
|
)
|
|
78
82
|
self._tables[mm_schemas.EventFieldType.MODEL_ENDPOINTS] = (
|
|
79
|
-
self.
|
|
83
|
+
self.model_endpoints_table
|
|
80
84
|
)
|
|
81
85
|
|
|
82
86
|
def _init_application_results_table(self):
|
|
@@ -150,22 +154,17 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
150
154
|
:param criteria: A list of binary expressions that filter the query.
|
|
151
155
|
"""
|
|
152
156
|
with create_session(dsn=self._sql_connection_string) as session:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
)
|
|
165
|
-
except sqlalchemy.exc.ProgrammingError:
|
|
166
|
-
# Probably table doesn't exist, try to create tables
|
|
167
|
-
self._create_tables_if_not_exist()
|
|
168
|
-
return
|
|
157
|
+
logger.debug(
|
|
158
|
+
"Querying the DB",
|
|
159
|
+
table=table.__name__,
|
|
160
|
+
criteria=[str(criterion) for criterion in criteria],
|
|
161
|
+
)
|
|
162
|
+
# Generate the get query
|
|
163
|
+
return (
|
|
164
|
+
session.query(table) # pyright: ignore[reportOptionalCall]
|
|
165
|
+
.filter(*criteria)
|
|
166
|
+
.one_or_none()
|
|
167
|
+
)
|
|
169
168
|
|
|
170
169
|
def _delete(
|
|
171
170
|
self,
|
|
@@ -213,14 +212,13 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
213
212
|
of the attributes dictionary should exist in the SQL table.
|
|
214
213
|
|
|
215
214
|
"""
|
|
216
|
-
self._init_model_endpoints_table()
|
|
217
215
|
|
|
218
216
|
attributes.pop(mm_schemas.EventFieldType.ENDPOINT_ID, None)
|
|
219
217
|
|
|
220
218
|
self._update(
|
|
221
219
|
attributes=attributes,
|
|
222
|
-
table=self.
|
|
223
|
-
criteria=[self.
|
|
220
|
+
table=self.model_endpoints_table,
|
|
221
|
+
criteria=[self.model_endpoints_table.uid == endpoint_id],
|
|
224
222
|
)
|
|
225
223
|
|
|
226
224
|
def delete_model_endpoint(self, endpoint_id: str) -> None:
|
|
@@ -229,11 +227,10 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
229
227
|
|
|
230
228
|
:param endpoint_id: The unique id of the model endpoint.
|
|
231
229
|
"""
|
|
232
|
-
self._init_model_endpoints_table()
|
|
233
230
|
# Delete the model endpoint record using sqlalchemy ORM
|
|
234
231
|
self._delete(
|
|
235
|
-
table=self.
|
|
236
|
-
criteria=[self.
|
|
232
|
+
table=self.model_endpoints_table,
|
|
233
|
+
criteria=[self.model_endpoints_table.uid == endpoint_id],
|
|
237
234
|
)
|
|
238
235
|
|
|
239
236
|
def get_model_endpoint(
|
|
@@ -249,12 +246,11 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
249
246
|
|
|
250
247
|
:raise MLRunNotFoundError: If the model endpoints table was not found or the model endpoint id was not found.
|
|
251
248
|
"""
|
|
252
|
-
self._init_model_endpoints_table()
|
|
253
249
|
|
|
254
250
|
# Get the model endpoint record
|
|
255
251
|
endpoint_record = self._get(
|
|
256
|
-
table=self.
|
|
257
|
-
criteria=[self.
|
|
252
|
+
table=self.model_endpoints_table,
|
|
253
|
+
criteria=[self.model_endpoints_table.uid == endpoint_id],
|
|
258
254
|
)
|
|
259
255
|
|
|
260
256
|
if not endpoint_record:
|
|
@@ -286,23 +282,22 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
286
282
|
|
|
287
283
|
:return: A list of model endpoint dictionaries.
|
|
288
284
|
"""
|
|
289
|
-
self._init_model_endpoints_table()
|
|
290
285
|
# Generate an empty model endpoints that will be filled afterwards with model endpoint dictionaries
|
|
291
286
|
endpoint_list = []
|
|
292
287
|
|
|
293
288
|
model_endpoints_table = (
|
|
294
|
-
self.
|
|
289
|
+
self.model_endpoints_table.__table__ # pyright: ignore[reportAttributeAccessIssue]
|
|
295
290
|
)
|
|
296
|
-
|
|
297
291
|
# Get the model endpoints records using sqlalchemy ORM
|
|
298
292
|
with create_session(dsn=self._sql_connection_string) as session:
|
|
299
293
|
# Generate the list query
|
|
300
|
-
query = session.query(self.
|
|
294
|
+
query = session.query(self.model_endpoints_table).filter_by(
|
|
301
295
|
project=self.project
|
|
302
296
|
)
|
|
303
297
|
|
|
304
298
|
# Apply filters
|
|
305
299
|
if model:
|
|
300
|
+
model = model if ":" in model else f"{model}:latest"
|
|
306
301
|
query = self._filter_values(
|
|
307
302
|
query=query,
|
|
308
303
|
model_endpoints_table=model_endpoints_table,
|
|
@@ -310,11 +305,12 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
310
305
|
filtered_values=[model],
|
|
311
306
|
)
|
|
312
307
|
if function:
|
|
308
|
+
function_uri = f"{self.project}/{function}"
|
|
313
309
|
query = self._filter_values(
|
|
314
310
|
query=query,
|
|
315
311
|
model_endpoints_table=model_endpoints_table,
|
|
316
|
-
key_filter=mm_schemas.EventFieldType.
|
|
317
|
-
filtered_values=[
|
|
312
|
+
key_filter=mm_schemas.EventFieldType.FUNCTION_URI,
|
|
313
|
+
filtered_values=[function_uri],
|
|
318
314
|
)
|
|
319
315
|
if uids:
|
|
320
316
|
query = self._filter_values(
|
|
@@ -364,11 +360,9 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
364
360
|
"""
|
|
365
361
|
|
|
366
362
|
if kind == mm_schemas.WriterEventKind.METRIC:
|
|
367
|
-
self._init_application_metrics_table()
|
|
368
363
|
table = self.application_metrics_table
|
|
369
364
|
table_name = mm_schemas.FileTargetKind.APP_METRICS
|
|
370
365
|
elif kind == mm_schemas.WriterEventKind.RESULT:
|
|
371
|
-
self._init_application_results_table()
|
|
372
366
|
table = self.application_results_table
|
|
373
367
|
table_name = mm_schemas.FileTargetKind.APP_RESULTS
|
|
374
368
|
else:
|
|
@@ -442,7 +436,6 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
442
436
|
:return: Timestamp as a Unix time.
|
|
443
437
|
:raise: MLRunNotFoundError if last analyzed value is not found.
|
|
444
438
|
"""
|
|
445
|
-
self._init_monitoring_schedules_table()
|
|
446
439
|
monitoring_schedule_record = self._get(
|
|
447
440
|
table=self.MonitoringSchedulesTable,
|
|
448
441
|
criteria=self._get_filter_criteria(
|
|
@@ -469,8 +462,6 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
469
462
|
:param last_analyzed: Timestamp as a Unix time that represents the last analyzed time of a certain
|
|
470
463
|
application and model endpoint.
|
|
471
464
|
"""
|
|
472
|
-
self._init_monitoring_schedules_table()
|
|
473
|
-
|
|
474
465
|
criteria = self._get_filter_criteria(
|
|
475
466
|
table=self.MonitoringSchedulesTable,
|
|
476
467
|
endpoint_id=endpoint_id,
|
|
@@ -500,7 +491,6 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
500
491
|
def _delete_last_analyzed(
|
|
501
492
|
self, endpoint_id: str, application_name: typing.Optional[str] = None
|
|
502
493
|
) -> None:
|
|
503
|
-
self._init_monitoring_schedules_table()
|
|
504
494
|
criteria = self._get_filter_criteria(
|
|
505
495
|
table=self.MonitoringSchedulesTable,
|
|
506
496
|
endpoint_id=endpoint_id,
|
|
@@ -512,7 +502,6 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
512
502
|
def _delete_application_result(
|
|
513
503
|
self, endpoint_id: str, application_name: typing.Optional[str] = None
|
|
514
504
|
) -> None:
|
|
515
|
-
self._init_application_results_table()
|
|
516
505
|
criteria = self._get_filter_criteria(
|
|
517
506
|
table=self.application_results_table,
|
|
518
507
|
endpoint_id=endpoint_id,
|
|
@@ -524,7 +513,6 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
524
513
|
def _delete_application_metrics(
|
|
525
514
|
self, endpoint_id: str, application_name: typing.Optional[str] = None
|
|
526
515
|
) -> None:
|
|
527
|
-
self._init_application_metrics_table()
|
|
528
516
|
criteria = self._get_filter_criteria(
|
|
529
517
|
table=self.application_metrics_table,
|
|
530
518
|
endpoint_id=endpoint_id,
|
|
@@ -538,8 +526,12 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
538
526
|
|
|
539
527
|
for table in self._tables:
|
|
540
528
|
# Create table if not exist. The `metadata` contains the `ModelEndpointsTable`
|
|
529
|
+
db_name = make_url(self._sql_connection_string).database
|
|
541
530
|
if not self._engine.has_table(table):
|
|
531
|
+
logger.info(f"Creating table {table} on {db_name} db.")
|
|
542
532
|
self._tables[table].metadata.create_all(bind=self._engine)
|
|
533
|
+
else:
|
|
534
|
+
logger.info(f"Table {table} already exists on {db_name} db.")
|
|
543
535
|
|
|
544
536
|
@staticmethod
|
|
545
537
|
def _filter_values(
|
|
@@ -581,41 +573,6 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
581
573
|
# Apply AND operator on the SQL query object with the filters tuple
|
|
582
574
|
return query.filter(sqlalchemy.and_(*filter_query))
|
|
583
575
|
|
|
584
|
-
@staticmethod
|
|
585
|
-
def _validate_labels(
|
|
586
|
-
endpoint_dict: dict,
|
|
587
|
-
labels: list,
|
|
588
|
-
) -> bool:
|
|
589
|
-
"""Validate that the model endpoint dictionary has the provided labels. There are 2 possible cases:
|
|
590
|
-
1 - Labels were provided as a list of key-values pairs (e.g. ['label_1=value_1', 'label_2=value_2']): Validate
|
|
591
|
-
that each pair exist in the endpoint dictionary.
|
|
592
|
-
2 - Labels were provided as a list of key labels (e.g. ['label_1', 'label_2']): Validate that each key exist in
|
|
593
|
-
the endpoint labels dictionary.
|
|
594
|
-
|
|
595
|
-
:param endpoint_dict: Dictionary of the model endpoint records.
|
|
596
|
-
:param labels: List of dictionary of required labels.
|
|
597
|
-
|
|
598
|
-
:return: True if the labels exist in the endpoint labels dictionary, otherwise False.
|
|
599
|
-
"""
|
|
600
|
-
|
|
601
|
-
# Convert endpoint labels into dictionary
|
|
602
|
-
endpoint_labels = json.loads(
|
|
603
|
-
endpoint_dict.get(mm_schemas.EventFieldType.LABELS)
|
|
604
|
-
)
|
|
605
|
-
|
|
606
|
-
for label in labels:
|
|
607
|
-
# Case 1 - label is a key=value pair
|
|
608
|
-
if "=" in label:
|
|
609
|
-
lbl, value = list(map(lambda x: x.strip(), label.split("=")))
|
|
610
|
-
if lbl not in endpoint_labels or str(endpoint_labels[lbl]) != value:
|
|
611
|
-
return False
|
|
612
|
-
# Case 2 - label is just a key
|
|
613
|
-
else:
|
|
614
|
-
if label not in endpoint_labels:
|
|
615
|
-
return False
|
|
616
|
-
|
|
617
|
-
return True
|
|
618
|
-
|
|
619
576
|
def delete_model_endpoints_resources(self) -> None:
|
|
620
577
|
"""
|
|
621
578
|
Delete all the model monitoring resources of the project in the SQL tables.
|
|
@@ -650,11 +607,9 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
650
607
|
type=type,
|
|
651
608
|
)
|
|
652
609
|
if type == mm_schemas.ModelEndpointMonitoringMetricType.METRIC:
|
|
653
|
-
self._init_application_metrics_table()
|
|
654
610
|
table = self.application_metrics_table
|
|
655
611
|
name_col = mm_schemas.MetricData.METRIC_NAME
|
|
656
612
|
else:
|
|
657
|
-
self._init_application_results_table()
|
|
658
613
|
table = self.application_results_table
|
|
659
614
|
name_col = mm_schemas.ResultData.RESULT_NAME
|
|
660
615
|
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
import json
|
|
16
|
-
import os
|
|
17
16
|
import typing
|
|
18
17
|
from dataclasses import dataclass
|
|
19
18
|
from http import HTTPStatus
|
|
@@ -24,8 +23,8 @@ import v3io.dataplane.response
|
|
|
24
23
|
|
|
25
24
|
import mlrun.common.model_monitoring.helpers
|
|
26
25
|
import mlrun.common.schemas.model_monitoring as mm_schemas
|
|
27
|
-
import mlrun.model_monitoring.db
|
|
28
26
|
import mlrun.utils.v3io_clients
|
|
27
|
+
from mlrun.model_monitoring.db import StoreBase
|
|
29
28
|
from mlrun.utils import logger
|
|
30
29
|
|
|
31
30
|
# Fields to encode before storing in the KV table or to decode after retrieving
|
|
@@ -89,18 +88,21 @@ _KIND_TO_SCHEMA_PARAMS: dict[mm_schemas.WriterEventKind, SchemaParams] = {
|
|
|
89
88
|
_EXCLUDE_SCHEMA_FILTER_EXPRESSION = '__name!=".#schema"'
|
|
90
89
|
|
|
91
90
|
|
|
92
|
-
class KVStoreBase(
|
|
91
|
+
class KVStoreBase(StoreBase):
|
|
92
|
+
type: typing.ClassVar[str] = "v3io-nosql"
|
|
93
93
|
"""
|
|
94
94
|
Handles the DB operations when the DB target is from type KV. For the KV operations, we use an instance of V3IO
|
|
95
95
|
client and usually the KV table can be found under v3io:///users/pipelines/project-name/model-endpoints/endpoints/.
|
|
96
96
|
"""
|
|
97
97
|
|
|
98
|
-
def __init__(
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
project: str,
|
|
101
|
+
) -> None:
|
|
99
102
|
super().__init__(project=project)
|
|
100
103
|
# Initialize a V3IO client instance
|
|
101
|
-
self.access_key = access_key or os.environ.get("V3IO_ACCESS_KEY")
|
|
102
104
|
self.client = mlrun.utils.v3io_clients.get_v3io_client(
|
|
103
|
-
endpoint=mlrun.mlconf.v3io_api,
|
|
105
|
+
endpoint=mlrun.mlconf.v3io_api,
|
|
104
106
|
)
|
|
105
107
|
# Get the KV table path and container
|
|
106
108
|
self.path, self.container = self._get_path_and_container()
|
|
@@ -186,7 +188,6 @@ class KVStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
186
188
|
table_path=self.path,
|
|
187
189
|
key=endpoint_id,
|
|
188
190
|
raise_for_status=v3io.dataplane.RaiseForStatus.never,
|
|
189
|
-
access_key=self.access_key,
|
|
190
191
|
)
|
|
191
192
|
endpoint = endpoint.output.item
|
|
192
193
|
|
|
@@ -255,7 +256,6 @@ class KVStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
255
256
|
self.project,
|
|
256
257
|
function,
|
|
257
258
|
model,
|
|
258
|
-
labels,
|
|
259
259
|
top_level,
|
|
260
260
|
),
|
|
261
261
|
raise_for_status=v3io.dataplane.RaiseForStatus.never,
|
|
@@ -268,7 +268,6 @@ class KVStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
268
268
|
exc=mlrun.errors.err_to_str(exc),
|
|
269
269
|
)
|
|
270
270
|
return endpoint_list
|
|
271
|
-
|
|
272
271
|
# Create a list of model endpoints unique ids
|
|
273
272
|
if uids is None:
|
|
274
273
|
uids = []
|
|
@@ -281,10 +280,16 @@ class KVStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
281
280
|
|
|
282
281
|
# Add each relevant model endpoint to the model endpoints list
|
|
283
282
|
for endpoint_id in uids:
|
|
284
|
-
|
|
283
|
+
endpoint_dict = self.get_model_endpoint(
|
|
285
284
|
endpoint_id=endpoint_id,
|
|
286
285
|
)
|
|
287
|
-
|
|
286
|
+
|
|
287
|
+
if labels and not self._validate_labels(
|
|
288
|
+
endpoint_dict=endpoint_dict, labels=labels
|
|
289
|
+
):
|
|
290
|
+
continue
|
|
291
|
+
|
|
292
|
+
endpoint_list.append(endpoint_dict)
|
|
288
293
|
|
|
289
294
|
return endpoint_list
|
|
290
295
|
|
|
@@ -499,7 +504,6 @@ class KVStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
499
504
|
|
|
500
505
|
def _get_frames_client(self):
|
|
501
506
|
return mlrun.utils.v3io_clients.get_frames_client(
|
|
502
|
-
token=self.access_key,
|
|
503
507
|
address=mlrun.mlconf.v3io_framesd,
|
|
504
508
|
container=self.container,
|
|
505
509
|
)
|
|
@@ -509,20 +513,16 @@ class KVStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
509
513
|
project: str,
|
|
510
514
|
function: str = None,
|
|
511
515
|
model: str = None,
|
|
512
|
-
labels: list[str] = None,
|
|
513
516
|
top_level: bool = False,
|
|
514
517
|
) -> str:
|
|
515
518
|
"""
|
|
516
519
|
Convert the provided filters into a valid filter expression. The expected filter expression includes different
|
|
517
520
|
conditions, divided by ' AND '.
|
|
518
521
|
|
|
519
|
-
:param project:
|
|
520
|
-
:param model:
|
|
521
|
-
:param function:
|
|
522
|
-
:param
|
|
523
|
-
a label (i.e. list("key=value")) or by looking for the existence of a given
|
|
524
|
-
key (i.e. "key").
|
|
525
|
-
:param top_level: If True will return only routers and endpoint that are NOT children of any router.
|
|
522
|
+
:param project: The name of the project.
|
|
523
|
+
:param model: The name of the model to filter by.
|
|
524
|
+
:param function: The name of the function to filter by.
|
|
525
|
+
:param top_level: If True will return only routers and endpoint that are NOT children of any router.
|
|
526
526
|
|
|
527
527
|
:return: A valid filter expression as a string.
|
|
528
528
|
|
|
@@ -533,25 +533,17 @@ class KVStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
533
533
|
raise mlrun.errors.MLRunInvalidArgumentError("project can't be empty")
|
|
534
534
|
|
|
535
535
|
# Add project filter
|
|
536
|
-
filter_expression = [f"
|
|
536
|
+
filter_expression = [f"{mm_schemas.EventFieldType.PROJECT}=='{project}'"]
|
|
537
537
|
|
|
538
538
|
# Add function and model filters
|
|
539
539
|
if function:
|
|
540
|
-
|
|
540
|
+
function_uri = f"{project}/{function}" if function else None
|
|
541
|
+
filter_expression.append(
|
|
542
|
+
f"{mm_schemas.EventFieldType.FUNCTION_URI}=='{function_uri}'"
|
|
543
|
+
)
|
|
541
544
|
if model:
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
# Add labels filters
|
|
545
|
-
if labels:
|
|
546
|
-
for label in labels:
|
|
547
|
-
if not label.startswith("_"):
|
|
548
|
-
label = f"_{label}"
|
|
549
|
-
|
|
550
|
-
if "=" in label:
|
|
551
|
-
lbl, value = list(map(lambda x: x.strip(), label.split("=")))
|
|
552
|
-
filter_expression.append(f"{lbl}=='{value}'")
|
|
553
|
-
else:
|
|
554
|
-
filter_expression.append(f"exists({label})")
|
|
545
|
+
model = model if ":" in model else f"{model}:latest"
|
|
546
|
+
filter_expression.append(f"{mm_schemas.EventFieldType.MODEL}=='{model}'")
|
|
555
547
|
|
|
556
548
|
# Apply top_level filter (remove endpoints that considered a child of a router)
|
|
557
549
|
if top_level:
|
|
@@ -57,41 +57,41 @@ class ObjectTSDBFactory(enum.Enum):
|
|
|
57
57
|
:param value: Provided enum (invalid) value.
|
|
58
58
|
"""
|
|
59
59
|
valid_values = list(cls.__members__.keys())
|
|
60
|
-
raise mlrun.errors.
|
|
60
|
+
raise mlrun.errors.MLRunInvalidMMStoreType(
|
|
61
61
|
f"{value} is not a valid tsdb, please choose a valid value: %{valid_values}."
|
|
62
62
|
)
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
def get_tsdb_connector(
|
|
66
66
|
project: str,
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
secret_provider: typing.Optional[typing.Callable[[str], str]] = None,
|
|
68
|
+
tsdb_connection_string: typing.Optional[str] = None,
|
|
69
69
|
**kwargs,
|
|
70
70
|
) -> TSDBConnector:
|
|
71
71
|
"""
|
|
72
72
|
Get TSDB connector object.
|
|
73
|
-
:param project:
|
|
74
|
-
:param
|
|
75
|
-
|
|
76
|
-
:param secret_provider: An optional secret provider to get the connection string secret.
|
|
73
|
+
:param project: The name of the project.
|
|
74
|
+
: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.
|
|
77
76
|
|
|
78
77
|
:return: `TSDBConnector` object. The main goal of this object is to handle different operations on the
|
|
79
78
|
TSDB connector such as updating drift metrics or write application record result.
|
|
80
79
|
"""
|
|
81
80
|
|
|
82
|
-
tsdb_connection_string =
|
|
83
|
-
|
|
81
|
+
tsdb_connection_string = (
|
|
82
|
+
tsdb_connection_string
|
|
83
|
+
or mlrun.model_monitoring.helpers.get_tsdb_connection_string(
|
|
84
|
+
secret_provider=secret_provider
|
|
85
|
+
)
|
|
84
86
|
)
|
|
85
87
|
|
|
86
88
|
if tsdb_connection_string and tsdb_connection_string.startswith("taosws"):
|
|
87
89
|
tsdb_connector_type = mlrun.common.schemas.model_monitoring.TSDBTarget.TDEngine
|
|
88
90
|
kwargs["connection_string"] = tsdb_connection_string
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
tsdb_connector_type
|
|
93
|
-
or mlrun.mlconf.model_endpoint_monitoring.tsdb_connector_type
|
|
94
|
-
)
|
|
91
|
+
elif tsdb_connection_string and tsdb_connection_string == "v3io":
|
|
92
|
+
tsdb_connector_type = mlrun.common.schemas.model_monitoring.TSDBTarget.V3IO_TSDB
|
|
93
|
+
else:
|
|
94
|
+
tsdb_connector_type = None
|
|
95
95
|
|
|
96
96
|
# Get connector type value from ObjectTSDBFactory enum class
|
|
97
97
|
tsdb_connector_factory = ObjectTSDBFactory(tsdb_connector_type)
|
|
@@ -38,7 +38,10 @@ def _is_no_schema_error(exc: v3io_frames.ReadError) -> bool:
|
|
|
38
38
|
In case of a nonexistent TSDB table - a `v3io_frames.ReadError` error is raised.
|
|
39
39
|
Check if the error message contains the relevant string to verify the cause.
|
|
40
40
|
"""
|
|
41
|
-
|
|
41
|
+
msg = str(exc)
|
|
42
|
+
# https://github.com/v3io/v3io-tsdb/blob/v0.14.1/pkg/tsdb/v3iotsdb.go#L205
|
|
43
|
+
# https://github.com/v3io/v3io-tsdb/blob/v0.14.1/pkg/partmgr/partmgr.go#L238
|
|
44
|
+
return "No TSDB schema file found" in msg or "Failed to read schema at path" in msg
|
|
42
45
|
|
|
43
46
|
|
|
44
47
|
class V3IOTSDBConnector(TSDBConnector):
|
|
@@ -418,9 +421,8 @@ class V3IOTSDBConnector(TSDBConnector):
|
|
|
418
421
|
f"Available tables: {list(self.tables.keys())}"
|
|
419
422
|
)
|
|
420
423
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
aggregators = ",".join(agg_funcs)
|
|
424
|
+
# Frames client expects the aggregators to be a comma-separated string
|
|
425
|
+
aggregators = ",".join(agg_funcs) if agg_funcs else None
|
|
424
426
|
table_path = self.tables[table]
|
|
425
427
|
try:
|
|
426
428
|
df = self._frames_client.read(
|
|
@@ -59,13 +59,17 @@ def get_stream_path(
|
|
|
59
59
|
|
|
60
60
|
stream_uri = mlrun.get_secret_or_env(
|
|
61
61
|
mlrun.common.schemas.model_monitoring.ProjectSecretKeys.STREAM_PATH
|
|
62
|
-
) or mlrun.mlconf.get_model_monitoring_file_target_path(
|
|
63
|
-
project=project,
|
|
64
|
-
kind=mlrun.common.schemas.model_monitoring.FileTargetKind.STREAM,
|
|
65
|
-
target="online",
|
|
66
|
-
function_name=function_name,
|
|
67
62
|
)
|
|
68
63
|
|
|
64
|
+
if not stream_uri or stream_uri == "v3io":
|
|
65
|
+
# TODO : remove the first part of this condition in 1.9.0
|
|
66
|
+
stream_uri = mlrun.mlconf.get_model_monitoring_file_target_path(
|
|
67
|
+
project=project,
|
|
68
|
+
kind=mlrun.common.schemas.model_monitoring.FileTargetKind.STREAM,
|
|
69
|
+
target="online",
|
|
70
|
+
function_name=function_name,
|
|
71
|
+
)
|
|
72
|
+
|
|
69
73
|
if isinstance(stream_uri, list): # ML-6043 - user side gets only the new stream uri
|
|
70
74
|
stream_uri = stream_uri[1] # get new stream path, under projects
|
|
71
75
|
return mlrun.common.model_monitoring.helpers.parse_monitoring_stream_path(
|
|
@@ -97,7 +101,7 @@ def get_monitoring_parquet_path(
|
|
|
97
101
|
return parquet_path
|
|
98
102
|
|
|
99
103
|
|
|
100
|
-
def get_connection_string(secret_provider: typing.Callable = None) -> str:
|
|
104
|
+
def get_connection_string(secret_provider: typing.Callable[[str], str] = None) -> str:
|
|
101
105
|
"""Get endpoint store connection string from the project secret. If wasn't set, take it from the system
|
|
102
106
|
configurations.
|
|
103
107
|
|
|
@@ -117,7 +121,7 @@ def get_connection_string(secret_provider: typing.Callable = None) -> str:
|
|
|
117
121
|
|
|
118
122
|
|
|
119
123
|
def get_tsdb_connection_string(
|
|
120
|
-
secret_provider: typing.Optional[typing.Callable] = None,
|
|
124
|
+
secret_provider: typing.Optional[typing.Callable[[str], str]] = None,
|
|
121
125
|
) -> str:
|
|
122
126
|
"""Get TSDB connection string from the project secret. If wasn't set, take it from the system
|
|
123
127
|
configurations.
|
|
@@ -278,9 +282,13 @@ def calculate_inputs_statistics(
|
|
|
278
282
|
return inputs_statistics
|
|
279
283
|
|
|
280
284
|
|
|
281
|
-
def get_endpoint_record(
|
|
285
|
+
def get_endpoint_record(
|
|
286
|
+
project: str,
|
|
287
|
+
endpoint_id: str,
|
|
288
|
+
secret_provider: typing.Optional[typing.Callable[[str], str]] = None,
|
|
289
|
+
) -> dict[str, typing.Any]:
|
|
282
290
|
model_endpoint_store = mlrun.model_monitoring.get_store_object(
|
|
283
|
-
project=project,
|
|
291
|
+
project=project, secret_provider=secret_provider
|
|
284
292
|
)
|
|
285
293
|
return model_endpoint_store.get_model_endpoint(endpoint_id=endpoint_id)
|
|
286
294
|
|