mlrun 1.7.0rc39__py3-none-any.whl → 1.7.0rc42__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 (58) hide show
  1. mlrun/common/constants.py +3 -0
  2. mlrun/common/db/sql_session.py +3 -2
  3. mlrun/common/helpers.py +0 -1
  4. mlrun/common/schemas/api_gateway.py +6 -6
  5. mlrun/common/schemas/common.py +4 -4
  6. mlrun/common/schemas/model_monitoring/model_endpoints.py +0 -1
  7. mlrun/config.py +1 -1
  8. mlrun/data_types/to_pandas.py +12 -12
  9. mlrun/datastore/alibaba_oss.py +1 -0
  10. mlrun/datastore/azure_blob.py +1 -6
  11. mlrun/datastore/base.py +12 -0
  12. mlrun/datastore/dbfs_store.py +1 -5
  13. mlrun/datastore/filestore.py +1 -3
  14. mlrun/datastore/google_cloud_storage.py +1 -9
  15. mlrun/datastore/redis.py +1 -0
  16. mlrun/datastore/s3.py +1 -0
  17. mlrun/datastore/storeytargets.py +147 -0
  18. mlrun/datastore/targets.py +67 -69
  19. mlrun/datastore/v3io.py +1 -0
  20. mlrun/errors.py +7 -4
  21. mlrun/feature_store/feature_vector.py +3 -1
  22. mlrun/feature_store/retrieval/job.py +3 -1
  23. mlrun/frameworks/sklearn/mlrun_interface.py +13 -3
  24. mlrun/model.py +1 -1
  25. mlrun/model_monitoring/api.py +1 -2
  26. mlrun/model_monitoring/applications/_application_steps.py +25 -43
  27. mlrun/model_monitoring/applications/context.py +206 -70
  28. mlrun/model_monitoring/controller.py +0 -1
  29. mlrun/model_monitoring/db/stores/__init__.py +3 -3
  30. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +17 -8
  31. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +14 -4
  32. mlrun/model_monitoring/db/tsdb/__init__.py +3 -3
  33. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +18 -10
  34. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +35 -23
  35. mlrun/model_monitoring/helpers.py +38 -1
  36. mlrun/model_monitoring/stream_processing.py +8 -26
  37. mlrun/package/packagers/default_packager.py +2 -2
  38. mlrun/projects/project.py +17 -16
  39. mlrun/runtimes/funcdoc.py +1 -1
  40. mlrun/runtimes/nuclio/api_gateway.py +9 -0
  41. mlrun/runtimes/nuclio/application/application.py +131 -55
  42. mlrun/runtimes/nuclio/function.py +4 -10
  43. mlrun/runtimes/nuclio/serving.py +2 -2
  44. mlrun/runtimes/sparkjob/spark3job.py +1 -1
  45. mlrun/runtimes/utils.py +16 -0
  46. mlrun/serving/routers.py +1 -1
  47. mlrun/serving/server.py +19 -5
  48. mlrun/serving/states.py +8 -0
  49. mlrun/serving/v2_serving.py +34 -26
  50. mlrun/utils/helpers.py +12 -2
  51. mlrun/utils/v3io_clients.py +2 -2
  52. mlrun/utils/version/version.json +2 -2
  53. {mlrun-1.7.0rc39.dist-info → mlrun-1.7.0rc42.dist-info}/METADATA +2 -2
  54. {mlrun-1.7.0rc39.dist-info → mlrun-1.7.0rc42.dist-info}/RECORD +58 -57
  55. {mlrun-1.7.0rc39.dist-info → mlrun-1.7.0rc42.dist-info}/WHEEL +1 -1
  56. {mlrun-1.7.0rc39.dist-info → mlrun-1.7.0rc42.dist-info}/LICENSE +0 -0
  57. {mlrun-1.7.0rc39.dist-info → mlrun-1.7.0rc42.dist-info}/entry_points.txt +0 -0
  58. {mlrun-1.7.0rc39.dist-info → mlrun-1.7.0rc42.dist-info}/top_level.txt +0 -0
@@ -11,19 +11,22 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
+
14
15
  import json
