mlrun 1.7.0rc17__py3-none-any.whl → 1.7.0rc19__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 (90) hide show
  1. mlrun/__main__.py +5 -2
  2. mlrun/alerts/alert.py +1 -1
  3. mlrun/artifacts/manager.py +5 -1
  4. mlrun/common/constants.py +64 -3
  5. mlrun/common/formatters/__init__.py +16 -0
  6. mlrun/common/formatters/base.py +59 -0
  7. mlrun/common/formatters/function.py +41 -0
  8. mlrun/common/runtimes/constants.py +32 -4
  9. mlrun/common/schemas/__init__.py +1 -2
  10. mlrun/common/schemas/alert.py +31 -9
  11. mlrun/common/schemas/api_gateway.py +52 -0
  12. mlrun/common/schemas/client_spec.py +1 -0
  13. mlrun/common/schemas/frontend_spec.py +1 -0
  14. mlrun/common/schemas/function.py +4 -0
  15. mlrun/common/schemas/model_monitoring/__init__.py +9 -4
  16. mlrun/common/schemas/model_monitoring/constants.py +22 -8
  17. mlrun/common/schemas/model_monitoring/grafana.py +9 -5
  18. mlrun/common/schemas/model_monitoring/model_endpoints.py +17 -6
  19. mlrun/config.py +9 -2
  20. mlrun/data_types/to_pandas.py +5 -5
  21. mlrun/datastore/datastore.py +6 -2
  22. mlrun/datastore/redis.py +2 -2
  23. mlrun/datastore/s3.py +5 -0
  24. mlrun/datastore/sources.py +106 -7
  25. mlrun/datastore/store_resources.py +5 -1
  26. mlrun/datastore/targets.py +5 -4
  27. mlrun/datastore/utils.py +42 -0
  28. mlrun/db/base.py +5 -1
  29. mlrun/db/httpdb.py +22 -3
  30. mlrun/db/nopdb.py +5 -1
  31. mlrun/errors.py +6 -0
  32. mlrun/execution.py +16 -6
  33. mlrun/feature_store/ingestion.py +7 -6
  34. mlrun/feature_store/retrieval/conversion.py +5 -5
  35. mlrun/feature_store/retrieval/job.py +7 -3
  36. mlrun/feature_store/retrieval/spark_merger.py +2 -1
  37. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +2 -2
  38. mlrun/frameworks/parallel_coordinates.py +2 -1
  39. mlrun/frameworks/tf_keras/__init__.py +4 -1
  40. mlrun/launcher/client.py +4 -2
  41. mlrun/launcher/local.py +8 -2
  42. mlrun/launcher/remote.py +8 -2
  43. mlrun/model.py +5 -1
  44. mlrun/model_monitoring/db/stores/__init__.py +0 -2
  45. mlrun/model_monitoring/db/stores/base/store.py +16 -4
  46. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +43 -21
  47. mlrun/model_monitoring/db/stores/sqldb/models/base.py +32 -2
  48. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +25 -5
  49. mlrun/model_monitoring/db/stores/sqldb/models/sqlite.py +5 -0
  50. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +235 -166
  51. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +190 -91
  52. mlrun/model_monitoring/db/tsdb/__init__.py +35 -6
  53. mlrun/model_monitoring/db/tsdb/base.py +232 -38
  54. mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
  55. mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
  56. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +240 -0
  57. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +45 -0
  58. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +397 -0
  59. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +292 -104
  60. mlrun/model_monitoring/helpers.py +45 -0
  61. mlrun/model_monitoring/stream_processing.py +7 -4
  62. mlrun/model_monitoring/writer.py +50 -20
  63. mlrun/package/utils/_formatter.py +2 -2
  64. mlrun/projects/operations.py +8 -5
  65. mlrun/projects/pipelines.py +42 -15
  66. mlrun/projects/project.py +55 -14
  67. mlrun/render.py +8 -5
  68. mlrun/runtimes/base.py +2 -1
  69. mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
  70. mlrun/runtimes/local.py +4 -1
  71. mlrun/runtimes/nuclio/api_gateway.py +32 -8
  72. mlrun/runtimes/nuclio/application/application.py +3 -3
  73. mlrun/runtimes/nuclio/function.py +1 -4
  74. mlrun/runtimes/utils.py +5 -6
  75. mlrun/serving/server.py +2 -1
  76. mlrun/utils/async_http.py +25 -5
  77. mlrun/utils/helpers.py +28 -7
  78. mlrun/utils/logger.py +28 -1
  79. mlrun/utils/notifications/notification/__init__.py +14 -9
  80. mlrun/utils/notifications/notification/slack.py +27 -7
  81. mlrun/utils/notifications/notification_pusher.py +47 -42
  82. mlrun/utils/v3io_clients.py +0 -1
  83. mlrun/utils/version/version.json +2 -2
  84. {mlrun-1.7.0rc17.dist-info → mlrun-1.7.0rc19.dist-info}/METADATA +9 -4
  85. {mlrun-1.7.0rc17.dist-info → mlrun-1.7.0rc19.dist-info}/RECORD +89 -82
  86. mlrun/model_monitoring/db/v3io_tsdb_reader.py +0 -134
  87. {mlrun-1.7.0rc17.dist-info → mlrun-1.7.0rc19.dist-info}/LICENSE +0 -0
  88. {mlrun-1.7.0rc17.dist-info → mlrun-1.7.0rc19.dist-info}/WHEEL +0 -0
  89. {mlrun-1.7.0rc17.dist-info → mlrun-1.7.0rc19.dist-info}/entry_points.txt +0 -0
  90. {mlrun-1.7.0rc17.dist-info → mlrun-1.7.0rc19.dist-info}/top_level.txt +0 -0
