mlrun 1.8.0rc4__py3-none-any.whl → 1.8.0rc7__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 (75) hide show
  1. mlrun/__init__.py +5 -3
  2. mlrun/alerts/alert.py +129 -2
  3. mlrun/artifacts/__init__.py +1 -1
  4. mlrun/artifacts/base.py +12 -1
  5. mlrun/artifacts/document.py +59 -38
  6. mlrun/common/constants.py +1 -0
  7. mlrun/common/model_monitoring/__init__.py +0 -2
  8. mlrun/common/model_monitoring/helpers.py +0 -28
  9. mlrun/common/schemas/__init__.py +2 -4
  10. mlrun/common/schemas/alert.py +80 -1
  11. mlrun/common/schemas/artifact.py +4 -0
  12. mlrun/common/schemas/client_spec.py +0 -1
  13. mlrun/common/schemas/model_monitoring/__init__.py +0 -6
  14. mlrun/common/schemas/model_monitoring/constants.py +11 -9
  15. mlrun/common/schemas/model_monitoring/model_endpoints.py +77 -149
  16. mlrun/common/schemas/notification.py +6 -0
  17. mlrun/common/schemas/project.py +3 -0
  18. mlrun/config.py +2 -3
  19. mlrun/datastore/datastore_profile.py +57 -17
  20. mlrun/datastore/sources.py +1 -2
  21. mlrun/datastore/vectorstore.py +67 -59
  22. mlrun/db/base.py +29 -19
  23. mlrun/db/factory.py +0 -3
  24. mlrun/db/httpdb.py +224 -161
  25. mlrun/db/nopdb.py +36 -17
  26. mlrun/execution.py +46 -32
  27. mlrun/feature_store/api.py +1 -0
  28. mlrun/model.py +7 -0
  29. mlrun/model_monitoring/__init__.py +3 -2
  30. mlrun/model_monitoring/api.py +55 -53
  31. mlrun/model_monitoring/applications/_application_steps.py +4 -2
  32. mlrun/model_monitoring/applications/base.py +165 -6
  33. mlrun/model_monitoring/applications/context.py +88 -37
  34. mlrun/model_monitoring/applications/evidently_base.py +0 -1
  35. mlrun/model_monitoring/applications/histogram_data_drift.py +3 -7
  36. mlrun/model_monitoring/controller.py +43 -37
  37. mlrun/model_monitoring/db/__init__.py +0 -2
  38. mlrun/model_monitoring/db/tsdb/base.py +2 -1
  39. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +2 -1
  40. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +43 -0
  41. mlrun/model_monitoring/helpers.py +79 -66
  42. mlrun/model_monitoring/stream_processing.py +83 -270
  43. mlrun/model_monitoring/writer.py +1 -10
  44. mlrun/projects/pipelines.py +37 -1
  45. mlrun/projects/project.py +171 -74
  46. mlrun/run.py +40 -0
  47. mlrun/runtimes/nuclio/function.py +7 -6
  48. mlrun/runtimes/nuclio/serving.py +9 -2
  49. mlrun/serving/routers.py +158 -145
  50. mlrun/serving/server.py +6 -0
  51. mlrun/serving/states.py +21 -7
  52. mlrun/serving/v2_serving.py +70 -61
  53. mlrun/utils/helpers.py +14 -30
  54. mlrun/utils/notifications/notification/mail.py +36 -9
  55. mlrun/utils/notifications/notification_pusher.py +43 -18
  56. mlrun/utils/version/version.json +2 -2
  57. {mlrun-1.8.0rc4.dist-info → mlrun-1.8.0rc7.dist-info}/METADATA +5 -4
  58. {mlrun-1.8.0rc4.dist-info → mlrun-1.8.0rc7.dist-info}/RECORD +62 -75
  59. mlrun/common/schemas/model_monitoring/model_endpoint_v2.py +0 -149
  60. mlrun/model_monitoring/db/stores/__init__.py +0 -136
  61. mlrun/model_monitoring/db/stores/base/__init__.py +0 -15
  62. mlrun/model_monitoring/db/stores/base/store.py +0 -154
  63. mlrun/model_monitoring/db/stores/sqldb/__init__.py +0 -13
  64. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +0 -46
  65. mlrun/model_monitoring/db/stores/sqldb/models/base.py +0 -93
  66. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +0 -47
  67. mlrun/model_monitoring/db/stores/sqldb/models/sqlite.py +0 -25
  68. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +0 -408
  69. mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +0 -13
  70. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +0 -464
  71. mlrun/model_monitoring/model_endpoint.py +0 -120
  72. {mlrun-1.8.0rc4.dist-info → mlrun-1.8.0rc7.dist-info}/LICENSE +0 -0
  73. {mlrun-1.8.0rc4.dist-info → mlrun-1.8.0rc7.dist-info}/WHEEL +0 -0
  74. {mlrun-1.8.0rc4.dist-info → mlrun-1.8.0rc7.dist-info}/entry_points.txt +0 -0
  75. {mlrun-1.8.0rc4.dist-info → mlrun-1.8.0rc7.dist-info}/top_level.txt +0 -0
