mlrun 1.10.0rc40__py3-none-any.whl → 1.11.0rc16__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/__init__.py +3 -2
- mlrun/__main__.py +0 -4
- mlrun/artifacts/dataset.py +2 -2
- mlrun/artifacts/plots.py +1 -1
- mlrun/{model_monitoring/db/tsdb/tdengine → auth}/__init__.py +2 -3
- mlrun/auth/nuclio.py +89 -0
- mlrun/auth/providers.py +429 -0
- mlrun/auth/utils.py +415 -0
- mlrun/common/constants.py +7 -0
- mlrun/common/model_monitoring/helpers.py +41 -4
- mlrun/common/runtimes/constants.py +28 -0
- mlrun/common/schemas/__init__.py +13 -3
- mlrun/common/schemas/alert.py +2 -2
- mlrun/common/schemas/api_gateway.py +3 -0
- mlrun/common/schemas/auth.py +10 -10
- mlrun/common/schemas/client_spec.py +4 -0
- mlrun/common/schemas/constants.py +25 -0
- mlrun/common/schemas/frontend_spec.py +1 -8
- mlrun/common/schemas/function.py +24 -0
- mlrun/common/schemas/hub.py +3 -2
- mlrun/common/schemas/model_monitoring/__init__.py +1 -1
- mlrun/common/schemas/model_monitoring/constants.py +2 -2
- mlrun/common/schemas/secret.py +17 -2
- mlrun/common/secrets.py +95 -1
- mlrun/common/types.py +10 -10
- mlrun/config.py +53 -15
- mlrun/data_types/infer.py +2 -2
- mlrun/datastore/__init__.py +2 -3
- mlrun/datastore/base.py +274 -10
- mlrun/datastore/datastore.py +1 -1
- mlrun/datastore/datastore_profile.py +49 -17
- mlrun/datastore/model_provider/huggingface_provider.py +6 -2
- mlrun/datastore/model_provider/model_provider.py +2 -2
- mlrun/datastore/model_provider/openai_provider.py +2 -2
- mlrun/datastore/s3.py +15 -16
- mlrun/datastore/sources.py +1 -1
- mlrun/datastore/store_resources.py +4 -4
- mlrun/datastore/storeytargets.py +16 -10
- mlrun/datastore/targets.py +1 -1
- mlrun/datastore/utils.py +16 -3
- mlrun/datastore/v3io.py +1 -1
- mlrun/db/base.py +36 -12
- mlrun/db/httpdb.py +316 -101
- mlrun/db/nopdb.py +29 -11
- mlrun/errors.py +4 -2
- mlrun/execution.py +11 -12
- mlrun/feature_store/api.py +1 -1
- mlrun/feature_store/common.py +1 -1
- mlrun/feature_store/feature_vector_utils.py +1 -1
- mlrun/feature_store/steps.py +8 -6
- mlrun/frameworks/_common/utils.py +3 -3
- mlrun/frameworks/_dl_common/loggers/logger.py +1 -1
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +2 -1
- mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +1 -1
- mlrun/frameworks/_ml_common/utils.py +2 -1
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +4 -3
- mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +2 -1
- mlrun/frameworks/onnx/dataset.py +2 -1
- mlrun/frameworks/onnx/mlrun_interface.py +2 -1
- mlrun/frameworks/pytorch/callbacks/logging_callback.py +5 -4
- mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +2 -1
- mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +2 -1
- mlrun/frameworks/pytorch/utils.py +2 -1
- mlrun/frameworks/sklearn/metric.py +2 -1
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +5 -4
- mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +2 -1
- mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +2 -1
- mlrun/hub/__init__.py +37 -0
- mlrun/hub/base.py +142 -0
- mlrun/hub/module.py +67 -76
- mlrun/hub/step.py +113 -0
- mlrun/launcher/base.py +2 -1
- mlrun/launcher/local.py +2 -1
- mlrun/model.py +12 -2
- mlrun/model_monitoring/__init__.py +0 -1
- mlrun/model_monitoring/api.py +2 -2
- mlrun/model_monitoring/applications/base.py +20 -6
- mlrun/model_monitoring/applications/context.py +1 -0
- mlrun/model_monitoring/controller.py +7 -17
- mlrun/model_monitoring/db/_schedules.py +2 -16
- mlrun/model_monitoring/db/_stats.py +2 -13
- mlrun/model_monitoring/db/tsdb/__init__.py +9 -7
- mlrun/model_monitoring/db/tsdb/base.py +2 -4
- mlrun/model_monitoring/db/tsdb/preaggregate.py +234 -0
- mlrun/model_monitoring/db/tsdb/stream_graph_steps.py +63 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_metrics_queries.py +414 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_predictions_queries.py +376 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_results_queries.py +590 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connection.py +434 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connector.py +541 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_operations.py +808 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_schema.py +502 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream.py +163 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream_graph_steps.py +60 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_dataframe_processor.py +141 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_query_builder.py +585 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/writer_graph_steps.py +73 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +4 -6
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +147 -79
- mlrun/model_monitoring/features_drift_table.py +2 -1
- mlrun/model_monitoring/helpers.py +2 -1
- mlrun/model_monitoring/stream_processing.py +18 -16
- mlrun/model_monitoring/writer.py +4 -3
- mlrun/package/__init__.py +2 -1
- mlrun/platforms/__init__.py +0 -44
- mlrun/platforms/iguazio.py +1 -1
- mlrun/projects/operations.py +11 -10
- mlrun/projects/project.py +81 -82
- mlrun/run.py +4 -7
- mlrun/runtimes/__init__.py +2 -204
- mlrun/runtimes/base.py +89 -21
- mlrun/runtimes/constants.py +225 -0
- mlrun/runtimes/daskjob.py +4 -2
- mlrun/runtimes/databricks_job/databricks_runtime.py +2 -1
- mlrun/runtimes/mounts.py +5 -0
- mlrun/runtimes/nuclio/__init__.py +12 -8
- mlrun/runtimes/nuclio/api_gateway.py +36 -6
- mlrun/runtimes/nuclio/application/application.py +200 -32
- mlrun/runtimes/nuclio/function.py +154 -49
- mlrun/runtimes/nuclio/serving.py +55 -42
- mlrun/runtimes/pod.py +59 -10
- mlrun/secrets.py +46 -2
- mlrun/serving/__init__.py +2 -0
- mlrun/serving/remote.py +5 -5
- mlrun/serving/routers.py +3 -3
- mlrun/serving/server.py +46 -43
- mlrun/serving/serving_wrapper.py +6 -2
- mlrun/serving/states.py +554 -207
- mlrun/serving/steps.py +1 -1
- mlrun/serving/system_steps.py +42 -33
- mlrun/track/trackers/mlflow_tracker.py +29 -31
- mlrun/utils/helpers.py +89 -16
- mlrun/utils/http.py +9 -2
- mlrun/utils/notifications/notification/git.py +1 -1
- mlrun/utils/notifications/notification/mail.py +39 -16
- mlrun/utils/notifications/notification_pusher.py +2 -2
- mlrun/utils/version/version.json +2 -2
- mlrun/utils/version/version.py +3 -4
- {mlrun-1.10.0rc40.dist-info → mlrun-1.11.0rc16.dist-info}/METADATA +39 -49
- {mlrun-1.10.0rc40.dist-info → mlrun-1.11.0rc16.dist-info}/RECORD +144 -130
- mlrun/db/auth_utils.py +0 -152
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +0 -343
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +0 -75
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +0 -281
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +0 -1368
- mlrun/model_monitoring/db/tsdb/tdengine/writer_graph_steps.py +0 -51
- {mlrun-1.10.0rc40.dist-info → mlrun-1.11.0rc16.dist-info}/WHEEL +0 -0
- {mlrun-1.10.0rc40.dist-info → mlrun-1.11.0rc16.dist-info}/entry_points.txt +0 -0
- {mlrun-1.10.0rc40.dist-info → mlrun-1.11.0rc16.dist-info}/licenses/LICENSE +0 -0
- {mlrun-1.10.0rc40.dist-info → mlrun-1.11.0rc16.dist-info}/top_level.txt +0 -0
|
@@ -28,7 +28,7 @@ class ObjectTSDBFactory(enum.Enum):
|
|
|
28
28
|
"""Enum class to handle the different TSDB connector type values for storing real time metrics"""
|
|
29
29
|
|
|
30
30
|
v3io_tsdb = "v3io-tsdb"
|
|
31
|
-
|
|
31
|
+
timescaledb = "postgresql"
|
|
32
32
|
|
|
33
33
|
def to_tsdb_connector(
|
|
34
34
|
self, project: str, profile: DatastoreProfile, **kwargs
|
|
@@ -50,12 +50,12 @@ class ObjectTSDBFactory(enum.Enum):
|
|
|
50
50
|
|
|
51
51
|
return V3IOTSDBConnector(project=project, **kwargs)
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
if self == self.timescaledb:
|
|
54
|
+
from .timescaledb.timescaledb_connector import TimescaleDBConnector
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
return TimescaleDBConnector(project=project, profile=profile, **kwargs)
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
raise mlrun.errors.MLRunInvalidMMStoreTypeError("Code should not reach here")
|
|
59
59
|
|
|
60
60
|
@classmethod
|
|
61
61
|
def _missing_(cls, value: typing.Any):
|
|
@@ -92,9 +92,11 @@ def get_tsdb_connector(
|
|
|
92
92
|
if isinstance(profile, mlrun.datastore.datastore_profile.DatastoreProfileV3io):
|
|
93
93
|
tsdb_connector_type = mlrun.common.schemas.model_monitoring.TSDBTarget.V3IO_TSDB
|
|
94
94
|
elif isinstance(
|
|
95
|
-
profile, mlrun.datastore.datastore_profile.
|
|
95
|
+
profile, mlrun.datastore.datastore_profile.DatastoreProfilePostgreSQL
|
|
96
96
|
):
|
|
97
|
-
tsdb_connector_type =
|
|
97
|
+
tsdb_connector_type = (
|
|
98
|
+
mlrun.common.schemas.model_monitoring.TSDBTarget.TimescaleDB
|
|
99
|
+
)
|
|
98
100
|
else:
|
|
99
101
|
extra_message = (
|
|
100
102
|
""
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
from abc import ABC, abstractmethod
|
|
16
16
|
from datetime import datetime, timedelta
|
|
17
|
-
from typing import
|
|
17
|
+
from typing import ClassVar, Literal, Optional, Union
|
|
18
18
|
|
|
19
19
|
import pandas as pd
|
|
20
20
|
import pydantic.v1
|
|
@@ -444,11 +444,9 @@ class TSDBConnector(ABC):
|
|
|
444
444
|
]
|
|
445
445
|
"""
|
|
446
446
|
|
|
447
|
-
|
|
447
|
+
def add_basic_metrics(
|
|
448
448
|
self,
|
|
449
449
|
model_endpoint_objects: list[mlrun.common.schemas.ModelEndpoint],
|
|
450
|
-
project: str,
|
|
451
|
-
run_in_threadpool: Callable,
|
|
452
450
|
metric_list: Optional[list[str]] = None,
|
|
453
451
|
) -> list[mlrun.common.schemas.ModelEndpoint]:
|
|
454
452
|
raise NotImplementedError()
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Copyright 2025 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 re
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from datetime import datetime, timedelta
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
import mlrun.errors
|
|
21
|
+
import mlrun.utils
|
|
22
|
+
|
|
23
|
+
# Compiled regex pattern for parsing time intervals (e.g., "1h", "10m", "1d", "1w", "1M")
|
|
24
|
+
_INTERVAL_PATTERN = re.compile(r"(\d+)([mhdwM])")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class PreAggregateConfig:
|
|
29
|
+
"""Configuration for pre-aggregated tables and retention policies."""
|
|
30
|
+
|
|
31
|
+
aggregate_intervals: list[str] = None
|
|
32
|
+
agg_functions: list[str] = None
|
|
33
|
+
retention_policy: dict[str, str] = None
|
|
34
|
+
|
|
35
|
+
def __post_init__(self):
|
|
36
|
+
if self.aggregate_intervals is None:
|
|
37
|
+
self.aggregate_intervals = ["10m", "1h", "6h", "1d", "1w", "1M"]
|
|
38
|
+
|
|
39
|
+
if self.agg_functions is None:
|
|
40
|
+
self.agg_functions = ["sum", "avg", "min", "max", "count", "last"]
|
|
41
|
+
|
|
42
|
+
if self.retention_policy is None:
|
|
43
|
+
self.retention_policy = {
|
|
44
|
+
"raw": "7d",
|
|
45
|
+
"10m": "30d",
|
|
46
|
+
"1h": "1y",
|
|
47
|
+
"6h": "1y",
|
|
48
|
+
"1d": "5y",
|
|
49
|
+
"1w": "5y",
|
|
50
|
+
"1M": "5y",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class PreAggregateManager:
|
|
55
|
+
"""Handles pre-aggregate validation, time alignment, and optimization decisions."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, pre_aggregate_config: Optional[PreAggregateConfig] = None):
|
|
58
|
+
"""
|
|
59
|
+
Initialize the pre-aggregate handler.
|
|
60
|
+
|
|
61
|
+
:param pre_aggregate_config: Configuration for pre-aggregated tables and operations.
|
|
62
|
+
If None, all pre-aggregate operations will be disabled.
|
|
63
|
+
"""
|
|
64
|
+
self._pre_aggregate_config = pre_aggregate_config
|
|
65
|
+
|
|
66
|
+
def validate_interval_and_function(
|
|
67
|
+
self, interval: Optional[str], agg_function: Optional[str]
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Validate that interval and aggregation function are available in pre-aggregate config."""
|
|
70
|
+
if not interval and not agg_function:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
if not self._pre_aggregate_config:
|
|
74
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
75
|
+
"Pre-aggregate configuration not available. Cannot use interval or agg_function parameters."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if interval and interval not in self._pre_aggregate_config.aggregate_intervals:
|
|
79
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
80
|
+
f"Interval '{interval}' not available in pre-aggregate configuration. "
|
|
81
|
+
f"Available intervals: {self._pre_aggregate_config.aggregate_intervals}"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if (
|
|
85
|
+
agg_function
|
|
86
|
+
and agg_function not in self._pre_aggregate_config.agg_functions
|
|
87
|
+
):
|
|
88
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
89
|
+
f"Aggregation function '{agg_function}' not available in pre-aggregate configuration. "
|
|
90
|
+
f"Available functions: {self._pre_aggregate_config.agg_functions}"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def can_use_pre_aggregates(
|
|
94
|
+
self, interval: Optional[str] = None, agg_funcs: Optional[list[str]] = None
|
|
95
|
+
) -> bool:
|
|
96
|
+
"""Check if pre-aggregates can be used for the given parameters."""
|
|
97
|
+
if not self._pre_aggregate_config or not interval:
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
if interval not in self._pre_aggregate_config.aggregate_intervals:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
if agg_funcs:
|
|
104
|
+
return all(
|
|
105
|
+
func in self._pre_aggregate_config.agg_functions for func in agg_funcs
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
def align_time_to_interval(
|
|
111
|
+
self, dt: datetime, interval: str, align_start: bool = True
|
|
112
|
+
) -> datetime:
|
|
113
|
+
"""Align datetime to interval boundaries."""
|
|
114
|
+
if not interval:
|
|
115
|
+
return dt
|
|
116
|
+
|
|
117
|
+
# Parse interval (e.g., "1h", "10m", "1d")
|
|
118
|
+
match = _INTERVAL_PATTERN.match(interval)
|
|
119
|
+
if not match:
|
|
120
|
+
return dt
|
|
121
|
+
|
|
122
|
+
amount, unit = int(match.group(1)), match.group(2)
|
|
123
|
+
|
|
124
|
+
# Get the start boundary for this interval
|
|
125
|
+
aligned_start = self._get_interval_start_boundary(dt, amount, unit)
|
|
126
|
+
|
|
127
|
+
if align_start:
|
|
128
|
+
return aligned_start
|
|
129
|
+
|
|
130
|
+
# For end alignment, add the interval duration to the start
|
|
131
|
+
return self._add_interval_to_datetime(aligned_start, amount, unit)
|
|
132
|
+
|
|
133
|
+
def _get_interval_start_boundary(
|
|
134
|
+
self, dt: datetime, amount: int, unit: str
|
|
135
|
+
) -> datetime:
|
|
136
|
+
"""Get the start boundary for the given interval."""
|
|
137
|
+
if unit == "m": # minutes
|
|
138
|
+
return dt.replace(second=0, microsecond=0) - timedelta(
|
|
139
|
+
minutes=dt.minute % amount
|
|
140
|
+
)
|
|
141
|
+
elif unit == "h": # hours
|
|
142
|
+
return dt.replace(minute=0, second=0, microsecond=0) - timedelta(
|
|
143
|
+
hours=dt.hour % amount
|
|
144
|
+
)
|
|
145
|
+
elif unit == "d": # days
|
|
146
|
+
return dt.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
147
|
+
elif unit == "w": # weeks
|
|
148
|
+
# Align to Monday (start of week)
|
|
149
|
+
days_since_monday = dt.weekday()
|
|
150
|
+
return (dt - timedelta(days=days_since_monday)).replace(
|
|
151
|
+
hour=0, minute=0, second=0, microsecond=0
|
|
152
|
+
)
|
|
153
|
+
elif unit == "M": # months (approximate)
|
|
154
|
+
return dt.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
|
155
|
+
|
|
156
|
+
return dt
|
|
157
|
+
|
|
158
|
+
def _add_interval_to_datetime(
|
|
159
|
+
self, dt: datetime, amount: int, unit: str
|
|
160
|
+
) -> datetime:
|
|
161
|
+
"""Add the specified interval amount to a datetime."""
|
|
162
|
+
if unit == "m": # minutes
|
|
163
|
+
return dt + timedelta(minutes=amount)
|
|
164
|
+
elif unit == "h": # hours
|
|
165
|
+
return dt + timedelta(hours=amount)
|
|
166
|
+
elif unit == "d": # days
|
|
167
|
+
return dt + timedelta(days=amount)
|
|
168
|
+
elif unit == "w": # weeks
|
|
169
|
+
return dt + timedelta(weeks=amount)
|
|
170
|
+
elif unit == "M": # months (approximate)
|
|
171
|
+
if dt.month == 12:
|
|
172
|
+
return dt.replace(year=dt.year + 1, month=1)
|
|
173
|
+
return dt.replace(month=dt.month + 1)
|
|
174
|
+
|
|
175
|
+
return dt
|
|
176
|
+
|
|
177
|
+
def align_time_range(
|
|
178
|
+
self, start: datetime, end: datetime, interval: Optional[str]
|
|
179
|
+
) -> tuple[datetime, datetime]:
|
|
180
|
+
"""Align both start and end times to interval boundaries."""
|
|
181
|
+
if not interval:
|
|
182
|
+
return start, end
|
|
183
|
+
|
|
184
|
+
aligned_start = self.align_time_to_interval(start, interval, align_start=True)
|
|
185
|
+
aligned_end = self.align_time_to_interval(end, interval, align_start=False)
|
|
186
|
+
|
|
187
|
+
return aligned_start, aligned_end
|
|
188
|
+
|
|
189
|
+
@staticmethod
|
|
190
|
+
def get_start_end(
|
|
191
|
+
start: Optional[datetime],
|
|
192
|
+
end: Optional[datetime],
|
|
193
|
+
) -> tuple[datetime, datetime]:
|
|
194
|
+
"""
|
|
195
|
+
Utility function for TSDB start/end format validation.
|
|
196
|
+
|
|
197
|
+
:param start: Either None or datetime, None is handled as datetime.min(tz=timezone.utc)
|
|
198
|
+
:param end: Either None or datetime, None is handled as datetime.now(tz=timezone.utc)
|
|
199
|
+
:return: start datetime, end datetime
|
|
200
|
+
"""
|
|
201
|
+
start = start or mlrun.utils.datetime_min()
|
|
202
|
+
end = end or mlrun.utils.datetime_now()
|
|
203
|
+
if not (isinstance(start, datetime) and isinstance(end, datetime)):
|
|
204
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
205
|
+
"Both start and end must be datetime objects"
|
|
206
|
+
)
|
|
207
|
+
return start, end
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def config(self) -> Optional[PreAggregateConfig]:
|
|
211
|
+
"""Get the current pre-aggregate configuration."""
|
|
212
|
+
return self._pre_aggregate_config
|
|
213
|
+
|
|
214
|
+
def is_pre_aggregates_enabled(self) -> bool:
|
|
215
|
+
"""Check if pre-aggregates are enabled (config is provided)."""
|
|
216
|
+
return self._pre_aggregate_config is not None
|
|
217
|
+
|
|
218
|
+
def get_available_intervals(self) -> list[str]:
|
|
219
|
+
"""Get list of available intervals for pre-aggregation."""
|
|
220
|
+
if not self._pre_aggregate_config:
|
|
221
|
+
return []
|
|
222
|
+
return self._pre_aggregate_config.aggregate_intervals.copy()
|
|
223
|
+
|
|
224
|
+
def get_available_functions(self) -> list[str]:
|
|
225
|
+
"""Get list of available aggregation functions."""
|
|
226
|
+
if not self._pre_aggregate_config:
|
|
227
|
+
return []
|
|
228
|
+
return self._pre_aggregate_config.agg_functions.copy()
|
|
229
|
+
|
|
230
|
+
def get_retention_policy(self) -> dict[str, str]:
|
|
231
|
+
"""Get the retention policy configuration."""
|
|
232
|
+
if not self._pre_aggregate_config:
|
|
233
|
+
return {}
|
|
234
|
+
return self._pre_aggregate_config.retention_policy.copy()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
from datetime import datetime
|
|
16
|
+
|
|
17
|
+
import mlrun.feature_store.steps
|
|
18
|
+
from mlrun.common.schemas.model_monitoring import EventFieldType
|
|
19
|
+
|
|
20
|
+
# Import the authoritative database schema constant
|
|
21
|
+
from mlrun.model_monitoring.db.tsdb.timescaledb.timescaledb_schema import (
|
|
22
|
+
MODEL_ERROR_MAX_LENGTH,
|
|
23
|
+
)
|
|
24
|
+
from mlrun.utils import logger
|
|
25
|
+
|
|
26
|
+
# Error truncation log message
|
|
27
|
+
ERROR_TRUNCATION_MESSAGE = "Error message truncated for storage"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BaseErrorExtractor(mlrun.feature_store.steps.MapClass):
|
|
31
|
+
"""
|
|
32
|
+
Shared error extraction implementation for TimescaleDB.
|
|
33
|
+
|
|
34
|
+
Prepares events for insertion into the errors TSDB table.
|
|
35
|
+
V3io has different requirements and uses its own implementation.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def do(self, event):
|
|
39
|
+
error = str(event.get("error"))
|
|
40
|
+
original_error_length = len(error)
|
|
41
|
+
if len(error) > MODEL_ERROR_MAX_LENGTH:
|
|
42
|
+
error = error[-MODEL_ERROR_MAX_LENGTH:]
|
|
43
|
+
logger.warning(
|
|
44
|
+
ERROR_TRUNCATION_MESSAGE,
|
|
45
|
+
endpoint_id=event.get(EventFieldType.ENDPOINT_ID),
|
|
46
|
+
function_uri=event.get(EventFieldType.FUNCTION_URI),
|
|
47
|
+
original_error_length=original_error_length,
|
|
48
|
+
max_length=MODEL_ERROR_MAX_LENGTH,
|
|
49
|
+
truncated_error=error,
|
|
50
|
+
)
|
|
51
|
+
timestamp = datetime.fromisoformat(event.get("when"))
|
|
52
|
+
endpoint_id = event[EventFieldType.ENDPOINT_ID]
|
|
53
|
+
result_event = {
|
|
54
|
+
EventFieldType.MODEL_ERROR: error,
|
|
55
|
+
EventFieldType.ERROR_TYPE: EventFieldType.INFER_ERROR,
|
|
56
|
+
EventFieldType.ENDPOINT_ID: endpoint_id,
|
|
57
|
+
EventFieldType.TIME: timestamp,
|
|
58
|
+
EventFieldType.PROJECT: event[EventFieldType.FUNCTION_URI].split("/")[0],
|
|
59
|
+
EventFieldType.TABLE_COLUMN: "_err_"
|
|
60
|
+
+ event.get(EventFieldType.ENDPOINT_ID),
|
|
61
|
+
}
|
|
62
|
+
logger.info("Write error to errors TSDB table", event=result_event)
|
|
63
|
+
return result_event
|