mlrun/execution.py CHANGED
@@ -22,6 +22,7 @@ import yaml
22
22
  from dateutil import parser
23
23
 
24
24
  import mlrun
25
+ import mlrun.common.constants as mlrun_constants
25
26
  from mlrun.artifacts import ModelArtifact
26
27
  from mlrun.datastore.store_resources import get_store_resource
27
28
  from mlrun.errors import MLRunInvalidArgumentError
@@ -129,7 +130,9 @@ class MLClientCtx:
129
130
  @property
130
131
  def tag(self):
131
132
  """Run tag (uid or workflow id if exists)"""
132
- return self._labels.get("workflow") or self._uid
133
+ return (
134
+ self._labels.get(mlrun_constants.MLRunInternalLabels.workflow) or self._uid
135
+ )
133
136
 
134
137
  @property
135
138
  def state(self):
@@ -329,8 +332,10 @@ class MLClientCtx:
329
332
  "uri": uri,
330
333
  "owner": get_in(self._labels, "owner"),
331
334
  }
332
- if "workflow" in self._labels:
333
- resp["workflow"] = self._labels["workflow"]
335
+ if mlrun_constants.MLRunInternalLabels.workflow in self._labels:
336
+ resp[mlrun_constants.MLRunInternalLabels.workflow] = self._labels[
337
+ mlrun_constants.MLRunInternalLabels.workflow
338
+ ]
334
339
  return resp
335
340
 
336
341
  @classmethod
@@ -396,7 +401,7 @@ class MLClientCtx:
396
401
  self._set_input(k, v)
397
402
 
398
403
  if host and not is_api:
399
- self.set_label("host", host)
404
+ self.set_label(mlrun_constants.MLRunInternalLabels.host, host)
400
405
 
401
406
  start = get_in(attrs, "status.start_time")
402
407
  if start:
@@ -990,10 +995,15 @@ class MLClientCtx:
990
995
  # If it's a OpenMPI job, get the global rank and compare to the logging rank (worker) set in MLRun's
991
996
  # configuration:
992
997
  labels = self.labels
993
- if "host" in labels and labels.get("kind", "job") == "mpijob":
998
+ if (
999
+ mlrun_constants.MLRunInternalLabels.host in labels
1000
+ and labels.get(mlrun_constants.MLRunInternalLabels.kind, "job") == "mpijob"
1001
+ ):
994
1002
  # The host (pod name) of each worker is created by k8s, and by default it uses the rank number as the id in
995
1003
  # the following template: ...-worker-<rank>
996
- rank = int(labels["host"].rsplit("-", 1)[1])
1004
+ rank = int(
1005
+ labels[mlrun_constants.MLRunInternalLabels.host].rsplit("-", 1)[1]
1006
+ )
997
1007
  return rank == mlrun.mlconf.packagers.logging_worker
