mlrun 1.10.0rc18__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 (167) hide show
  1. mlrun/__init__.py +24 -3
  2. mlrun/__main__.py +0 -4
  3. mlrun/artifacts/dataset.py +2 -2
  4. mlrun/artifacts/document.py +6 -1
  5. mlrun/artifacts/llm_prompt.py +21 -15
  6. mlrun/artifacts/model.py +3 -3
  7. mlrun/artifacts/plots.py +1 -1
  8. mlrun/{model_monitoring/db/tsdb/tdengine → auth}/__init__.py +2 -3
  9. mlrun/auth/nuclio.py +89 -0
  10. mlrun/auth/providers.py +429 -0
  11. mlrun/auth/utils.py +415 -0
  12. mlrun/common/constants.py +14 -0
  13. mlrun/common/model_monitoring/helpers.py +123 -0
  14. mlrun/common/runtimes/constants.py +28 -0
  15. mlrun/common/schemas/__init__.py +14 -3
  16. mlrun/common/schemas/alert.py +2 -2
  17. mlrun/common/schemas/api_gateway.py +3 -0
  18. mlrun/common/schemas/auth.py +12 -10
  19. mlrun/common/schemas/client_spec.py +4 -0
  20. mlrun/common/schemas/constants.py +25 -0
  21. mlrun/common/schemas/frontend_spec.py +1 -8
  22. mlrun/common/schemas/function.py +34 -0
  23. mlrun/common/schemas/hub.py +33 -20
  24. mlrun/common/schemas/model_monitoring/__init__.py +2 -1
  25. mlrun/common/schemas/model_monitoring/constants.py +12 -15
  26. mlrun/common/schemas/model_monitoring/functions.py +13 -4
  27. mlrun/common/schemas/model_monitoring/model_endpoints.py +11 -0
  28. mlrun/common/schemas/pipeline.py +1 -1
  29. mlrun/common/schemas/secret.py +17 -2
  30. mlrun/common/secrets.py +95 -1
  31. mlrun/common/types.py +10 -10
  32. mlrun/config.py +69 -19
  33. mlrun/data_types/infer.py +2 -2
  34. mlrun/datastore/__init__.py +12 -5
  35. mlrun/datastore/azure_blob.py +162 -47
  36. mlrun/datastore/base.py +274 -10
  37. mlrun/datastore/datastore.py +7 -2
  38. mlrun/datastore/datastore_profile.py +84 -22
  39. mlrun/datastore/model_provider/huggingface_provider.py +225 -41
  40. mlrun/datastore/model_provider/mock_model_provider.py +87 -0
  41. mlrun/datastore/model_provider/model_provider.py +206 -74
  42. mlrun/datastore/model_provider/openai_provider.py +226 -66
  43. mlrun/datastore/s3.py +39 -18
  44. mlrun/datastore/sources.py +1 -1
  45. mlrun/datastore/store_resources.py +4 -4
  46. mlrun/datastore/storeytargets.py +17 -12
  47. mlrun/datastore/targets.py +1 -1
  48. mlrun/datastore/utils.py +25 -6
  49. mlrun/datastore/v3io.py +1 -1
  50. mlrun/db/base.py +63 -32
  51. mlrun/db/httpdb.py +373 -153
  52. mlrun/db/nopdb.py +54 -21
  53. mlrun/errors.py +4 -2
  54. mlrun/execution.py +66 -25
  55. mlrun/feature_store/api.py +1 -1
  56. mlrun/feature_store/common.py +1 -1
  57. mlrun/feature_store/feature_vector_utils.py +1 -1
  58. mlrun/feature_store/steps.py +8 -6
  59. mlrun/frameworks/_common/utils.py +3 -3
  60. mlrun/frameworks/_dl_common/loggers/logger.py +1 -1
  61. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +2 -1
  62. mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +1 -1
  63. mlrun/frameworks/_ml_common/utils.py +2 -1
  64. mlrun/frameworks/auto_mlrun/auto_mlrun.py +4 -3
  65. mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +2 -1
  66. mlrun/frameworks/onnx/dataset.py +2 -1
  67. mlrun/frameworks/onnx/mlrun_interface.py +2 -1
  68. mlrun/frameworks/pytorch/callbacks/logging_callback.py +5 -4
  69. mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +2 -1
  70. mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +2 -1
  71. mlrun/frameworks/pytorch/utils.py +2 -1
  72. mlrun/frameworks/sklearn/metric.py +2 -1
  73. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +5 -4
  74. mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +2 -1
  75. mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +2 -1
  76. mlrun/hub/__init__.py +52 -0
  77. mlrun/hub/base.py +142 -0
  78. mlrun/hub/module.py +172 -0
  79. mlrun/hub/step.py +113 -0
  80. mlrun/k8s_utils.py +105 -16
  81. mlrun/launcher/base.py +15 -7
  82. mlrun/launcher/local.py +4 -1
  83. mlrun/model.py +14 -4
  84. mlrun/model_monitoring/__init__.py +0 -1
  85. mlrun/model_monitoring/api.py +65 -28
  86. mlrun/model_monitoring/applications/__init__.py +1 -1
  87. mlrun/model_monitoring/applications/base.py +299 -128
  88. mlrun/model_monitoring/applications/context.py +2 -4
  89. mlrun/model_monitoring/controller.py +132 -58
  90. mlrun/model_monitoring/db/_schedules.py +38 -29
  91. mlrun/model_monitoring/db/_stats.py +6 -16
  92. mlrun/model_monitoring/db/tsdb/__init__.py +9 -7
  93. mlrun/model_monitoring/db/tsdb/base.py +29 -9
  94. mlrun/model_monitoring/db/tsdb/preaggregate.py +234 -0
  95. mlrun/model_monitoring/db/tsdb/stream_graph_steps.py +63 -0
  96. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_metrics_queries.py +414 -0
  97. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_predictions_queries.py +376 -0
  98. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_results_queries.py +590 -0
  99. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connection.py +434 -0
  100. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connector.py +541 -0
  101. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_operations.py +808 -0
  102. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_schema.py +502 -0
  103. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream.py +163 -0
  104. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream_graph_steps.py +60 -0
  105. mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_dataframe_processor.py +141 -0
  106. mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_query_builder.py +585 -0
  107. mlrun/model_monitoring/db/tsdb/timescaledb/writer_graph_steps.py +73 -0
  108. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +20 -9
  109. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +235 -51
  110. mlrun/model_monitoring/features_drift_table.py +2 -1
  111. mlrun/model_monitoring/helpers.py +30 -6
  112. mlrun/model_monitoring/stream_processing.py +34 -28
  113. mlrun/model_monitoring/writer.py +224 -4
  114. mlrun/package/__init__.py +2 -1
  115. mlrun/platforms/__init__.py +0 -43
  116. mlrun/platforms/iguazio.py +8 -4
  117. mlrun/projects/operations.py +17 -11
  118. mlrun/projects/pipelines.py +2 -2
  119. mlrun/projects/project.py +187 -123
  120. mlrun/run.py +95 -21
  121. mlrun/runtimes/__init__.py +2 -186
  122. mlrun/runtimes/base.py +103 -25
  123. mlrun/runtimes/constants.py +225 -0
  124. mlrun/runtimes/daskjob.py +5 -2
  125. mlrun/runtimes/databricks_job/databricks_runtime.py +2 -1
  126. mlrun/runtimes/local.py +5 -2
  127. mlrun/runtimes/mounts.py +20 -2
  128. mlrun/runtimes/nuclio/__init__.py +12 -7
  129. mlrun/runtimes/nuclio/api_gateway.py +36 -6
  130. mlrun/runtimes/nuclio/application/application.py +339 -40
  131. mlrun/runtimes/nuclio/function.py +222 -72
  132. mlrun/runtimes/nuclio/serving.py +132 -42
  133. mlrun/runtimes/pod.py +213 -21
  134. mlrun/runtimes/utils.py +49 -9
  135. mlrun/secrets.py +99 -14
  136. mlrun/serving/__init__.py +2 -0
  137. mlrun/serving/remote.py +84 -11
  138. mlrun/serving/routers.py +26 -44
  139. mlrun/serving/server.py +138 -51
  140. mlrun/serving/serving_wrapper.py +6 -2
  141. mlrun/serving/states.py +997 -283
  142. mlrun/serving/steps.py +62 -0
  143. mlrun/serving/system_steps.py +149 -95
  144. mlrun/serving/v2_serving.py +9 -10
  145. mlrun/track/trackers/mlflow_tracker.py +29 -31
  146. mlrun/utils/helpers.py +292 -94
  147. mlrun/utils/http.py +9 -2
  148. mlrun/utils/notifications/notification/base.py +18 -0
  149. mlrun/utils/notifications/notification/git.py +3 -5
  150. mlrun/utils/notifications/notification/mail.py +39 -16
  151. mlrun/utils/notifications/notification/slack.py +2 -4
  152. mlrun/utils/notifications/notification/webhook.py +2 -5
  153. mlrun/utils/notifications/notification_pusher.py +3 -3
  154. mlrun/utils/version/version.json +2 -2
  155. mlrun/utils/version/version.py +3 -4
  156. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/METADATA +63 -74
  157. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/RECORD +161 -143
  158. mlrun/api/schemas/__init__.py +0 -259
  159. mlrun/db/auth_utils.py +0 -152
  160. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +0 -344
  161. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +0 -75
  162. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +0 -281
  163. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +0 -1266
  164. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/WHEEL +0 -0
  165. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/entry_points.txt +0 -0
  166. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/licenses/LICENSE +0 -0
  167. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,414 @@
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
+ from typing import Optional, Union
17
+
18
+ import pandas as pd
19
+
20
+ import mlrun
21
+ import mlrun.common.schemas.model_monitoring as mm_schemas
22
+ import mlrun.errors
23
+ from mlrun.model_monitoring.db.tsdb.timescaledb.timescaledb_connection import (
24
+ Statement,
25
+ )
26
+ from mlrun.model_monitoring.db.tsdb.timescaledb.utils.timescaledb_query_builder import (
27
+ TimescaleDBQueryBuilder,
28
+ )
29
+
30
+
31
+ class TimescaleDBMetricsQueries:
32
+ """
33
+ Query class containing metrics-related query methods for TimescaleDB.
34
+
35
+ Can be used as a mixin or standalone instance with proper initialization.
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ project: Optional[str] = None,
41
+ connection=None,
42
+ pre_aggregate_manager=None,
43
+ tables: Optional[dict] = None,
44
+ ):
45
+ """
46
+ Initialize TimescaleDB metrics query handler.
47
+
48
+ :param project: Project name
49
+ :param connection: TimescaleDB connection instance
50
+ :param pre_aggregate_manager: PreAggregateManager instance
51
+ :param tables: Dictionary of table schemas
52
+ """
53
+ self.project = project
54
+ self._connection = connection
55
+ self._pre_aggregate_manager = pre_aggregate_manager
56
+ self.tables = tables
57
+
58
+ def get_model_endpoint_real_time_metrics(
59
+ self,
60
+ endpoint_id: str,
61
+ metrics: list[str],
62
+ start: str,
63
+ end: str,
64
+ interval: Optional[str] = None,
65
+ agg_function: Optional[str] = None,
66
+ ) -> dict[str, list[tuple[str, float]]]:
67
+ """Get real-time metrics with optional pre-aggregate optimization."""
68
+
69
+ # Validate that metrics are provided
70
+ if not metrics:
71
+ raise mlrun.errors.MLRunInvalidArgumentError(
72
+ "Metric names must be provided"
73
+ )
74
+
75
+ # Prepare time range with validation and ISO conversion using helper
76
+ start_dt, end_dt, interval = (
77
+ TimescaleDBQueryBuilder.prepare_time_range_with_validation(
78
+ self._pre_aggregate_manager, start, end, interval, agg_function
79
+ )
80
+ )
81
+
82
+ table_schema = self.tables[mm_schemas.TimescaleDBTables.METRICS]
83
+
84
+ # Build query including metric names for filtering and grouping
85
+ columns = [
86
+ table_schema.time_column,
87
+ mm_schemas.MetricData.METRIC_NAME,
88
+ mm_schemas.MetricData.METRIC_VALUE,
89
+ ]
90
+
91
+ # Build filters: endpoint + requested metrics
92
+ endpoint_filter = TimescaleDBQueryBuilder.build_endpoint_filter(endpoint_id)
93
+ metrics_filter = TimescaleDBQueryBuilder.build_metrics_filter_from_names(
94
+ metrics
95
+ )
96
+ combined_filter = TimescaleDBQueryBuilder.combine_filters(
97
+ [endpoint_filter, metrics_filter]
98
+ )
99
+
100
+ # Use fallback pattern for potential pre-aggregate compatibility issues
101
+ def build_pre_agg_query():
102
+ return table_schema._get_records_query(
103
+ start=start_dt,
104
+ end=end_dt,
105
+ columns_to_filter=columns,
106
+ filter_query=combined_filter,
107
+ interval=interval,
108
+ agg_funcs=[agg_function] if agg_function else None,
109
+ use_pre_aggregates=True,
110
+ )
111
+
112
+ def build_raw_query():
113
+ return table_schema._get_records_query(
114
+ start=start_dt,
115
+ end=end_dt,
116
+ columns_to_filter=columns,
117
+ filter_query=combined_filter,
118
+ )
119
+
120
+ # Column mapping rules for pre-aggregate results (if needed)
121
+ column_mapping_rules = {
122
+ mm_schemas.MetricData.METRIC_NAME: [mm_schemas.MetricData.METRIC_NAME],
123
+ mm_schemas.MetricData.METRIC_VALUE: [mm_schemas.MetricData.METRIC_VALUE],
124
+ table_schema.time_column: [table_schema.time_column],
125
+ }
126
+
127
+ df = self._connection.execute_with_fallback(
128
+ self._pre_aggregate_manager,
129
+ build_pre_agg_query,
130
+ build_raw_query,
131
+ interval=interval,
132
+ agg_funcs=[agg_function] if agg_function else None,
133
+ column_mapping_rules=column_mapping_rules,
134
+ debug_name="get_model_endpoint_real_time_metrics",
135
+ )
136
+
137
+ # Process DataFrame result into expected format: {metric_name: [(timestamp, value), ...]}
138
+ metrics_data = {metric_name: [] for metric_name in metrics}
139
+
140
+ if not df.empty:
141
+ for _, row in df.iterrows():
142
+ timestamp = row[table_schema.time_column]
143
+ metric_name = row[mm_schemas.MetricData.METRIC_NAME]
144
+ value = row[mm_schemas.MetricData.METRIC_VALUE]
145
+
146
+ # Only include requested metrics
147
+ if metric_name in metrics_data:
148
+ metrics_data[metric_name].append(
149
+ (timestamp.isoformat(), float(value))
150
+ )
151
+
152
+ return metrics_data
153
+
154
+ def read_metrics_data_impl(
155
+ self,
156
+ *,
157
+ endpoint_id: Optional[str] = None,
158
+ start: datetime,
159
+ end: datetime,
160
+ metrics: Optional[list[mm_schemas.ModelEndpointMonitoringMetric]] = None,
161
+ timestamp_column: Optional[str] = None,
162
+ ) -> pd.DataFrame:
163
+ """Read metrics data from TimescaleDB (metrics table only) - returns DataFrame.
164
+
165
+ :param endpoint_id: Endpoint ID to filter by, or None to get all endpoints
166
+ :param start: Start time
167
+ :param end: End time
168
+ :param metrics: List of metrics to filter by, or None to get all metrics
169
+ :param timestamp_column: Optional timestamp column to use for time filtering
170
+ :return: DataFrame with metrics data
171
+ """
172
+
173
+ table_schema = self.tables[mm_schemas.TimescaleDBTables.METRICS]
174
+ name_column = mm_schemas.MetricData.METRIC_NAME
175
+ value_column = mm_schemas.MetricData.METRIC_VALUE
176
+ columns = [
177
+ table_schema.time_column,
178
+ mm_schemas.WriterEvent.START_INFER_TIME,
179
+ mm_schemas.WriterEvent.ENDPOINT_ID,
180
+ mm_schemas.WriterEvent.APPLICATION_NAME,
181
+ name_column,
182
+ value_column,
183
+ ]
184
+
185
+ # Build metrics condition using query builder utilities (accepts None)
186
+ metrics_condition = TimescaleDBQueryBuilder.build_metrics_filter(metrics)
187
+ endpoint_filter = TimescaleDBQueryBuilder.build_endpoint_filter(endpoint_id)
188
+
189
+ # Combine filters using query builder utilities
190
+ filters = [endpoint_filter, metrics_condition]
191
+ filter_query = TimescaleDBQueryBuilder.combine_filters(filters)
192
+
193
+ # Use shared utility for consistent query building with fallback
194
+ df = TimescaleDBQueryBuilder.build_read_data_with_fallback(
195
+ connection=self._connection,
196
+ pre_aggregate_manager=self._pre_aggregate_manager,
197
+ table_schema=table_schema,
198
+ start=start,
199
+ end=end,
200
+ columns=columns,
201
+ filter_query=filter_query,
202
+ name_column=name_column,
203
+ value_column=value_column,
204
+ debug_name="read_metrics_data",
205
+ timestamp_column=timestamp_column,
206
+ )
207
+
208
+ if not df.empty:
209
+ df[table_schema.time_column] = pd.to_datetime(df[table_schema.time_column])
210
+ df.set_index(table_schema.time_column, inplace=True)
211
+
212
+ return df
213
+
214
+ def get_metrics_metadata(
215
+ self,
216
+ endpoint_id: Union[str, list[str]],
217
+ start: Optional[datetime] = None,
218
+ end: Optional[datetime] = None,
219
+ interval: Optional[str] = None,
220
+ ) -> pd.DataFrame:
221
+ """Get metrics metadata with optional pre-aggregate optimization."""
222
+
223
+ # Prepare time range and interval (no auto-determination since interval passed in)
224
+ start, end, interval = TimescaleDBQueryBuilder.prepare_time_range_and_interval(
225
+ self._pre_aggregate_manager,
226
+ start,
227
+ end,
228
+ interval,
229
+ auto_determine_interval=False,
230
+ )
231
+
232
+ table_schema = self.tables[mm_schemas.TimescaleDBTables.METRICS]
233
+ filter_query = TimescaleDBQueryBuilder.build_endpoint_filter(endpoint_id)
234
+
235
+ columns = [
236
+ mm_schemas.WriterEvent.APPLICATION_NAME,
237
+ mm_schemas.MetricData.METRIC_NAME,
238
+ mm_schemas.WriterEvent.ENDPOINT_ID,
239
+ ]
240
+
241
+ # Use fallback pattern for potential pre-aggregate compatibility issues
242
+ def build_pre_agg_query():
243
+ return table_schema._get_records_query(
244
+ start=start,
245
+ end=end,
246
+ columns_to_filter=columns,
247
+ filter_query=filter_query,
248
+ interval=interval,
249
+ use_pre_aggregates=True,
250
+ )
251
+
252
+ def build_raw_query():
253
+ return table_schema._get_records_query(
254
+ start=start,
255
+ end=end,
256
+ columns_to_filter=columns,
257
+ filter_query=filter_query,
258
+ )
259
+
260
+ # Column mapping rules for pre-aggregate results (if needed)
261
+ column_mapping_rules = {
262
+ mm_schemas.WriterEvent.APPLICATION_NAME: [
263
+ mm_schemas.WriterEvent.APPLICATION_NAME
264
+ ],
265
+ mm_schemas.MetricData.METRIC_NAME: [mm_schemas.MetricData.METRIC_NAME],
266
+ mm_schemas.WriterEvent.ENDPOINT_ID: [mm_schemas.WriterEvent.ENDPOINT_ID],
267
+ }
268
+
269
+ df = self._connection.execute_with_fallback(
270
+ self._pre_aggregate_manager,
271
+ build_pre_agg_query,
272
+ build_raw_query,
273
+ interval=interval,
274
+ agg_funcs=None,
275
+ column_mapping_rules=column_mapping_rules,
276
+ debug_name="get_metrics_metadata",
277
+ )
278
+
279
+ # Get distinct values
280
+ if not df.empty:
281
+ df = df.drop_duplicates()
282
+
283
+ return df
284
+
285
+ def calculate_latest_metrics(
286
+ self,
287
+ start: Optional[datetime] = None,
288
+ end: Optional[datetime] = None,
289
+ application_names: Optional[list[str]] = None,
290
+ ) -> list[
291
+ Union[mm_schemas.ApplicationResultRecord, mm_schemas.ApplicationMetricRecord]
292
+ ]:
293
+ """
294
+ Calculate the latest metrics and results across applications.
295
+
296
+ Returns a list of ApplicationResultRecord and ApplicationMetricRecord objects.
297
+
298
+ :param start: The start time of the query. Last 24 hours is used by default.
299
+ :param end: The end time of the query. The current time is used by default.
300
+ :param application_names: A list of application names to filter the results by. If not provided, all
301
+ applications are included.
302
+ :return: A list containing the latest metrics and results for each application.
303
+ """
304
+ if not application_names:
305
+ return []
306
+
307
+ start, end = self._pre_aggregate_manager.get_start_end(start, end)
308
+
309
+ metric_objects = []
310
+
311
+ for app_name in application_names:
312
+ # Get latest results for this application
313
+ results_records = self._get_latest_results_for_application(
314
+ app_name, start, end
315
+ )
316
+ metric_objects.extend(results_records)
317
+ # Get latest metrics for this application
318
+ metrics_records = self._get_latest_metrics_for_application(
319
+ app_name, start, end
320
+ )
321
+ metric_objects.extend(metrics_records)
322
+ return metric_objects
323
+
324
+ def _get_latest_metrics_for_application(
325
+ self, application_name: str, start: datetime, end: datetime
326
+ ) -> list[mm_schemas.ApplicationMetricRecord]:
327
+ """Get the latest metrics for a specific application."""
328
+ table_schema = self.tables[mm_schemas.TimescaleDBTables.METRICS]
329
+
330
+ # Build filters using query builder utilities
331
+ app_filter = TimescaleDBQueryBuilder.build_application_filter(application_name)
332
+ time_filter = TimescaleDBQueryBuilder.build_time_range_filter(
333
+ start, end, mm_schemas.WriterEvent.END_INFER_TIME
334
+ )
335
+ where_clause = TimescaleDBQueryBuilder.combine_filters(
336
+ [app_filter, time_filter]
337
+ )
338
+
339
+ # DISTINCT ON is PostgreSQL-specific, keep as specialized query
340
+ query = f"""
341
+ SELECT DISTINCT ON (metric_name)
342
+ {mm_schemas.WriterEvent.END_INFER_TIME},
343
+ {mm_schemas.WriterEvent.APPLICATION_NAME},
344
+ {mm_schemas.MetricData.METRIC_NAME},
345
+ {mm_schemas.MetricData.METRIC_VALUE}
346
+ FROM {table_schema.full_name()}
347
+ WHERE {where_clause}
348
+ ORDER BY metric_name, {mm_schemas.WriterEvent.END_INFER_TIME} DESC
349
+ """
350
+
351
+ stmt = Statement(query)
352
+ result = self._connection.run(query=stmt)
353
+
354
+ if not result or not result.data:
355
+ return []
356
+
357
+ # Work directly with raw result data instead of constructing DataFrame
358
+ # Fields order: end_infer_time, application_name, metric_name, metric_value
359
+ return [
360
+ mm_schemas.ApplicationMetricRecord(
361
+ time=row[0], # end_infer_time
362
+ value=row[3], # metric_value
363
+ metric_name=row[2], # metric_name
364
+ )
365
+ for row in result.data
366
+ ]
367
+
368
+ def _get_latest_results_for_application(
369
+ self, application_name: str, start: datetime, end: datetime
370
+ ) -> list[mm_schemas.ApplicationResultRecord]:
371
+ """Get the latest results for a specific application."""
372
+ table_schema = self.tables[mm_schemas.TimescaleDBTables.APP_RESULTS]
373
+
374
+ # Build filters using query builder utilities
375
+ app_filter = TimescaleDBQueryBuilder.build_application_filter(application_name)
376
+ time_filter = TimescaleDBQueryBuilder.build_time_range_filter(
377
+ start, end, mm_schemas.WriterEvent.END_INFER_TIME
378
+ )
379
+ where_clause = TimescaleDBQueryBuilder.combine_filters(
380
+ [app_filter, time_filter]
381
+ )
382
+
383
+ # DISTINCT ON is PostgreSQL-specific, keep as specialized query
384
+ query = f"""
385
+ SELECT DISTINCT ON (result_name)
386
+ {mm_schemas.WriterEvent.END_INFER_TIME},
387
+ {mm_schemas.WriterEvent.APPLICATION_NAME},
388
+ {mm_schemas.ResultData.RESULT_NAME},
389
+ {mm_schemas.ResultData.RESULT_VALUE},
390
+ {mm_schemas.ResultData.RESULT_STATUS},
391
+ {mm_schemas.ResultData.RESULT_KIND}
392
+ FROM {table_schema.full_name()}
393
+ WHERE {where_clause}
394
+ ORDER BY result_name, {mm_schemas.WriterEvent.END_INFER_TIME} DESC
395
+ """
396
+
397
+ stmt = Statement(query)
398
+ result = self._connection.run(query=stmt)
399
+
400
+ if not result or not result.data:
401
+ return []
402
+
403
+ # Work directly with raw result data instead of constructing DataFrame
404
+ # Fields order: end_infer_time, application_name, result_name, result_value, result_status, result_kind
405
+ return [
406
+ mm_schemas.ApplicationResultRecord(
407
+ time=row[0], # end_infer_time
408
+ value=row[3], # result_value
409
+ kind=mm_schemas.ResultKindApp(row[5]), # result_kind
410
+ status=mm_schemas.ResultStatusApp(row[4]), # result_status
411
+ result_name=row[2], # result_name
412
+ )
413
+ for row in result.data
414
+ ]