@@ -1,408 +0,0 @@
1
- # Copyright 2024 Iguazio
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- import datetime
16
- import typing
17
-
18
- import pandas as pd
19
- import sqlalchemy
20
- import sqlalchemy.exc
21
- import sqlalchemy.orm
22
- from sqlalchemy.engine import Engine, make_url
23
- from sqlalchemy.sql.elements import BinaryExpression
24
-
25
- import mlrun.common.model_monitoring.helpers
26
- import mlrun.common.schemas.model_monitoring as mm_schemas
27
- import mlrun.model_monitoring.db.stores.sqldb.models
28
- import mlrun.model_monitoring.helpers
29
- from mlrun.common.db.sql_session import create_session, get_engine
30
- from mlrun.model_monitoring.db import StoreBase
31
- from mlrun.utils import datetime_now, logger
32
-
33
-
34
- class SQLStoreBase(StoreBase):
35
- type: typing.ClassVar[str] = mm_schemas.ModelEndpointTarget.SQL
36
- """
37
- Handles the DB operations when the DB target is from type SQL. For the SQL operations, we use SQLAlchemy, a Python
38
- SQL toolkit that handles the communication with the database. When using SQL for storing the model monitoring
39
- data, the user needs to provide a valid connection string for the database.
40
- """
41
-
42
- _tables = {}
43
-
44
- def __init__(
45
- self,
46
- project: str,
47
- **kwargs,
48
- ):
49
- """
50
- Initialize SQL store target object.
51
-
52
- :param project: The name of the project.
53
- """
54
-
55
- super().__init__(project=project)
56
-
57
- if "store_connection_string" not in kwargs:
58
- raise mlrun.errors.MLRunInvalidArgumentError(
59
- "connection_string is a required parameter for SQLStoreBase."
60
- )
61
-
62
- self._sql_connection_string = kwargs.get("store_connection_string")
63
- self._engine = None
64
- self._init_tables()
65
-
66
- @property
67
- def engine(self) -> Engine:
68
- if not self._engine:
69
- self._engine = get_engine(dsn=self._sql_connection_string)
70
- return self._engine
71
-
72
- def create_tables(self):
73
- self._create_tables_if_not_exist()
74
-
75
- def _init_tables(self):
76
- self._init_model_endpoints_table()
77
-
78
- def _init_model_endpoints_table(self):
79
- self.model_endpoints_table = (
80
- mlrun.model_monitoring.db.stores.sqldb.models._get_model_endpoints_table(
81
- connection_string=self._sql_connection_string
82
- )
83
- )
84
- self._tables[mm_schemas.EventFieldType.MODEL_ENDPOINTS] = (
85
- self.model_endpoints_table
86
- )
87
-
88
- def _write(self, table_name: str, event: dict[str, typing.Any]) -> None:
89
- """
90
- Create a new record in the SQL table.
91
-
92
- :param table_name: Target table name.
93
- :param event: Event dictionary that will be written into the DB.
94
- """
95
- with self.engine.connect() as connection:
96
- # Convert the result into a pandas Dataframe and write it into the database
97
- event_df = pd.DataFrame([event])
98
- event_df.to_sql(table_name, con=connection, index=False, if_exists="append")
99
-
100
- def _update(
101
- self,
102
- attributes: dict[str, typing.Any],
103
- table: sqlalchemy.orm.decl_api.DeclarativeMeta,
104
- criteria: list[BinaryExpression],
105
- ) -> None:
106
- """
107
- Update a record in the SQL table.
108
-
109
- :param attributes: Dictionary of attributes that will be used for update the record. Note that the keys
110
- of the attributes dictionary should exist in the SQL table.
111
- :param table: SQLAlchemy declarative table.
112
- :param criteria: A list of binary expressions that filter the query.
113
- """
114
- with create_session(dsn=self._sql_connection_string) as session:
115
- # Generate and commit the update session query
116
- session.query(
117
- table # pyright: ignore[reportOptionalCall]
118
- ).filter(*criteria).update(attributes, synchronize_session=False)
119
- session.commit()
120
-
121
- def _get(
122
- self,
123
- table: sqlalchemy.orm.decl_api.DeclarativeMeta,
124
- criteria: list[BinaryExpression],
125
- ):
126
- """
127
- Get a record from the SQL table.
128
-
129
- param table: SQLAlchemy declarative table.
130
- :param criteria: A list of binary expressions that filter the query.
131
- """
132
- with create_session(dsn=self._sql_connection_string) as session:
133
- logger.debug(
134
- "Querying the DB",
135
- table=table.__name__,
136
- criteria=[str(criterion) for criterion in criteria],
137
- )
138
- # Generate the get query
139
- return (
140
- session.query(table) # pyright: ignore[reportOptionalCall]
141
- .filter(*criteria)
142
- .one_or_none()
143
- )
144
-
145
- def _delete(
146
- self,
147
- table: sqlalchemy.orm.decl_api.DeclarativeMeta,
148
- criteria: list[BinaryExpression],
149
- ) -> None:
150
- """
151
- Delete records from the SQL table.
152
-
153
- param table: SQLAlchemy declarative table.
154
- :param criteria: A list of binary expressions that filter the query.
155
- """
156
- if not self.engine.has_table(table.__tablename__):
157
- logger.debug(
158
- f"Table {table.__tablename__} does not exist in the database. Skipping deletion."
159
- )
160
- return
161
- with create_session(dsn=self._sql_connection_string) as session:
162
- # Generate and commit the delete query
163
- session.query(
164
- table # pyright: ignore[reportOptionalCall]
165
- ).filter(*criteria).delete(synchronize_session=False)
166
- session.commit()
167
-
168
- def write_model_endpoint(self, endpoint: dict[str, typing.Any]):
169
- """
170
- Create a new endpoint record in the SQL table. This method also creates the model endpoints table within the
171
- SQL database if not exist.
172
-
173
- :param endpoint: model endpoint dictionary that will be written into the DB.
174
- """
175
-
176
- # Adjust timestamps fields
177
- endpoint[mm_schemas.EventFieldType.FIRST_REQUEST] = (endpoint)[
178
- mm_schemas.EventFieldType.LAST_REQUEST
179
- ] = datetime_now()
180
-
181
- self._write(
182
- table_name=mm_schemas.EventFieldType.MODEL_ENDPOINTS, event=endpoint
183
- )
184
-
185
- def update_model_endpoint(
186
- self, endpoint_id: str, attributes: dict[str, typing.Any]
187
- ):
188
- """
189
- Update a model endpoint record with a given attributes.
190
-
191
- :param endpoint_id: The unique id of the model endpoint.
192
- :param attributes: Dictionary of attributes that will be used for update the model endpoint. Note that the keys
193
- of the attributes dictionary should exist in the SQL table.
194
-
195
- """
196
-
197
- attributes.pop(mm_schemas.EventFieldType.ENDPOINT_ID, None)
198
-
199
- self._update(
200
- attributes=attributes,
201
- table=self.model_endpoints_table,
202
- criteria=[self.model_endpoints_table.uid == endpoint_id],
203
- )
204
-
205
- def delete_model_endpoint(self, endpoint_id: str) -> None:
206
- """
207
- Deletes the SQL record of a given model endpoint id.
208
-
209
- :param endpoint_id: The unique id of the model endpoint.
210
- """
211
- # Delete the model endpoint record using sqlalchemy ORM
212
- self._delete(
213
- table=self.model_endpoints_table,
214
- criteria=[self.model_endpoints_table.uid == endpoint_id],
215
- )
216
-
217
- def get_model_endpoint(
218
- self,
219
- endpoint_id: str,
220
- ) -> dict[str, typing.Any]:
221
- """
222
- Get a single model endpoint record.
223
-
224
- :param endpoint_id: The unique id of the model endpoint.
225
-
226
- :return: A model endpoint record as a dictionary.
227
-
228
- :raise MLRunNotFoundError: If the model endpoints table was not found or the model endpoint id was not found.
229
- """
230
-
231
- # Get the model endpoint record
232
- endpoint_record = self._get(
233
- table=self.model_endpoints_table,
234
- criteria=[self.model_endpoints_table.uid == endpoint_id],
235
- )
236
-
237
- if not endpoint_record:
238
- raise mlrun.errors.MLRunNotFoundError(f"Endpoint {endpoint_id} not found")
239
-
240
- # Convert the database values and the table columns into a python dictionary
241
- return endpoint_record.to_dict()
242
-
243
- def list_model_endpoints(
244
- self,
245
- model: typing.Optional[str] = None,
246
- function: typing.Optional[str] = None,
247
- labels: typing.Optional[list[str]] = None,
248
- top_level: typing.Optional[bool] = None,
249
- uids: typing.Optional[list] = None,
250
- include_stats: typing.Optional[bool] = None,
251
- ) -> list[dict[str, typing.Any]]:
252
- # Generate an empty model endpoints that will be filled afterwards with model endpoint dictionaries
253
- endpoint_list = []
254
-
255
- model_endpoints_table = (
256
- self.model_endpoints_table.__table__ # pyright: ignore[reportAttributeAccessIssue]
257
- )
258
- # Get the model endpoints records using sqlalchemy ORM
259
- with create_session(dsn=self._sql_connection_string) as session:
260
- # Generate the list query
261
- query = session.query(self.model_endpoints_table).filter_by(
262
- project=self.project
263
- )
264
-
265
- # Apply filters
266
- if model:
267
- model = model if ":" in model else f"{model}:latest"
268
- query = self._filter_values(
269
- query=query,
270
- model_endpoints_table=model_endpoints_table,
271
- key_filter=mm_schemas.EventFieldType.MODEL,
272
- filtered_values=[model],
273
- )
274
- if function:
275
- function_uri = f"{self.project}/{function}"
276
- query = self._filter_values(
277
- query=query,
278
- model_endpoints_table=model_endpoints_table,
279
- key_filter=mm_schemas.EventFieldType.FUNCTION_URI,
280
- filtered_values=[function_uri],
281
- )
282
- if uids:
283
- query = self._filter_values(
284
- query=query,
285
- model_endpoints_table=model_endpoints_table,
286
- key_filter=mm_schemas.EventFieldType.UID,
287
- filtered_values=uids,
288
- combined=False,
289
- )
290
- if top_level:
291
- node_ep = str(mm_schemas.EndpointType.NODE_EP.value)
292
- router_ep = str(mm_schemas.EndpointType.ROUTER.value)
293
- endpoint_types = [node_ep, router_ep]
294
- query = self._filter_values(
295
- query=query,
296
- model_endpoints_table=model_endpoints_table,
297
- key_filter=mm_schemas.EventFieldType.ENDPOINT_TYPE,
298
- filtered_values=endpoint_types,
299
- combined=False,
300
- )
301
- # Convert the results from the DB into a ModelEndpoint object and append it to the model endpoints list
302
- for endpoint_record in query.all():
303
- endpoint_dict = endpoint_record.to_dict()
304
-
305
- # Filter labels
306
- if labels and not self._validate_labels(
307
- endpoint_dict=endpoint_dict, labels=labels
308
- ):
309
- continue
310
-
311
- if not include_stats:
312
- # Exclude these fields when listing model endpoints to avoid returning too much data (ML-6594)
313
- # TODO: Remove stats from table schema (ML-7196)
314
- endpoint_dict.pop(mm_schemas.EventFieldType.FEATURE_STATS)
315
- endpoint_dict.pop(mm_schemas.EventFieldType.CURRENT_STATS)
316
-
317
- endpoint_list.append(endpoint_dict)
318
-
319
- return endpoint_list
320
-
321
- @staticmethod
322
- def _convert_to_datetime(event: dict[str, typing.Any], key: str) -> None:
323
- if isinstance(event[key], str):
324
- event[key] = datetime.datetime.fromisoformat(event[key])
325
- event[key] = event[key].astimezone(tz=datetime.timezone.utc)
326
-
327
- def _create_tables_if_not_exist(self):
328
- self._init_tables()
329
-
330
- for table in self._tables:
331
- # Create table if not exist. The `metadata` contains the `ModelEndpointsTable`
332
- db_name = make_url(self._sql_connection_string).database
333
- if not self.engine.has_table(table):
334
- logger.info(f"Creating table {table} on {db_name} db.")
335
- self._tables[table].metadata.create_all(bind=self.engine)
336
- else:
337
- logger.info(f"Table {table} already exists on {db_name} db.")
338
-
339
- @staticmethod
340
- def _filter_values(
341
- query: sqlalchemy.orm.query.Query,
342
- model_endpoints_table: sqlalchemy.Table,
343
- key_filter: str,
344
- filtered_values: list,
345
- combined=True,
346
- ) -> sqlalchemy.orm.query.Query:
347
- """Filtering the SQL query object according to the provided filters.
348
-
349
- :param query: SQLAlchemy ORM query object. Includes the SELECT statements generated by the ORM
350
- for getting the model endpoint data from the SQL table.
351
- :param model_endpoints_table: SQLAlchemy table object that represents the model endpoints table.
352
- :param key_filter: Key column to filter by.
353
- :param filtered_values: List of values to filter the query the result.
354
- :param combined: If true, then apply AND operator on the filtered values list. Otherwise, apply OR
355
- operator.
356
-
357
- return: SQLAlchemy ORM query object that represents the updated query with the provided
358
- filters.
359
- """
360
-
361
- if combined and len(filtered_values) > 1:
362
- raise mlrun.errors.MLRunInvalidArgumentError(
363
- "Can't apply combined policy with multiple values"
364
- )
365
-
366
- if not combined:
367
- return query.filter(
368
- model_endpoints_table.c[key_filter].in_(filtered_values)
369
- )
370
-
371
- # Generating a tuple with the relevant filters
372
- filter_query = []
373
- for _filter in filtered_values:
374
- filter_query.append(model_endpoints_table.c[key_filter] == _filter)
375
-
376
- # Apply AND operator on the SQL query object with the filters tuple
377
- return query.filter(sqlalchemy.and_(*filter_query))
378
-
379
- def delete_model_endpoints_resources(self) -> None:
380
- """
381
- Delete all the model monitoring resources of the project in the SQL tables.
382
- """
383
- logger.debug(
384
- "Deleting model monitoring endpoints resources from the SQL tables",
385
- project=self.project,
386
- )
387
- endpoints = self.list_model_endpoints()
388
-
389
- for endpoint_dict in endpoints:
390
- endpoint_id = endpoint_dict[mm_schemas.EventFieldType.UID]
391
- logger.debug(
392
- "Deleting model endpoint resources from the SQL tables",
393
- endpoint_id=endpoint_id,
394
- project=self.project,
395
- )
396
-
397
- # Delete model endpoint record
398
- self.delete_model_endpoint(endpoint_id=endpoint_id)
399
- logger.debug(
400
- "Successfully deleted model endpoint resources",
401
- endpoint_id=endpoint_id,
402
- project=self.project,
403
- )
404
-
405
- logger.debug(
406
- "Successfully deleted model monitoring endpoints resources from the SQL tables",
407
- project=self.project,
408
- )
@@ -1,13 +0,0 @@
1
- # Copyright 2024 Iguazio
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.