998
1008
 
999
1009
  # Single worker is always the logging worker:
@@ -17,6 +17,7 @@ import uuid
17
17
  import pandas as pd
18
18
 
19
19
  import mlrun
20
+ import mlrun.common.constants as mlrun_constants
20
21
  from mlrun.datastore.sources import get_source_from_dict, get_source_step
21
22
  from mlrun.datastore.targets import (
22
23
  add_target_steps,
@@ -263,13 +264,13 @@ def run_ingestion_job(name, featureset, run_config, schedule=None, spark_service
263
264
  out_path=featureset.spec.output_path,
264
265
  )
265
266
  task.spec.secret_sources = run_config.secret_sources
266
- task.set_label("job-type", "feature-ingest").set_label(
267
- "feature-set", featureset.uri
268
- )
267
+ task.set_label(
268
+ mlrun_constants.MLRunInternalLabels.job_type, "feature-ingest"
269
+ ).set_label("feature-set", featureset.uri)
269
270
  if run_config.owner:
270
- task.set_label("owner", run_config.owner).set_label(
271
- "v3io_user", run_config.owner
272
- )
271
+ task.set_label(
272
+ mlrun_constants.MLRunInternalLabels.owner, run_config.owner
273
+ ).set_label(mlrun_constants.MLRunInternalLabels.v3io_user, run_config.owner)
273
274
 
274
275
  # set run UID and save in the feature set status (linking the features et to the job)
275
276
  task.metadata.uid = uuid.uuid4().hex
@@ -168,10 +168,10 @@ class PandasConversionMixin:
168
168
  column_counter = Counter(self.columns)
169
169
 
170
170
  dtype = [None] * len(self.schema)
171
- for fieldIdx, field in enumerate(self.schema):
171
+ for field_idx, field in enumerate(self.schema):
172
172
  # For duplicate column name, we use `iloc` to access it.
173
173
  if column_counter[field.name] > 1:
174
- pandas_col = pdf.iloc[:, fieldIdx]
174
+ pandas_col = pdf.iloc[:, field_idx]
175
175
  else:
176
176
  pandas_col = pdf[field.name]
177
177
 
@@ -187,12 +187,12 @@ class PandasConversionMixin:
187
187
  and field.nullable
188
188
  and pandas_col.isnull().any()
189
189
  ):
190
- dtype[fieldIdx] = pandas_type
190
+ dtype[field_idx] = pandas_type
191
191
  # Ensure we fall back to nullable numpy types, even when whole column is null:
192
192
  if isinstance(field.dataType, IntegralType) and pandas_col.isnull().any():
193
- dtype[fieldIdx] = np.float64
193
+ dtype[field_idx] = np.float64
194
194
  if isinstance(field.dataType, BooleanType) and pandas_col.isnull().any():
195
- dtype[fieldIdx] = object
195
+ dtype[field_idx] = object
196
196
 
197
197
  df = pd.DataFrame()
198
198
  for index, t in enumerate(dtype):
@@ -15,6 +15,7 @@
15
15
  import uuid
16
16
 
17
17
  import mlrun
18
+ import mlrun.common.constants as mlrun_constants
18
19
  from mlrun.config import config as mlconf
19
20
  from mlrun.model import DataTargetBase, new_task
20
21
  from mlrun.runtimes.function_reference import FunctionReference
@@ -122,7 +123,9 @@ def run_merge_job(
122
123
  inputs={"entity_rows": entity_rows} if entity_rows is not None else {},
123
124
  )
124
125
  task.spec.secret_sources = run_config.secret_sources
125
- task.set_label("job-type", "feature-merge").set_label("feature-vector", vector.uri)
126
+ task.set_label(
127
+ mlrun_constants.MLRunInternalLabels.job_type, "feature-merge"
128
+ ).set_label(mlrun_constants.MLRunInternalLabels.feature_vector, vector.uri)
126
129
  task.metadata.uid = uuid.uuid4().hex
127
130
  vector.status.run_uri = task.metadata.uid
128
131
  vector.save()
@@ -198,7 +201,8 @@ import mlrun.feature_store.retrieval
198
201
  from mlrun.datastore.targets import get_target_driver