15
- import typing
16
+ import socket
17
+ from typing import Any, Optional, cast
16
18
 
17
19
  import numpy as np
18
20
  import pandas as pd
19
21
 
20
- import mlrun.common.helpers
21
- import mlrun.common.model_monitoring.helpers
22
+ import mlrun.common.constants as mlrun_constants
22
23
  import mlrun.common.schemas.model_monitoring.constants as mm_constants
23
24
  import mlrun.feature_store as fstore
24
- from mlrun.artifacts.model import ModelArtifact, get_model
25
+ import mlrun.features
26
+ import mlrun.serving
27
+ import mlrun.utils
28
+ from mlrun.artifacts import Artifact, DatasetArtifact, ModelArtifact, get_model
25
29
  from mlrun.common.model_monitoring.helpers import FeatureStats, pad_features_hist
26
- from mlrun.execution import MLClientCtx
27
30
  from mlrun.model_monitoring.helpers import (
28
31
  calculate_inputs_statistics,
29
32
  get_endpoint_record,
@@ -31,13 +34,17 @@ from mlrun.model_monitoring.helpers import (
31
34
  from mlrun.model_monitoring.model_endpoint import ModelEndpoint
32
35
 
33
36
 
34
- class MonitoringApplicationContext(MLClientCtx):
37
+ class MonitoringApplicationContext:
35
38
  """
36
39
  The monitoring context holds all the relevant information for the monitoring application,
37
40
  and also it can be used for logging artifacts and results.
38
41
  The monitoring context has the following attributes:
39
42
 
40
- :param application_name: (str) the app name
43
+ :param application_name: (str) The model monitoring application name.
44
+ :param project_name: (str) The project name.
45
+ :param project: (MlrunProject) The project object.
46
+ :param logger: (mlrun.utils.Logger) MLRun logger.
47
+ :param nuclio_logger: (nuclio.request.Logger) Nuclio logger.
41
48
  :param sample_df_stats: (FeatureStats) The new sample distribution dictionary.
42
49
  :param feature_stats: (FeatureStats) The train sample distribution dictionary.
43
50
  :param sample_df: (pd.DataFrame) The new sample DataFrame.
@@ -49,79 +56,89 @@ class MonitoringApplicationContext(MLClientCtx):
49
56
  :param model_endpoint: (ModelEndpoint) The model endpoint object.
50
57
  :param feature_names: (list[str]) List of models feature names.
51
58
  :param label_names: (list[str]) List of models label names.
52
- :param model: (tuple[str, ModelArtifact, dict]) The model file, model spec object, and list of
53
-
59
+ :param model: (tuple[str, ModelArtifact, dict]) The model file, model spec object,
60
+ and a list of extra data items.
54
61
  """
55
62
 
56
- def __init__(self, **kwargs):
57
- super().__init__(**kwargs)
58
-
59
- def _enrich_data(self):
60
- self.application_name: typing.Optional[str] = None
61
- self.start_infer_time: typing.Optional[pd.Timestamp] = None
62
- self.end_infer_time: typing.Optional[pd.Timestamp] = None
63
- self.latest_request: typing.Optional[pd.Timestamp] = None
64
- self.endpoint_id: typing.Optional[str] = None
65
- self.output_stream_uri: typing.Optional[str] = None
66
-
67
- self._sample_df: typing.Optional[pd.DataFrame] = None
68
- self._model_endpoint: typing.Optional[ModelEndpoint] = None
69
- self._feature_stats: typing.Optional[FeatureStats] = None
70
- self._sample_df_stats: typing.Optional[FeatureStats] = None
71
-
72
- @classmethod
73
- def from_dict(
74
- cls,
75
- attrs: dict,
76
- context=None,
77
- model_endpoint_dict=None,
78
- **kwargs,
79
- ) -> "MonitoringApplicationContext":
63
+ def __init__(
64
+ self,
65
+ *,
66
+ graph_context: mlrun.serving.GraphContext,
67
+ application_name: str,
68
+ event: dict[str, Any],
69
+ model_endpoint_dict: dict[str, ModelEndpoint],
70
+ ) -> None:
80
71
  """
81
- Create an instance of the MonitoringApplicationContext from a dictionary.
72
+ Initialize a `MonitoringApplicationContext` object.
73
+ Note: this object should not be instantiated manually.
82
74
 
83
- :param attrs: The instance data dictionary.
84
- :param context: The current application context.
75
+ :param application_name: The application name.
76
+ :param event: The instance data dictionary.
85
77
  :param model_endpoint_dict: Dictionary of model endpoints.
86
-
87
78
  """
79
+ self.application_name = application_name
88
80
 
89
- if not context:
90
- ctx = (
91
- super().from_dict(
92
- attrs=attrs.get(mm_constants.ApplicationEvent.MLRUN_CONTEXT, {}),
93
- **kwargs,
94
- ),
95
- )
96
- else:
97
- ctx = context
98
- cls._enrich_data(ctx)
81
+ self.project_name = graph_context.project
82
+ self.project = mlrun.load_project(url=self.project_name)
83
+
84
+ # MLRun Logger
85
+ self.logger = mlrun.utils.create_logger(
86
+ level=mlrun.mlconf.log_level,
87
+ formatter_kind=mlrun.mlconf.log_formatter,
88
+ name="monitoring-application",
89
+ )
90
+ # Nuclio logger - `nuclio.request.Logger`.
91
+ # Note: this logger does not accept keyword arguments.
92
+ self.nuclio_logger = graph_context.logger
99
93
 
100
- ctx.start_infer_time = pd.Timestamp(
101
- attrs.get(mm_constants.ApplicationEvent.START_INFER_TIME)
94
+ # event data
95
+ self.start_infer_time = pd.Timestamp(
96
+ cast(str, event.get(mm_constants.ApplicationEvent.START_INFER_TIME))
102
97
  )
103
- ctx.end_infer_time = pd.Timestamp(
104
- attrs.get(mm_constants.ApplicationEvent.END_INFER_TIME)
98
+ self.end_infer_time = pd.Timestamp(
99
+ cast(str, event.get(mm_constants.ApplicationEvent.END_INFER_TIME))
105
100
  )
106
- ctx.latest_request = pd.Timestamp(
107
- attrs.get(mm_constants.ApplicationEvent.LAST_REQUEST)
101
+ self.latest_request = pd.Timestamp(
102
+ cast(str, event.get(mm_constants.ApplicationEvent.LAST_REQUEST))
108
103
  )
109
- ctx.application_name = attrs.get(mm_constants.ApplicationEvent.APPLICATION_NAME)
110
- ctx._feature_stats = json.loads(
111
- attrs.get(mm_constants.ApplicationEvent.FEATURE_STATS, "{}")
104
+ self.endpoint_id = cast(
105
+ str, event.get(mm_constants.ApplicationEvent.ENDPOINT_ID)
112
106
  )
113
- ctx._sample_df_stats = json.loads(
114
- attrs.get(mm_constants.ApplicationEvent.CURRENT_STATS, "{}")
107
+ self.output_stream_uri = cast(
108
+ str, event.get(mm_constants.ApplicationEvent.OUTPUT_STREAM_URI)
115
109
  )
116
110
 
117
- ctx.endpoint_id = attrs.get(mm_constants.ApplicationEvent.ENDPOINT_ID)
118
- ctx._model_endpoint = model_endpoint_dict.get(ctx.endpoint_id)
111
+ self._feature_stats: Optional[FeatureStats] = json.loads(
112
+ event.get(mm_constants.ApplicationEvent.FEATURE_STATS, "{}")
113
+ )
114
+ self._sample_df_stats: Optional[FeatureStats] = json.loads(
115
+ event.get(mm_constants.ApplicationEvent.CURRENT_STATS, "{}")
116
+ )
119
117
 
120
- return ctx
118
+ # Default labels for the artifacts
119
+ self._default_labels = self._get_default_labels()
120
+
121
+ # Persistent data - fetched when needed
122
+ self._sample_df: Optional[pd.DataFrame] = None
123
+ self._model_endpoint: Optional[ModelEndpoint] = model_endpoint_dict.get(
124
+ self.endpoint_id
125
+ )
126
+
127
+ def _get_default_labels(self) -> dict[str, str]:
128
+ return {
129
+ mlrun_constants.MLRunInternalLabels.runner_pod: socket.gethostname(),
130
+ mlrun_constants.MLRunInternalLabels.producer_type: "model-monitoring-app",
131
+ mlrun_constants.MLRunInternalLabels.app_name: self.application_name,
132
+ mlrun_constants.MLRunInternalLabels.endpoint_id: self.endpoint_id,
133
+ }
134
+
135
+ def _add_default_labels(self, labels: Optional[dict[str, str]]) -> dict[str, str]:
136
+ """Add the default labels to logged artifacts labels"""
137
+ return (labels or {}) | self._default_labels
121
138
 
122
139
  @property
123
140
  def sample_df(self) -> pd.DataFrame:
124
- if not hasattr(self, "_sample_df") or self._sample_df is None:
141
+ if self._sample_df is None:
125
142
  feature_set = fstore.get_feature_set(
126
143
  self.model_endpoint.status.monitoring_feature_set_uri
127
144
  )
@@ -144,15 +161,15 @@ class MonitoringApplicationContext(MLClientCtx):
144
161
 
145
162
  @property
146
163
  def model_endpoint(self) -> ModelEndpoint:
147
- if not hasattr(self, "_model_endpoint") or not self._model_endpoint:
164
+ if not self._model_endpoint:
148
165
  self._model_endpoint = ModelEndpoint.from_flat_dict(
149
- get_endpoint_record(self.project, self.endpoint_id)
166
+ get_endpoint_record(self.project_name, self.endpoint_id)
150
167
  )
151
168
  return self._model_endpoint
152
169
 
153
170
  @property
154
171
  def feature_stats(self) -> FeatureStats:
155
- if not hasattr(self, "_feature_stats") or not self._feature_stats:
172
+ if not self._feature_stats:
156
173
  self._feature_stats = json.loads(self.model_endpoint.status.feature_stats)
157
174
  pad_features_hist(self._feature_stats)
158
175
  return self._feature_stats
@@ -160,7 +177,7 @@ class MonitoringApplicationContext(MLClientCtx):
160
177
  @property
161
178
  def sample_df_stats(self) -> FeatureStats:
162
179
  """statistics of the sample dataframe"""
163
- if not hasattr(self, "_sample_df_stats") or not self._sample_df_stats:
180
+ if not self._sample_df_stats:
164
181
  self._sample_df_stats = calculate_inputs_statistics(
165
182
  self.feature_stats, self.sample_df
166
183
  )
@@ -184,13 +201,11 @@ class MonitoringApplicationContext(MLClientCtx):
184
201
 
185
202
  @property
186
203
  def model(self) -> tuple[str, ModelArtifact, dict]:
187
- """return model file, model spec object, and list of extra data items"""
204
+ """The model file, model spec object, and a list of extra data items"""
188
205
  return get_model(self.model_endpoint.spec.model_uri)
189
206
 
190
207
  @staticmethod
191
- def dict_to_histogram(
192
- histogram_dict: mlrun.common.model_monitoring.helpers.FeatureStats,
193
- ) -> pd.DataFrame:
208
+ def dict_to_histogram(histogram_dict: FeatureStats) -> pd.DataFrame:
194
209
  """
195
210
  Convert histogram dictionary to pandas DataFrame with feature histograms as columns
196
211
 
@@ -210,3 +225,124 @@ class MonitoringApplicationContext(MLClientCtx):
210
225
  histograms = pd.DataFrame(histograms)
211
226
 
212
227
  return histograms
228
+
229
+ def log_artifact(
230
+ self,
231
+ item,
232
+ body=None,
233
+ tag: str = "",
234
+ local_path: str = "",
235
+ artifact_path: Optional[str] = None,
236
+ format: Optional[str] = None,
237
+ upload: Optional[bool] = None,
238
+ labels: Optional[dict[str, str]] = None,
239
+ target_path: Optional[str] = None,
240
+ **kwargs,
241
+ ) -> Artifact:
242
+ """
243
+ Log an artifact.
244
+ See :func:`~mlrun.projects.MlrunProject.log_artifact` for the documentation.
245
+ """
246
+ labels = self._add_default_labels(labels)
247
+ return self.project.log_artifact(
248
+ item,
249
+ body=body,
250
+ tag=tag,
251
+ local_path=local_path,
252
+ artifact_path=artifact_path,
253
+ format=format,
254
+ upload=upload,
255
+ labels=labels,
256
+ target_path=target_path,
257
+ **kwargs,
258
+ )
259
+
260
+ def log_dataset(
261
+ self,
262
+ key,
263
+ df,
264
+ tag="",
265
+ local_path=None,
266
+ artifact_path=None,
267
+ upload=None,
268
+ labels=None,
269
+ format="",
270
+ preview=None,
271
+ stats=None,
272
+ target_path="",
273
+ extra_data=None,
274
+ label_column: Optional[str] = None,
275
+ **kwargs,
276
+ ) -> DatasetArtifact:
277
+ """
278
+ Log a dataset artifact.
279
+ See :func:`~mlrun.projects.MlrunProject.log_dataset` for the documentation.
280
+ """
281
+ labels = self._add_default_labels(labels)
282
+ return self.project.log_dataset(
283
+ key,
284
+ df,
285
+ tag=tag,
286
+ local_path=local_path,
287
+ artifact_path=artifact_path,
288
+ upload=upload,
289
+ labels=labels,
290
+ format=format,
291
+ preview=preview,
292
+ stats=stats,
293
+ target_path=target_path,
294
+ extra_data=extra_data,
295
+ label_column=label_column,
296
+ **kwargs,
297
+ )
298
+
299
+ def log_model(
300
+ self,
301
+ key,
302
+ body=None,
303
+ framework="",
304
+ tag="",
305
+ model_dir=None,
306
+ model_file=None,
307
+ algorithm=None,
308
+ metrics=None,
309
+ parameters=None,
310
+ artifact_path=None,
311
+ upload=None,
312
+ labels=None,
313
+ inputs: Optional[list[mlrun.features.Feature]] = None,
314
+ outputs: Optional[list[mlrun.features.Feature]] = None,
315
+ feature_vector: Optional[str] = None,
316
+ feature_weights: Optional[list] = None,
317
+ training_set=None,
318
+ label_column=None,
319
+ extra_data=None,
320
+ **kwargs,
321
+ ) -> ModelArtifact:
322
+ """
323
+ Log a model artifact.
324
+ See :func:`~mlrun.projects.MlrunProject.log_model` for the documentation.
325
+ """
326
+ labels = self._add_default_labels(labels)
327
+ return self.project.log_model(
328
+ key,
329
+ body=body,
330
+ framework=framework,
331
+ tag=tag,
332
+ model_dir=model_dir,
333
+ model_file=model_file,
334
+ algorithm=algorithm,
335
+ metrics=metrics,
336
+ parameters=parameters,
337
+ artifact_path=artifact_path,
338
+ upload=upload,
339
+ labels=labels,
340
+ inputs=inputs,
341
+ outputs=outputs,
342
+ feature_vector=feature_vector,
343
+ feature_weights=feature_weights,
344
+ training_set=training_set,
345
+ label_column=label_column,
346
+ extra_data=extra_data,
347
+ **kwargs,
348
+ )
@@ -596,7 +596,6 @@ class MonitoringApplicationController:
596
596
  project=project,
597
597
  function_name=mm_constants.MonitoringFunctionNames.WRITER,
598
598
  ),
599
- mm_constants.ApplicationEvent.MLRUN_CONTEXT: {}, # TODO : for future use by ad-hoc batch infer
600
599
  }
601
600
  for app_name in applications_names:
602
601
  data.update({mm_constants.ApplicationEvent.APPLICATION_NAME: app_name})
@@ -63,7 +63,7 @@ class ObjectStoreFactory(enum.Enum):
63
63
  :param value: Provided enum (invalid) value.
64
64
  """
65
65
  valid_values = list(cls.__members__.keys())
66
- raise mlrun.errors.MLRunInvalidMMStoreType(
66
+ raise mlrun.errors.MLRunInvalidMMStoreTypeError(
67
67
  f"{value} is not a valid endpoint store, please choose a valid value: %{valid_values}."
68
68
  )
69
69
 
@@ -101,7 +101,7 @@ def get_store_object(
101
101
 
102
102
  :return: `StoreBase` object. Using this object, the user can apply different operations such as write, update, get
103
103
  and delete a model endpoint record.
104
- :raise: `MLRunInvalidMMStoreType` if the user didn't provide store connection
104
+ :raise: `MLRunInvalidMMStoreTypeError` if the user didn't provide store connection
105
105
  or the provided store connection is invalid.
106
106
  """
107
107
 
@@ -123,7 +123,7 @@ def get_store_object(
123
123
  mlrun.common.schemas.model_monitoring.ModelEndpointTarget.V3IO_NOSQL
124
124
  )
125
125
  else:
126
- raise mlrun.errors.MLRunInvalidMMStoreType(
126
+ raise mlrun.errors.MLRunInvalidMMStoreTypeError(
127
127
  "You must provide a valid store connection by using "
128
128
  "set_model_monitoring_credentials API."
129
129
  )
@@ -20,7 +20,7 @@ import pandas as pd
20
20
  import sqlalchemy
21
21
  import sqlalchemy.exc
22
22
  import sqlalchemy.orm
23
- from sqlalchemy.engine import make_url
23
+ from sqlalchemy.engine import Engine, make_url
24
24
  from sqlalchemy.sql.elements import BinaryExpression
25
25
 
26
26
  import mlrun.common.model_monitoring.helpers
@@ -61,9 +61,15 @@ class SQLStoreBase(StoreBase):
61
61
  )
62
62
 
63
63
  self._sql_connection_string = kwargs.get("store_connection_string")
64
- self._engine = get_engine(dsn=self._sql_connection_string)
64
+ self._engine = None
65
65
  self._init_tables()
66
66
 
67
+ @property
68
+ def engine(self) -> Engine:
69
+ if not self._engine:
70
+ self._engine = get_engine(dsn=self._sql_connection_string)
71
+ return self._engine
72
+
67
73
  def create_tables(self):
68
74
  self._create_tables_if_not_exist()
69
75
 
@@ -116,7 +122,7 @@ class SQLStoreBase(StoreBase):
116
122
  :param table_name: Target table name.
117
123
  :param event: Event dictionary that will be written into the DB.
118
124
  """
119
- with self._engine.connect() as connection:
125
+ with self.engine.connect() as connection:
120
126
  # Convert the result into a pandas Dataframe and write it into the database
121
127
  event_df = pd.DataFrame([event])
122
128
  event_df.to_sql(table_name, con=connection, index=False, if_exists="append")
@@ -177,7 +183,7 @@ class SQLStoreBase(StoreBase):
177
183
  param table: SQLAlchemy declarative table.
178
184
  :param criteria: A list of binary expressions that filter the query.
179
185
  """
180
- if not self._engine.has_table(table.__tablename__):
186
+ if not self.engine.has_table(table.__tablename__):
181
187
  logger.debug(
182
188
  f"Table {table.__tablename__} does not exist in the database. Skipping deletion."
183
189
  )
@@ -524,9 +530,9 @@ class SQLStoreBase(StoreBase):
524
530
  for table in self._tables:
525
531
  # Create table if not exist. The `metadata` contains the `ModelEndpointsTable`
526
532
  db_name = make_url(self._sql_connection_string).database
527
- if not self._engine.has_table(table):
533
+ if not self.engine.has_table(table):
528
534
  logger.info(f"Creating table {table} on {db_name} db.")
529
- self._tables[table].metadata.create_all(bind=self._engine)
535
+ self._tables[table].metadata.create_all(bind=self.engine)
530
536
  else:
531
537
  logger.info(f"Table {table} already exists on {db_name} db.")
532
538
 
@@ -574,8 +580,11 @@ class SQLStoreBase(StoreBase):
574
580
  """
575
581
  Delete all the model monitoring resources of the project in the SQL tables.
576
582
  """
583
+ logger.debug(
584
+ "Deleting model monitoring endpoints resources from the SQL tables",
585
+ project=self.project,
586
+ )
577
587
  endpoints = self.list_model_endpoints()
578
- logger.debug("Deleting model monitoring resources", project=self.project)
579
588
 
580
589
  for endpoint_dict in endpoints:
581
590
  endpoint_id = endpoint_dict[mm_schemas.EventFieldType.UID]
@@ -612,7 +621,7 @@ class SQLStoreBase(StoreBase):
612
621
 
613
622
  # Note: the block below does not use self._get, as we need here all the
614
623
  # results, not only `one_or_none`.
615
- with sqlalchemy.orm.Session(self._engine) as session:
624
+ with sqlalchemy.orm.Session(self.engine) as session:
616
625
  metric_rows = (
617
626
  session.query(table) # pyright: ignore[reportOptionalCall]
618
627
  .filter(table.endpoint_id == endpoint_id)
@@ -20,6 +20,7 @@ from http import HTTPStatus
20
20
  import v3io.dataplane
21
21
  import v3io.dataplane.output
22
22
  import v3io.dataplane.response
23
+ from v3io.dataplane import Client as V3IOClient
23
24
 
24
25
  import mlrun.common.model_monitoring.helpers
25
26
  import mlrun.common.schemas.model_monitoring as mm_schemas
@@ -100,13 +101,18 @@ class KVStoreBase(StoreBase):
100
101
  project: str,
101
102
  ) -> None:
102
103
  super().__init__(project=project)
103
- # Initialize a V3IO client instance
104
- self.client = mlrun.utils.v3io_clients.get_v3io_client(
105
- endpoint=mlrun.mlconf.v3io_api,
106
- )
104
+ self._client = None
107
105
  # Get the KV table path and container
108
106
  self.path, self.container = self._get_path_and_container()
109
107
 
108
+ @property
109
+ def client(self) -> V3IOClient:
110
+ if not self._client:
111
+ self._client = mlrun.utils.v3io_clients.get_v3io_client(
112
+ endpoint=mlrun.mlconf.v3io_api,
113
+ )
114
+ return self._client
115
+
110
116
  def write_model_endpoint(self, endpoint: dict[str, typing.Any]):
111
117
  """
112
118
  Create a new endpoint record in the KV table.
@@ -285,6 +291,10 @@ class KVStoreBase(StoreBase):
285
291
  """
286
292
  Delete all model endpoints resources in V3IO KV.
287
293
  """
294
+ logger.debug(
295
+ "Deleting model monitoring endpoints resources in V3IO KV",
296
+ project=self.project,
297
+ )
288
298
 
289
299
  endpoints = self.list_model_endpoints()
290
300
 
@@ -57,7 +57,7 @@ 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.MLRunInvalidMMStoreType(
60
+ raise mlrun.errors.MLRunInvalidMMStoreTypeError(
61
61
  f"{value} is not a valid tsdb, please choose a valid value: %{valid_values}."
62
62
  )
63
63
 
@@ -76,7 +76,7 @@ def get_tsdb_connector(
76
76
 
77
77
  :return: `TSDBConnector` object. The main goal of this object is to handle different operations on the
78
78
  TSDB connector such as updating drift metrics or write application record result.
79
- :raise: `MLRunInvalidMMStoreType` if the user didn't provide TSDB connection
79
+ :raise: `MLRunInvalidMMStoreTypeError` if the user didn't provide TSDB connection
80
80
  or the provided TSDB connection is invalid.
81
81
  """
82
82
 
@@ -93,7 +93,7 @@ def get_tsdb_connector(
93
93
  elif tsdb_connection_string and tsdb_connection_string == "v3io":
94
94
  tsdb_connector_type = mlrun.common.schemas.model_monitoring.TSDBTarget.V3IO_TSDB
95
95
  else:
96
- raise mlrun.errors.MLRunInvalidMMStoreType(
96
+ raise mlrun.errors.MLRunInvalidMMStoreTypeError(
97
97
  "You must provide a valid tsdb store connection by using "
98
98
  "set_model_monitoring_credentials API."
99
99
  )
@@ -47,10 +47,17 @@ class TDEngineConnector(TSDBConnector):
47
47
  )
48
48
  self._tdengine_connection_string = kwargs.get("connection_string")
49
49
  self.database = database
50
- self._connection = self._create_connection()
50
+
51
+ self._connection = None
51
52
  self._init_super_tables()
52
53
 
53
- def _create_connection(self):
54
+ @property
55
+ def connection(self) -> taosws.Connection:
56
+ if not self._connection:
57
+ self._connection = self._create_connection()
58
+ return self._connection
59
+
60
+ def _create_connection(self) -> taosws.Connection:
54
61
  """Establish a connection to the TSDB server."""
55
62
  conn = taosws.connect(self._tdengine_connection_string)
56
63
  try:
@@ -61,7 +68,7 @@ class TDEngineConnector(TSDBConnector):
61
68
  try:
62
69
  conn.execute(f"USE {self.database}")
63
70
  except taosws.QueryError as e:
64
- raise mlrun.errors.MLRunTSDBConnectionFailure(
71
+ raise mlrun.errors.MLRunTSDBConnectionFailureError(
65
72
  f"Failed to use TDEngine database {self.database}, {mlrun.errors.err_to_str(e)}"
66
73
  )
67
74
  return conn
@@ -84,7 +91,7 @@ class TDEngineConnector(TSDBConnector):
84
91
  """Create TDEngine supertables."""
85
92
  for table in self.tables:
86
93
  create_table_query = self.tables[table]._create_super_table_query()
87
- self._connection.execute(create_table_query)
94
+ self.connection.execute(create_table_query)
88
95
 
89
96
  def write_application_event(
90
97
  self,
@@ -128,10 +135,10 @@ class TDEngineConnector(TSDBConnector):
128
135
  create_table_query = table._create_subtable_query(
129
136
  subtable=table_name, values=event
130
137
  )
131
- self._connection.execute(create_table_query)
138
+ self.connection.execute(create_table_query)
132
139
 
133
140
  insert_statement = table._insert_subtable_query(
134
- self._connection,
141
+ self.connection,
135
142
  subtable=table_name,
136
143
  values=event,
137
144
  )
@@ -176,7 +183,8 @@ class TDEngineConnector(TSDBConnector):
176
183
  mm_schemas.EventFieldType.PROJECT,
177
184
  mm_schemas.EventFieldType.ENDPOINT_ID,
178
185
  ],
179
- max_events=10,
186
+ max_events=1000,
187
+ flush_after_seconds=30,
180
188
  )
181
189
 
182
190
  apply_process_before_tsdb()
@@ -196,12 +204,12 @@ class TDEngineConnector(TSDBConnector):
196
204
  get_subtable_names_query = self.tables[table]._get_subtables_query(
197
205
  values={mm_schemas.EventFieldType.PROJECT: self.project}
198
206
  )
199
- subtables = self._connection.query(get_subtable_names_query)
207
+ subtables = self.connection.query(get_subtable_names_query)
200
208
  for subtable in subtables:
201
209
  drop_query = self.tables[table]._drop_subtable_query(
202
210
  subtable=subtable[0]
203
211
  )
204
- self._connection.execute(drop_query)
212
+ self.connection.execute(drop_query)
205
213
  logger.info(
206
214
  f"Deleted all project resources in the TSDB connector for project {self.project}"
207
215
  )
@@ -273,7 +281,7 @@ class TDEngineConnector(TSDBConnector):
273
281
  database=self.database,
274
282
  )
275
283
  try:
276
- query_result = self._connection.query(full_query)
284
+ query_result = self.connection.query(full_query)
277
285
  except taosws.QueryError as e:
278
286
  raise mlrun.errors.MLRunInvalidArgumentError(
279
287
  f"Failed to query table {table} in database {self.database}, {str(e)}"