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.

Files changed (150) hide show
  1. mlrun/__init__.py +3 -2
  2. mlrun/__main__.py +0 -4
  3. mlrun/artifacts/dataset.py +2 -2
  4. mlrun/artifacts/plots.py +1 -1
  5. mlrun/{model_monitoring/db/tsdb/tdengine → auth}/__init__.py +2 -3
  6. mlrun/auth/nuclio.py +89 -0
  7. mlrun/auth/providers.py +429 -0
  8. mlrun/auth/utils.py +415 -0
  9. mlrun/common/constants.py +7 -0
  10. mlrun/common/model_monitoring/helpers.py +41 -4
  11. mlrun/common/runtimes/constants.py +28 -0
  12. mlrun/common/schemas/__init__.py +13 -3
  13. mlrun/common/schemas/alert.py +2 -2
  14. mlrun/common/schemas/api_gateway.py +3 -0
  15. mlrun/common/schemas/auth.py +10 -10
  16. mlrun/common/schemas/client_spec.py +4 -0
  17. mlrun/common/schemas/constants.py +25 -0
  18. mlrun/common/schemas/frontend_spec.py +1 -8
  19. mlrun/common/schemas/function.py +24 -0
  20. mlrun/common/schemas/hub.py +3 -2
  21. mlrun/common/schemas/model_monitoring/__init__.py +1 -1
  22. mlrun/common/schemas/model_monitoring/constants.py +2 -2
  23. mlrun/common/schemas/secret.py +17 -2
  24. mlrun/common/secrets.py +95 -1
  25. mlrun/common/types.py +10 -10
  26. mlrun/config.py +53 -15
  27. mlrun/data_types/infer.py +2 -2
  28. mlrun/datastore/__init__.py +2 -3
  29. mlrun/datastore/base.py +274 -10
  30. mlrun/datastore/datastore.py +1 -1
  31. mlrun/datastore/datastore_profile.py +49 -17
  32. mlrun/datastore/model_provider/huggingface_provider.py +6 -2
  33. mlrun/datastore/model_provider/model_provider.py +2 -2
  34. mlrun/datastore/model_provider/openai_provider.py +2 -2
  35. mlrun/datastore/s3.py +15 -16
  36. mlrun/datastore/sources.py +1 -1
  37. mlrun/datastore/store_resources.py +4 -4
  38. mlrun/datastore/storeytargets.py +16 -10
  39. mlrun/datastore/targets.py +1 -1
  40. mlrun/datastore/utils.py +16 -3
  41. mlrun/datastore/v3io.py +1 -1
  42. mlrun/db/base.py +36 -12
  43. mlrun/db/httpdb.py +316 -101
  44. mlrun/db/nopdb.py +29 -11
  45. mlrun/errors.py +4 -2
  46. mlrun/execution.py +11 -12
  47. mlrun/feature_store/api.py +1 -1
  48. mlrun/feature_store/common.py +1 -1
  49. mlrun/feature_store/feature_vector_utils.py +1 -1
  50. mlrun/feature_store/steps.py +8 -6
  51. mlrun/frameworks/_common/utils.py +3 -3
  52. mlrun/frameworks/_dl_common/loggers/logger.py +1 -1
  53. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +2 -1
  54. mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +1 -1
  55. mlrun/frameworks/_ml_common/utils.py +2 -1
  56. mlrun/frameworks/auto_mlrun/auto_mlrun.py +4 -3
  57. mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +2 -1
  58. mlrun/frameworks/onnx/dataset.py +2 -1
  59. mlrun/frameworks/onnx/mlrun_interface.py +2 -1
  60. mlrun/frameworks/pytorch/callbacks/logging_callback.py +5 -4
  61. mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +2 -1
  62. mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +2 -1
  63. mlrun/frameworks/pytorch/utils.py +2 -1
  64. mlrun/frameworks/sklearn/metric.py +2 -1
  65. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +5 -4
  66. mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +2 -1
  67. mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +2 -1
  68. mlrun/hub/__init__.py +37 -0
  69. mlrun/hub/base.py +142 -0
  70. mlrun/hub/module.py +67 -76
  71. mlrun/hub/step.py +113 -0
  72. mlrun/launcher/base.py +2 -1
  73. mlrun/launcher/local.py +2 -1
  74. mlrun/model.py +12 -2
  75. mlrun/model_monitoring/__init__.py +0 -1
  76. mlrun/model_monitoring/api.py +2 -2
  77. mlrun/model_monitoring/applications/base.py +20 -6
  78. mlrun/model_monitoring/applications/context.py +1 -0
  79. mlrun/model_monitoring/controller.py +7 -17
  80. mlrun/model_monitoring/db/_schedules.py +2 -16
  81. mlrun/model_monitoring/db/_stats.py +2 -13
  82. mlrun/model_monitoring/db/tsdb/__init__.py +9 -7
  83. mlrun/model_monitoring/db/tsdb/base.py +2 -4
  84. mlrun/model_monitoring/db/tsdb/preaggregate.py +234 -0
  85. mlrun/model_monitoring/db/tsdb/stream_graph_steps.py +63 -0
  86. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_metrics_queries.py +414 -0
  87. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_predictions_queries.py +376 -0
  88. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_results_queries.py +590 -0
  89. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connection.py +434 -0
  90. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connector.py +541 -0
  91. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_operations.py +808 -0
  92. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_schema.py +502 -0
  93. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream.py +163 -0
  94. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream_graph_steps.py +60 -0
  95. mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_dataframe_processor.py +141 -0
  96. mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_query_builder.py +585 -0
  97. mlrun/model_monitoring/db/tsdb/timescaledb/writer_graph_steps.py +73 -0
  98. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +4 -6
  99. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +147 -79
  100. mlrun/model_monitoring/features_drift_table.py +2 -1
  101. mlrun/model_monitoring/helpers.py +2 -1
  102. mlrun/model_monitoring/stream_processing.py +18 -16
  103. mlrun/model_monitoring/writer.py +4 -3
  104. mlrun/package/__init__.py +2 -1
  105. mlrun/platforms/__init__.py +0 -44
  106. mlrun/platforms/iguazio.py +1 -1
  107. mlrun/projects/operations.py +11 -10
  108. mlrun/projects/project.py +81 -82
  109. mlrun/run.py +4 -7
  110. mlrun/runtimes/__init__.py +2 -204
  111. mlrun/runtimes/base.py +89 -21
  112. mlrun/runtimes/constants.py +225 -0
  113. mlrun/runtimes/daskjob.py +4 -2
  114. mlrun/runtimes/databricks_job/databricks_runtime.py +2 -1
  115. mlrun/runtimes/mounts.py +5 -0
  116. mlrun/runtimes/nuclio/__init__.py +12 -8
  117. mlrun/runtimes/nuclio/api_gateway.py +36 -6
  118. mlrun/runtimes/nuclio/application/application.py +200 -32
  119. mlrun/runtimes/nuclio/function.py +154 -49
  120. mlrun/runtimes/nuclio/serving.py +55 -42
  121. mlrun/runtimes/pod.py +59 -10
  122. mlrun/secrets.py +46 -2
  123. mlrun/serving/__init__.py +2 -0
  124. mlrun/serving/remote.py +5 -5
  125. mlrun/serving/routers.py +3 -3
  126. mlrun/serving/server.py +46 -43
  127. mlrun/serving/serving_wrapper.py +6 -2
  128. mlrun/serving/states.py +554 -207
  129. mlrun/serving/steps.py +1 -1
  130. mlrun/serving/system_steps.py +42 -33
  131. mlrun/track/trackers/mlflow_tracker.py +29 -31
  132. mlrun/utils/helpers.py +89 -16
  133. mlrun/utils/http.py +9 -2
  134. mlrun/utils/notifications/notification/git.py +1 -1
  135. mlrun/utils/notifications/notification/mail.py +39 -16
  136. mlrun/utils/notifications/notification_pusher.py +2 -2
  137. mlrun/utils/version/version.json +2 -2
  138. mlrun/utils/version/version.py +3 -4
  139. {mlrun-1.10.0rc40.dist-info → mlrun-1.11.0rc16.dist-info}/METADATA +39 -49
  140. {mlrun-1.10.0rc40.dist-info → mlrun-1.11.0rc16.dist-info}/RECORD +144 -130
  141. mlrun/db/auth_utils.py +0 -152
  142. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +0 -343
  143. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +0 -75
  144. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +0 -281
  145. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +0 -1368
  146. mlrun/model_monitoring/db/tsdb/tdengine/writer_graph_steps.py +0 -51
  147. {mlrun-1.10.0rc40.dist-info → mlrun-1.11.0rc16.dist-info}/WHEEL +0 -0
  148. {mlrun-1.10.0rc40.dist-info → mlrun-1.11.0rc16.dist-info}/entry_points.txt +0 -0
  149. {mlrun-1.10.0rc40.dist-info → mlrun-1.11.0rc16.dist-info}/licenses/LICENSE +0 -0
  150. {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
- tdengine = "tdengine"
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
- # Assuming TDEngine connector if connector type is not V3IO TSDB.
54
- # Update these lines once there are more than two connector types.
53
+ if self == self.timescaledb:
54
+ from .timescaledb.timescaledb_connector import TimescaleDBConnector
55
55
 
56
- from .tdengine.tdengine_connector import TDEngineConnector
56
+ return TimescaleDBConnector(project=project, profile=profile, **kwargs)
57
57
 
58
- return TDEngineConnector(project=project, profile=profile, **kwargs)
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.DatastoreProfileTDEngine
95
+ profile, mlrun.datastore.datastore_profile.DatastoreProfilePostgreSQL
96
96
  ):
97
- tsdb_connector_type = mlrun.common.schemas.model_monitoring.TSDBTarget.TDEngine
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 Callable, ClassVar, Literal, Optional, Union
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
- async def add_basic_metrics(
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