199
202
  def merge_handler(context, vector_uri, target, entity_rows=None,
200
203
  entity_timestamp_column=None, drop_columns=None, with_indexes=None, query=None,
201
- engine_args=None, order_by=None, start_time=None, end_time=None, timestamp_for_filtering=None):
204
+ engine_args=None, order_by=None, start_time=None, end_time=None, timestamp_for_filtering=None,
205
+ additional_filters=None):
202
206
  vector = context.get_store_resource(vector_uri)
203
207
  store_target = get_target_driver(target, vector)
204
208
  if entity_rows:
@@ -208,7 +212,7 @@ def merge_handler(context, vector_uri, target, entity_rows=None,
208
212
  merger = mlrun.feature_store.retrieval.{{{engine}}}(vector, **(engine_args or {}))
209
213
  merger.start(entity_rows, entity_timestamp_column, store_target, drop_columns, with_indexes=with_indexes,
210
214
  query=query, order_by=order_by, start_time=start_time, end_time=end_time,
211
- timestamp_for_filtering=timestamp_for_filtering)
215
+ timestamp_for_filtering=timestamp_for_filtering, additional_filters=additional_filters)
212
216
 
213
217
  target = vector.status.targets[store_target.name].to_dict()
214
218
  context.log_result('feature_vector', vector.uri)
@@ -12,6 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  #
15
+
15
16
  import pandas as pd
16
17
  import semver
17
18
 
@@ -252,13 +253,13 @@ class SparkFeatureMerger(BaseMerger):
252
253
  # handling case where there are multiple feature sets and user creates vector where
253
254
  # entity_timestamp_column is from a specific feature set (can't be entity timestamp)
254
255
  source_driver = mlrun.datastore.sources.source_kind_to_driver[source_kind]
255
-
256
256
  source = source_driver(
257
257
  name=self.vector.metadata.name,
258
258
  path=source_path,
259
259
  time_field=time_column,
260
260
  start_time=start_time,
261
261
  end_time=end_time,
262
+ additional_filters=additional_filters,
262
263
  **source_kwargs,
263
264
  )
264
265
 
@@ -648,13 +648,13 @@ class TensorboardLogger(Logger, Generic[DLTypes.WeightType]):
648
648
  if isinstance(value, list):
649
649
  if len(value) == 0:
650
650
  return ""
651
- text = "\n" + yaml.dump(value)
651
+ text = "\n" + yaml.safe_dump(value)
652
652
  text = " \n".join([" " * tabs + line for line in text.splitlines()])
653
653
  return text
654
654
  if isinstance(value, dict):
655
655
  if len(value) == 0:
656
656
  return ""
657
- text = yaml.dump(value)
657
+ text = yaml.safe_dump(value)
658
658
  text = " \n".join(
659
659
  [" " * tabs + "- " + line for line in text.splitlines()]
660
660
  )
@@ -295,7 +295,7 @@ def compare_db_runs(
295
295
  iter=False,
296
296
  start_time_from: datetime = None,
297
297
  hide_identical: bool = True,
298
- exclude: list = [],
298
+ exclude: list = None,
299
299
  show=None,
300
300
  colorscale: str = "Blues",
301
301
  filename=None,
@@ -332,6 +332,7 @@ def compare_db_runs(
332
332
  **query_args,
333
333
  )
334
334
 
335
+ exclude = exclude or []
335
336
  runs_df = _runs_list_to_df(runs_list)
336
337
  plot_as_html = gen_pcp_plot(
337
338
  runs_df,
@@ -18,6 +18,7 @@ from typing import Any, Union
18
18
  from tensorflow import keras
19
19
 
20
20
  import mlrun
21
+ import mlrun.common.constants as mlrun_constants
21
22
 
22
23
  from .callbacks import MLRunLoggingCallback, TensorboardLoggingCallback
23
24
  from .mlrun_interface import TFKerasMLRunInterface
@@ -126,7 +127,9 @@ def apply_mlrun(
126
127
  # # Use horovod:
127
128
  if use_horovod is None:
128
129
  use_horovod = (
129
- context.labels.get("kind", "") == "mpijob" if context is not None else False
130
+ context.labels.get(mlrun_constants.MLRunInternalLabels.kind, "") == "mpijob"
131
+ if context is not None
132
+ else False
130
133
  )
131
134
 
132
135
  # Create a model handler:
mlrun/launcher/client.py CHANGED
@@ -16,6 +16,7 @@ from typing import Optional
16
16
 
17
17
  import IPython
18
18
 
19
+ import mlrun.common.constants as mlrun_constants
19
20
  import mlrun.errors
20
21
  import mlrun.launcher.base as launcher
21
22
  import mlrun.lists
@@ -69,13 +70,14 @@ class ClientBaseLauncher(launcher.BaseLauncher, abc.ABC):
69
70
  def _store_function(
70
71
  runtime: "mlrun.runtimes.BaseRuntime", run: "mlrun.run.RunObject"
71
72
  ):
72
- run.metadata.labels["kind"] = runtime.kind
73
+ run.metadata.labels[mlrun_constants.MLRunInternalLabels.kind] = runtime.kind
73
74
  mlrun.runtimes.utils.enrich_run_labels(
74
75
  run.metadata.labels, [mlrun.common.runtimes.constants.RunLabels.owner]
75
76
  )
76
77
  if run.spec.output_path:
77
78
  run.spec.output_path = run.spec.output_path.replace(
78
- "{{run.user}}", run.metadata.labels["owner"]
79
+ "{{run.user}}",
80
+ run.metadata.labels[mlrun_constants.MLRunInternalLabels.owner],
79
81
  )
80
82
  db = runtime._get_db()
81
83
  if db and runtime.kind != "handler":
mlrun/launcher/local.py CHANGED
@@ -15,6 +15,7 @@ import os
15
15
  import pathlib
16
16
  from typing import Callable, Optional, Union
17
17
 
18
+ import mlrun.common.constants as mlrun_constants
18
19
  import mlrun.common.schemas.schedule
19
20
  import mlrun.errors
20
21
  import mlrun.launcher.client as launcher
@@ -132,8 +133,13 @@ class ClientLocalLauncher(launcher.ClientBaseLauncher):
132
133
  runtime: "mlrun.runtimes.BaseRuntime",
133
134
  run: Optional[Union["mlrun.run.RunTemplate", "mlrun.run.RunObject"]] = None,
134
135
  ):
135
- if "V3IO_USERNAME" in os.environ and "v3io_user" not in run.metadata.labels:
136
- run.metadata.labels["v3io_user"] = os.environ.get("V3IO_USERNAME")
136
+ if (
137
+ "V3IO_USERNAME" in os.environ
138
+ and mlrun_constants.MLRunInternalLabels.v3io_user not in run.metadata.labels
139
+ ):
140
+ run.metadata.labels[mlrun_constants.MLRunInternalLabels.v3io_user] = (
141
+ os.environ.get("V3IO_USERNAME")
142
+ )
137
143
 
138
144
  # store function object in db unless running from within a run pod
139
145
  if not runtime.is_child:
mlrun/launcher/remote.py CHANGED
@@ -17,6 +17,7 @@ from typing import Optional, Union
17
17
  import pandas as pd
18
18
  import requests
19
19
 
20
+ import mlrun.common.constants as mlrun_constants
20
21
  import mlrun.common.schemas.schedule
21
22
  import mlrun.db
22
23
  import mlrun.errors
@@ -100,8 +101,13 @@ class ClientRemoteLauncher(launcher.ClientBaseLauncher):
100
101
  if runtime.verbose:
101
102
  logger.info(f"runspec:\n{run.to_yaml()}")
102
103
 
103
- if "V3IO_USERNAME" in os.environ and "v3io_user" not in run.metadata.labels:
104
- run.metadata.labels["v3io_user"] = os.environ.get("V3IO_USERNAME")
104
+ if (
105
+ "V3IO_USERNAME" in os.environ
106
+ and mlrun_constants.MLRunInternalLabels.v3io_user not in run.metadata.labels
107
+ ):
108
+ run.metadata.labels[mlrun_constants.MLRunInternalLabels.v3io_user] = (
109
+ os.environ.get("V3IO_USERNAME")
110
+ )
105
111
 
106
112
  logger.info(
107
113
  "Storing function",
mlrun/model.py CHANGED
@@ -27,6 +27,7 @@ from typing import Any, Optional, Union
27
27
  import pydantic.error_wrappers
28
28
 
29
29
  import mlrun
30
+ import mlrun.common.constants as mlrun_constants
30
31
  import mlrun.common.schemas.notification
31
32
 
32
33
  from .utils import (
@@ -770,7 +771,10 @@ class RunMetadata(ModelObj):
770
771
  def is_workflow_runner(self):
771
772
  if not self.labels:
772
773
  return False
773
- return self.labels.get("job-type", "") == "workflow-runner"
774
+ return (
775
+ self.labels.get(mlrun_constants.MLRunInternalLabels.job_type, "")
776
+ == "workflow-runner"
777
+ )
774
778
 
775
779
 
776
780
  class HyperParamStrategies:
@@ -12,8 +12,6 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- # flake8: noqa - this is until we take care of the F401 violations with respect to __all__ & sphinx
16
-
17
15
  import enum
18
16
  import typing
19
17
  import warnings
@@ -15,7 +15,7 @@
15
15
  import typing
16
16
  from abc import ABC, abstractmethod
17
17
 
18
- import mlrun.common.schemas.model_monitoring.constants as mm_constants
18
+ import mlrun.common.schemas.model_monitoring as mm_schemas
19
19
 
20
20
 
21
21
  class StoreBase(ABC):
@@ -115,8 +115,8 @@ class StoreBase(ABC):
115
115
  def write_application_event(
116
116
  self,
117
117
  event: dict[str, typing.Any],
118
- kind: mm_constants.WriterEventKind = mm_constants.WriterEventKind.RESULT,
119
- ):
118
+ kind: mm_schemas.WriterEventKind = mm_schemas.WriterEventKind.RESULT,
119
+ ) -> None:
120
120
  """
121
121
  Write a new event in the target table.
122
122
 
@@ -125,7 +125,6 @@ class StoreBase(ABC):
125
125
  object.
126
126
  :param kind: The type of the event, can be either "result" or "metric".
127
127
  """
128
- pass
129
128
 
130
129
  @abstractmethod
131
130
  def get_last_analyzed(self, endpoint_id: str, application_name: str) -> int:
@@ -157,3 +156,16 @@ class StoreBase(ABC):
157
156
 
158
157
  """
159
158
  pass
159
+
160
+ @abstractmethod
161
+ def get_model_endpoint_metrics(
162
+ self, endpoint_id: str, type: mm_schemas.ModelEndpointMonitoringMetricType
163
+ ) -> list[mm_schemas.ModelEndpointMonitoringMetric]:
164
+ """
165
+ Get the model monitoring results and metrics of the requested model endpoint.
166
+
167
+ :param: endpoint_id: The model endpoint identifier.
168
+ :param: type: The type of the requested metrics ("result" or "metric").
169
+
170
+ :return: A list of the available metrics.
171
+ """
@@ -12,38 +12,60 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- from typing import Optional, Union
15
+ from functools import partial
16
+ from typing import Optional, TypeVar, Union
16
17
 
18
+ from .mysql import ApplicationMetricsTable as MySQLApplicationMetricsTable
17
19
  from .mysql import ApplicationResultTable as MySQLApplicationResultTable
18
20
  from .mysql import ModelEndpointsTable as MySQLModelEndpointsTable
19
21
  from .mysql import MonitoringSchedulesTable as MySQLMonitoringSchedulesTable
22
+ from .sqlite import ApplicationMetricsTable as SQLiteApplicationMetricsTable
20
23
  from .sqlite import ApplicationResultTable as SQLiteApplicationResultTable
21
24
  from .sqlite import ModelEndpointsTable as SQLiteModelEndpointsTable
22
25
  from .sqlite import MonitoringSchedulesTable as SQLiteMonitoringSchedulesTable
23
26
 
27
+ MySQLTableType = TypeVar("MySQLTableType")
28
+ SQLiteTableType = TypeVar("SQLiteTableType")
24
29
 
25
- def _get_model_endpoints_table(
26
- connection_string: Optional[str] = None,
27
- ) -> Union[type[MySQLModelEndpointsTable], type[SQLiteModelEndpointsTable]]:
28
- """Return ModelEndpointsTable based on the provided connection string"""
29
- if connection_string and "mysql:" in connection_string:
30
- return MySQLModelEndpointsTable
31
- return SQLiteModelEndpointsTable
30
+ _MYSQL_SCHEME = "mysql:"
32
31
 
33
32
 
34
- def _get_application_result_table(
33
+ def _get_sql_table(
34
+ *,
35
+ mysql_table: MySQLTableType,
36
+ sqlite_table: SQLiteTableType,
35
37
  connection_string: Optional[str] = None,
36
- ) -> Union[type[MySQLApplicationResultTable], type[SQLiteApplicationResultTable]]:
37
- """Return ModelEndpointsTable based on the provided connection string"""
38
- if connection_string and "mysql:" in connection_string:
39
- return MySQLApplicationResultTable
40
- return SQLiteApplicationResultTable
38
+ ) -> Union[MySQLTableType, SQLiteTableType]:
39
+ """
40
+ Return a SQLAlchemy table for MySQL or SQLite according to the connection string.
41
+ Note: this function should not be directly used in other modules.
42
+ """
43
+ if connection_string and _MYSQL_SCHEME in connection_string:
44
+ return mysql_table
45
+ return sqlite_table
41
46
 
42
47
 
43
- def _get_monitoring_schedules_table(
44
- connection_string: Optional[str] = None,
45
- ) -> Union[type[MySQLMonitoringSchedulesTable], type[SQLiteMonitoringSchedulesTable]]:
46
- """Return ModelEndpointsTable based on the provided connection string"""
47
- if connection_string and "mysql:" in connection_string:
48
- return MySQLMonitoringSchedulesTable
49
- return SQLiteMonitoringSchedulesTable
48
+ _get_model_endpoints_table = partial(
49
+ _get_sql_table,
50
+ mysql_table=MySQLModelEndpointsTable,
51
+ sqlite_table=SQLiteModelEndpointsTable,
52
+ )
53
+
54
+
55
+ _get_application_result_table = partial(
56
+ _get_sql_table,
57
+ mysql_table=MySQLApplicationResultTable,
58
+ sqlite_table=SQLiteApplicationResultTable,
59
+ )
60
+
61
+ _get_application_metrics_table = partial(
62
+ _get_sql_table,
63
+ mysql_table=MySQLApplicationMetricsTable,
64
+ sqlite_table=SQLiteApplicationMetricsTable,
65
+ )
66
+
67
+ _get_monitoring_schedules_table = partial(
68
+ _get_sql_table,
69
+ mysql_table=MySQLMonitoringSchedulesTable,
70
+ sqlite_table=SQLiteMonitoringSchedulesTable,
71
+ )
@@ -24,6 +24,7 @@ from sqlalchemy import (
24
24
  from mlrun.common.schemas.model_monitoring import (
25
25
  EventFieldType,
26
26
  FileTargetKind,
27
+ MetricData,
27
28
  ResultData,
28
29
  SchedulingKeys,
29
30
  WriterEvent,
@@ -89,11 +90,11 @@ class ModelEndpointsBaseTable(BaseModel):
89
90
  metrics = Column(EventFieldType.METRICS, Text)
90
91
  first_request = Column(
91
92
  EventFieldType.FIRST_REQUEST,
92
- TIMESTAMP,
93
+ TIMESTAMP(timezone=True),
93
94
  )
94
95
  last_request = Column(
95
96
  EventFieldType.LAST_REQUEST,
96
- TIMESTAMP,
97
+ TIMESTAMP(timezone=True),
97
98
  )
98
99
 
99
100
 
@@ -135,6 +136,35 @@ class ApplicationResultBaseTable(BaseModel):
135
136
  current_stats = Column(ResultData.CURRENT_STATS, Text)
136
137
 
137
138
 
139
+ class ApplicationMetricsBaseTable(BaseModel):
140
+ __tablename__ = FileTargetKind.APP_METRICS
141
+
142
+ uid = Column(EventFieldType.UID, String(120), primary_key=True)
143
+ application_name = Column(
144
+ WriterEvent.APPLICATION_NAME,
145
+ String(40),
146
+ nullable=True,
147
+ )
148
+ endpoint_id = Column(
149
+ WriterEvent.ENDPOINT_ID,
150
+ String(40),
151
+ nullable=True,
152
+ )
153
+ start_infer_time = Column(
154
+ WriterEvent.START_INFER_TIME,
155
+ TIMESTAMP(timezone=True),
156
+ )
157
+ end_infer_time = Column(
158
+ WriterEvent.END_INFER_TIME,
159
+ TIMESTAMP(timezone=True),
160
+ )
161
+ metric_name = Column(
162
+ MetricData.METRIC_NAME,
163
+ String(40),
164
+ )
165
+ metric_value = Column(MetricData.METRIC_VALUE, Float)
166
+
167
+
138
168
  class MonitoringSchedulesBaseTable(BaseModel):
139
169
  __tablename__ = FileTargetKind.MONITORING_SCHEDULES
140
170
 
@@ -22,6 +22,7 @@ from mlrun.common.schemas.model_monitoring import (
22
22
  )
23
23
 
24
24
  from .base import (
25
+ ApplicationMetricsBaseTable,
25
26
  ApplicationResultBaseTable,
26
27
  ModelEndpointsBaseTable,
27
28
  MonitoringSchedulesBaseTable,
@@ -33,22 +34,29 @@ Base = declarative_base()
33
34
  class ModelEndpointsTable(Base, ModelEndpointsBaseTable):
34
35
  first_request = Column(
35
36
  EventFieldType.FIRST_REQUEST,
36
- sqlalchemy.dialects.mysql.TIMESTAMP(fsp=3),
37
+ sqlalchemy.dialects.mysql.TIMESTAMP(fsp=3, timezone=True),
37
38
  )
38
39
  last_request = Column(
39
40
  EventFieldType.LAST_REQUEST,
40
- sqlalchemy.dialects.mysql.TIMESTAMP(fsp=3),
41
+ sqlalchemy.dialects.mysql.TIMESTAMP(fsp=3, timezone=True),
41
42
  )
42
43
 
43
44
 
44
- class ApplicationResultTable(Base, ApplicationResultBaseTable):
45
+ class _ApplicationResultOrMetric:
46
+ """
47
+ This class sets common columns of `ApplicationResultTable` and `ApplicationMetricsTable`
48
+ to the correct values in MySQL.
49
+ Note: This class must come before the base tables in the inheritance order to override
50
+ the relevant columns.
51
+ """
52
+
45
53
  start_infer_time = Column(
46
54
  WriterEvent.START_INFER_TIME,
47
- sqlalchemy.dialects.mysql.TIMESTAMP(fsp=3),
55
+ sqlalchemy.dialects.mysql.TIMESTAMP(fsp=3, timezone=True),
48
56
  )
49
57
  end_infer_time = Column(
50
58
  WriterEvent.END_INFER_TIME,
51
- sqlalchemy.dialects.mysql.TIMESTAMP(fsp=3),
59
+ sqlalchemy.dialects.mysql.TIMESTAMP(fsp=3, timezone=True),
52
60
  )
53
61
 
54
62
  @declared_attr
@@ -59,6 +67,18 @@ class ApplicationResultTable(Base, ApplicationResultBaseTable):
59
67
  )
60
68
 
61
69
 
70
+ class ApplicationResultTable(
71
+ Base, _ApplicationResultOrMetric, ApplicationResultBaseTable
72
+ ):
73
+ pass
74
+
75
+
76
+ class ApplicationMetricsTable(
77
+ Base, _ApplicationResultOrMetric, ApplicationMetricsBaseTable
78
+ ):
79
+ pass
80
+
81
+
62
82
  class MonitoringSchedulesTable(Base, MonitoringSchedulesBaseTable):
63
83
  @declared_attr
64
84
  def endpoint_id(cls):
@@ -15,6 +15,7 @@
15
15
  from sqlalchemy.ext.declarative import declarative_base
16
16
 
17
17
  from .base import (
18
+ ApplicationMetricsBaseTable,
18
19
  ApplicationResultBaseTable,
19
20
  ModelEndpointsBaseTable,
20
21
  MonitoringSchedulesBaseTable,
@@ -31,5 +32,9 @@ class ApplicationResultTable(Base, ApplicationResultBaseTable):
31
32
  pass
32
33
 
33
34
 
35
+ class ApplicationMetricsTable(Base, ApplicationMetricsBaseTable):
36
+ pass
37
+
38
+
34
39
  class MonitoringSchedulesTable(Base, MonitoringSchedulesBaseTable):
35
40
  pass