fiddler-client 2.4.0.dev1__py3-none-any.whl → 2.5.0.dev1__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.
Files changed (49) hide show
  1. fiddler/_version.py +1 -1
  2. fiddler/api/alert_mixin.py +54 -1
  3. fiddler/api/webhooks_mixin.py +6 -8
  4. fiddler/core_objects.py +40 -32
  5. fiddler/schema/alert.py +12 -1
  6. fiddler3/__init__.py +1 -36
  7. fiddler3/connection.py +68 -1
  8. fiddler3/constants/alert_rule.py +30 -0
  9. fiddler3/constants/events.py +7 -0
  10. fiddler3/entities/__init__.py +3 -0
  11. fiddler3/entities/alert_record.py +106 -0
  12. fiddler3/entities/alert_rule.py +246 -0
  13. fiddler3/entities/base.py +2 -16
  14. fiddler3/entities/events.py +120 -0
  15. fiddler3/entities/files.py +170 -0
  16. fiddler3/entities/helpers.py +6 -6
  17. fiddler3/entities/model.py +67 -18
  18. fiddler3/entities/model_artifact.py +48 -220
  19. fiddler3/entities/project.py +1 -1
  20. fiddler3/entities/webhook.py +128 -0
  21. fiddler3/entities/xai.py +267 -0
  22. fiddler3/libs/json_encoder.py +1 -1
  23. fiddler3/schemas/alert_record.py +27 -0
  24. fiddler3/schemas/alert_rule.py +32 -0
  25. fiddler3/schemas/custom_features.py +27 -6
  26. fiddler3/schemas/events.py +15 -0
  27. fiddler3/schemas/files.py +23 -0
  28. fiddler3/schemas/model.py +1 -6
  29. fiddler3/schemas/webhook.py +26 -0
  30. fiddler3/tests/apis/test_alert_record.py +99 -0
  31. fiddler3/tests/apis/test_alert_rule.py +323 -0
  32. fiddler3/tests/apis/test_events.py +128 -0
  33. fiddler3/tests/apis/test_files.py +164 -0
  34. fiddler3/tests/apis/test_model.py +55 -8
  35. fiddler3/tests/apis/test_model_artifact.py +21 -0
  36. fiddler3/tests/apis/test_project.py +2 -2
  37. fiddler3/tests/apis/test_webhook.py +280 -0
  38. fiddler3/tests/apis/test_xai.py +233 -1
  39. fiddler3/tests/conftest.py +2 -2
  40. fiddler3/tests/constants.py +5 -0
  41. fiddler3/tests/test_connection.py +24 -1
  42. fiddler3/tests/test_json_encoder.py +2 -2
  43. {fiddler_client-2.4.0.dev1.dist-info → fiddler_client-2.5.0.dev1.dist-info}/METADATA +1 -1
  44. {fiddler_client-2.4.0.dev1.dist-info → fiddler_client-2.5.0.dev1.dist-info}/RECORD +48 -32
  45. tests/fiddler/test_alert.py +127 -6
  46. fiddler3/schemas/model_artifact.py +0 -6
  47. {fiddler_client-2.4.0.dev1.dist-info → fiddler_client-2.5.0.dev1.dist-info}/LICENSE.txt +0 -0
  48. {fiddler_client-2.4.0.dev1.dist-info → fiddler_client-2.5.0.dev1.dist-info}/WHEEL +0 -0
  49. {fiddler_client-2.4.0.dev1.dist-info → fiddler_client-2.5.0.dev1.dist-info}/top_level.txt +0 -0
fiddler/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '2.4.0.dev1'
1
+ __version__ = '2.5.0.dev1'
@@ -6,6 +6,8 @@ from typing import Any, Dict, List, Optional, Union
6
6
 
7
7
  from pydantic import parse_obj_as
8
8
 
9
+ import fiddler3
10
+ from fiddler3.entities.model import Model
9
11
  from fiddler.libs.http_client import RequestClient
10
12
  from fiddler.schema.alert import (
11
13
  AlertCondition,
@@ -38,6 +40,8 @@ class AlertMixin:
38
40
  client: RequestClient
39
41
  organization_name: str
40
42
  server_info: ServerInfo
43
+ url: str
44
+ auth_token: str
41
45
 
42
46
  @handle_api_error_response
43
47
  def add_alert_rule(
@@ -50,6 +54,7 @@ class AlertMixin:
50
54
  condition: AlertCondition,
51
55
  project_id: str,
52
56
  model_id: str,
57
+ segment_id: Optional[str] = None,
53
58
  bin_size: BinSize = BinSize.ONE_DAY,
54
59
  alert_type: Optional[AlertType] = None,
55
60
  baseline_id: Optional[str] = None,
@@ -60,13 +65,14 @@ class AlertMixin:
60
65
  warning_threshold: Optional[float] = None,
61
66
  notifications_config: Optional[Dict[str, Any]] = None,
62
67
  ) -> AlertRule:
63
- """
68
+ f"""
64
69
  To add an alert rule
65
70
 
66
71
  :param project_id: Unique project name for which the alert rule is created
67
72
  :param model_id: Unique model name for which the alert rule is created
68
73
  :param project_id: Unique project name for which the alert rule is created
69
74
  :param model_id: Unique model name for which the alert rule is created
75
+ :param segment_id: An optional segment id to filter the data for alert rule
70
76
  :param name: Name of the Alert rule
71
77
  :param alert_type: [DEPRECATED] Used only for older server versions. For {self.RULE_V3_API_VERSION} you can simply use 'metric'.
72
78
  Selects one of the four metric types:
@@ -147,6 +153,7 @@ class AlertMixin:
147
153
  alert_type=alert_type,
148
154
  metric=metric,
149
155
  metric_id=metric,
156
+ segment_id=segment_id,
150
157
  category=category,
151
158
  compare_to=compare_to,
152
159
  compare_period=compare_period,
@@ -485,3 +492,49 @@ class AlertMixin:
485
492
  },
486
493
  'webhooks': webhooks_dict,
487
494
  }
495
+
496
+ def update_alert_notification_status(
497
+ self,
498
+ notification_status: bool,
499
+ alert_config_ids: Optional[list[str]] = None,
500
+ model_id: Optional[str] = None,
501
+ ) -> list[AlertRuleWithNotifications]:
502
+ """
503
+ To enable/disable the notifications for a list of Alert Ids or for a given Model Id.
504
+ Either the Alert Ids should be given or the Model Id, but not both.
505
+ :param notification_status: The value of notification status for an alert
506
+ :param alert_config_ids: List of Alert Ids, on which we need to update notification
507
+ :param model_id: The Model Id which for whom we want to update all alerts.
508
+ :return: List of updated Alert Rules.
509
+ """
510
+ if not alert_config_ids and not model_id:
511
+ raise ValueError('Either alert_config_ids or model_id must be specified')
512
+ elif alert_config_ids and model_id:
513
+ raise ValueError(
514
+ 'Either alert_config_ids or model_id should be specified, not both.'
515
+ )
516
+
517
+ updated_alert_configs: list[AlertRuleWithNotifications] = []
518
+ alert_ids: list[str] = []
519
+
520
+ if alert_config_ids:
521
+ # verify that all the alert-configs are valid
522
+ for alert_id in alert_config_ids:
523
+ self.client.get(
524
+ url=f'alert-configs/{alert_id}',
525
+ )
526
+ alert_ids = alert_config_ids
527
+
528
+ if model_id:
529
+ fiddler3.init(url=self.url, token=self.auth_token)
530
+ model: Model = Model.get(id_=model_id)
531
+ alerts: list[AlertRule] = self.get_alert_rules(model_id=model.name)
532
+ alert_ids = [alert.alert_rule_uuid for alert in alerts]
533
+
534
+ for alert_id in alert_ids:
535
+ updated_alert_configs.append(
536
+ self.update_alert_rule(
537
+ alert_config_uuid=alert_id, enable_notification=notification_status
538
+ )
539
+ )
540
+ return updated_alert_configs
@@ -1,5 +1,5 @@
1
1
  from http import HTTPStatus
2
- from typing import List
2
+ from typing import Dict, List
3
3
 
4
4
  from pydantic import parse_obj_as
5
5
 
@@ -80,13 +80,11 @@ class WebhookMixin:
80
80
 
81
81
  :returns: Updated `Webhook` object.
82
82
  """
83
- request_body = Webhook(
84
- uuid=uuid,
85
- organization_name=self.organization_name,
86
- name=name,
87
- url=url,
88
- provider=provider,
89
- ).dict()
83
+ request_body: Dict[str, str] = {
84
+ 'name': name,
85
+ 'url': url,
86
+ 'provider': provider,
87
+ }
90
88
  response = self.client.patch(
91
89
  url=f'webhooks/{uuid}',
92
90
  data=request_body,
fiddler/core_objects.py CHANGED
@@ -1232,10 +1232,10 @@ class ModelInfo:
1232
1232
  'The custom_features argument only accepts a list of CustomFeature objects.'
1233
1233
  )
1234
1234
 
1235
- available_cols = (
1236
- self.get_input_names()
1237
- + self.get_target_names()
1238
- + self.get_metadata_names()
1235
+ available_cols: Set[str] = set().union(
1236
+ self.get_input_names(),
1237
+ self.get_target_names(),
1238
+ self.get_metadata_names(),
1239
1239
  )
1240
1240
 
1241
1241
  numeric_cols = self.get_input_pandas_dtypes()
@@ -1244,23 +1244,18 @@ class ModelInfo:
1244
1244
  if self.targets:
1245
1245
  numeric_cols.update(self.get_target_pandas_dtypes())
1246
1246
 
1247
- valid_numeric_names = [
1247
+ valid_numeric_names = set(
1248
1248
  k
1249
1249
  for k, v in numeric_cols.items()
1250
1250
  if v in (DataType.INTEGER.value, DataType.FLOAT.value)
1251
- ]
1252
-
1253
- valid_enrichment_names = [
1254
- cf.name for cf in self.custom_features if isinstance(cf, Enrichment)
1255
- ]
1256
-
1257
- valid_vector_cols = [
1251
+ )
1252
+ valid_vector_cols = set(
1258
1253
  vec_col.name
1259
1254
  for vec_col in (self.inputs or []) + (self.metadata or [])
1260
1255
  if vec_col.data_type.is_vector()
1261
- ]
1256
+ )
1262
1257
 
1263
- unique_cf_names = []
1258
+ valid_cf_names, valid_enrichment_names = set(), set()
1264
1259
  for feature in self.custom_features:
1265
1260
  if not isinstance(feature, CustomFeature):
1266
1261
  raise ValueError(
@@ -1272,61 +1267,74 @@ class ModelInfo:
1272
1267
  f'A column name "{feature.name}" already exists in the dataset. Please use a different name for custom feature {feature}.'
1273
1268
  )
1274
1269
 
1275
- if feature.name in unique_cf_names:
1270
+ if feature.name in valid_cf_names:
1276
1271
  raise ValueError(
1277
1272
  f'Multiple custom features are defined with the same name {feature.name}.'
1278
1273
  )
1279
- else:
1280
- unique_cf_names.append(feature.name)
1274
+ if isinstance(feature, Enrichment):
1275
+ valid_enrichment_names.add(feature.name)
1276
+ valid_cf_names.add(feature.name)
1281
1277
 
1278
+ for feature in self.custom_features:
1282
1279
  if isinstance(feature, Multivariate):
1283
1280
  self.validate_multivariate_cf(feature, valid_numeric_names)
1284
1281
  elif isinstance(feature, TextEmbedding):
1285
- self.validate_text_embedding_cf(
1282
+ self.validate_embedding_cf(
1286
1283
  feature,
1287
1284
  available_cols,
1288
1285
  valid_vector_cols,
1289
1286
  valid_enrichment_names,
1290
1287
  )
1288
+ elif isinstance(feature, ImageEmbedding):
1289
+ self.validate_embedding_cf(
1290
+ feature,
1291
+ available_cols,
1292
+ valid_vector_cols,
1293
+ [],
1294
+ )
1291
1295
  elif isinstance(feature, VectorFeature):
1292
1296
  self.validate_vector_cf(feature, valid_vector_cols)
1293
1297
  elif isinstance(feature, Enrichment):
1294
1298
  self.validate_enrichment_cf(
1295
- feature, available_cols, valid_enrichment_names
1299
+ feature, available_cols, valid_cf_names
1296
1300
  )
1297
1301
 
1298
1302
  def validate_enrichment_cf(
1299
1303
  self,
1300
1304
  feature: Enrichment,
1301
- available_cols: List[str],
1302
- valid_enrichment_names: List[str],
1305
+ available_cols: Iterable[str],
1306
+ valid_cf_names: Iterable[str],
1303
1307
  ) -> None:
1304
1308
  if not feature.columns:
1305
1309
  raise ValueError(
1306
- f'{feature.name} ({feature.__class__.__name__}) must specify at least one enrichment/column in columns.'
1310
+ f'{feature.name} ({feature.__class__.__name__}) must specify at least one enrichment/column/custom feature in columns.'
1307
1311
  )
1308
1312
  for column in feature.columns:
1309
1313
  if column == feature.name:
1310
1314
  raise ValueError(
1311
1315
  f'{feature.name} ({feature.__class__.__name__}) cannot depend on itself.'
1312
1316
  )
1313
- if column not in [*valid_enrichment_names, *available_cols]:
1317
+ if column not in valid_cf_names and column not in available_cols:
1314
1318
  raise ValueError(
1315
- f"Column '{column}' defined in {feature.name} ({feature.__class__.__name__}) does not exist in the data or as an enrichment."
1319
+ f"Column '{column}' defined in {feature.name} ({feature.__class__.__name__}) does not exist in the data, as a custom feature or as an enrichment."
1316
1320
  )
1317
1321
 
1318
- def validate_text_embedding_cf(
1322
+ def validate_embedding_cf(
1319
1323
  self,
1320
- feature: TextEmbedding,
1321
- available_cols: List[str],
1322
- valid_vector_cols: List[str],
1323
- valid_enrichment_names: List[str],
1324
+ feature: Union[TextEmbedding, ImageEmbedding],
1325
+ available_cols: Iterable[str],
1326
+ valid_vector_cols: Iterable[str],
1327
+ valid_enrichment_names: Iterable[str],
1324
1328
  ) -> None:
1325
1329
  if feature.column not in valid_vector_cols:
1326
1330
  # we require either a column or source_column
1331
+ if not feature.source_column:
1332
+ raise ValueError(
1333
+ f'{feature.name} ({feature.__class__.__name__}) needs to specify source_column.'
1334
+ )
1327
1335
  if feature.source_column not in available_cols:
1328
1336
  raise ValueError(
1329
- f'{feature.name} ({feature.__class__.__name__}) needs to specify at least one of [column, source_col].'
1337
+ f'source_column {feature.source_column} defined in {feature.name} ({feature.__class__.__name__}) does not exist or cannot be used as a source column.'
1330
1338
  )
1331
1339
  # column could be the result of an enrichment
1332
1340
  if feature.column not in valid_enrichment_names:
@@ -1334,13 +1342,13 @@ class ModelInfo:
1334
1342
  f"Column/Enrichment'{feature.column}' defined in {feature.name} ({feature.__class__.__name__}) does not exist or not of type vector."
1335
1343
  )
1336
1344
 
1337
- def validate_vector_cf(self, feature: VectorFeature, valid_vector_cols: List[str]) -> None:
1345
+ def validate_vector_cf(self, feature: VectorFeature, valid_vector_cols: Iterable[str]) -> None:
1338
1346
  if feature.column not in valid_vector_cols:
1339
1347
  raise ValueError(
1340
1348
  f"Column '{feature.column}' defined in {feature.name} ({feature.__class__.__name__}) does not exist or not of type vector."
1341
1349
  )
1342
1350
 
1343
- def validate_multivariate_cf(self, feature: Multivariate, valid_numeric_names: List[str]) -> None:
1351
+ def validate_multivariate_cf(self, feature: Multivariate, valid_numeric_names: Iterable[str]) -> None:
1344
1352
  if len(feature.columns or []) < 2:
1345
1353
  raise ValueError(
1346
1354
  f'{feature.name} ({feature.__class__.__name__}) must specify at least two columns.'
fiddler/schema/alert.py CHANGED
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Optional, Union
5
5
  from pydantic import Field, root_validator
6
6
 
7
7
  from fiddler.schema.base import BaseDataSchema
8
+ from fiddler.schema.segment import Segment
8
9
 
9
10
 
10
11
  @enum.unique
@@ -33,6 +34,12 @@ class Metric(str, enum.Enum):
33
34
  MISSING_VALUE = 'null_violation_count'
34
35
  RANGE_VIOLATION = 'range_violation_count'
35
36
  TYPE_VIOLATION = 'type_violation_count'
37
+ ANY_VIOLATION = 'any_violation_count'
38
+
39
+ MISSING_VALUE_PCT = 'null_violation_percentage'
40
+ RANGE_VIOLATION_PCT = 'range_violation_percentage'
41
+ TYPE_VIOLATION_PCT = 'type_violation_percentage'
42
+ ANY_VIOLATION_PCT = 'any_violation_percentage'
36
43
 
37
44
  ## Performance metrics
38
45
  # Binary Classification
@@ -46,7 +53,6 @@ class Metric(str, enum.Enum):
46
53
  CALIBRATED_THRESHOLD = 'calibrated_threshold'
47
54
  GMEAN = 'geometric_mean'
48
55
  LOG_LOSS = 'log_loss'
49
-
50
56
 
51
57
  # Regression
52
58
  R2 = 'r2'
@@ -146,6 +152,7 @@ class AlertRulePayload(BaseDataSchema):
146
152
  alert_type: Optional[AlertType]
147
153
  metric: Union[Metric, str]
148
154
  metric_id: Union[Metric, str]
155
+ segment_id: Optional[str]
149
156
  feature_name: Optional[str]
150
157
  feature_names: Optional[List[str]]
151
158
  category: Optional[str]
@@ -183,6 +190,7 @@ class AlertRule(BaseDataSchema):
183
190
  compare_to: CompareTo
184
191
  compare_period: Optional[ComparePeriod]
185
192
  warning_threshold: Optional[float]
193
+ segment: Optional[Segment]
186
194
  critical_threshold: float
187
195
  condition: AlertCondition
188
196
  bin_size: Optional[str]
@@ -221,3 +229,6 @@ class TriggeredAlerts(BaseDataSchema):
221
229
  message: str
222
230
  multi_col_values: Optional[Dict[str, float]]
223
231
  feature_name: Optional[str]
232
+ alert_record_main_version: int
233
+ alert_record_sub_version: int
234
+ segment: Optional[Segment]
fiddler3/__init__.py CHANGED
@@ -1,39 +1,4 @@
1
- from __future__ import annotations
2
-
3
- from fiddler3.configs import DEFAULT_CONNECTION_TIMEOUT
4
- from fiddler3.connection import Connection
1
+ from fiddler3.connection import Connection, ConnectionMixin, init # noqa
5
2
  from fiddler3.entities import * # noqa
6
3
  from fiddler3.exceptions import * # noqa
7
4
  from fiddler3.schemas import * # noqa
8
-
9
- # Global connection object
10
- connection: Connection | None = None
11
-
12
-
13
- def init(
14
- url: str,
15
- token: str,
16
- proxies: dict | None = None,
17
- timeout: int = DEFAULT_CONNECTION_TIMEOUT,
18
- verify: bool = True,
19
- validate: bool = True,
20
- ) -> None:
21
- """
22
- Initiate Python Client Connection
23
- :param url: URL of Fiddler Platform
24
- :param token: Authorization token
25
- :param proxies: Dictionary mapping protocol to the URL of the proxy
26
- :param timeout: Seconds to wait for the server to send data before giving up
27
- :param verify: Controls whether we verify the server’s TLS certificate
28
- :param validate: Whether to validate the server/client version compatibility.
29
- Some functionalities might not work if this is turned off.
30
- """
31
- global connection
32
- connection = Connection(
33
- url=url,
34
- token=token,
35
- proxies=proxies,
36
- timeout=timeout,
37
- verify=verify,
38
- validate=validate,
39
- )
fiddler3/connection.py CHANGED
@@ -15,9 +15,12 @@ from fiddler3.version import __version__
15
15
 
16
16
  logger = get_logger(__name__)
17
17
 
18
+ # Global connection object
19
+ connection: Connection | None = None
20
+
18
21
 
19
22
  class Connection:
20
- def __init__(
23
+ def __init__( # pylint: disable=too-many-arguments
21
24
  self,
22
25
  url: str,
23
26
  token: str,
@@ -28,6 +31,7 @@ class Connection:
28
31
  ) -> None:
29
32
  """
30
33
  Initiate connection to Fiddler Platform
34
+
31
35
  :param url: URL of Fiddler Platform
32
36
  :param token: Authorization token
33
37
  :param proxies: Dictionary mapping protocol to the URL of the proxy
@@ -115,3 +119,66 @@ class Connection:
115
119
  return
116
120
 
117
121
  raise IncompatibleClient(server_version=str(self.server_version))
122
+
123
+
124
+ class ConnectionMixin:
125
+ @classmethod
126
+ def _connection(cls) -> Connection:
127
+ """Fiddler connection instance"""
128
+ assert connection is not None
129
+ return connection
130
+
131
+ @classmethod
132
+ def _client(cls) -> RequestClient:
133
+ """Request client instance"""
134
+ return cls._connection().client
135
+
136
+ @property
137
+ def organization_name(self) -> str:
138
+ """Organization name property"""
139
+ return self._connection().server_info.organization.name
140
+
141
+ @property
142
+ def organization_id(self) -> UUID:
143
+ """Organization id property"""
144
+ return self._connection().server_info.organization.id
145
+
146
+ @classmethod
147
+ def get_organization_name(cls) -> str:
148
+ """Organization name"""
149
+ return cls._connection().server_info.organization.name
150
+
151
+ @classmethod
152
+ def get_organization_id(cls) -> UUID:
153
+ """Organization id"""
154
+ return cls._connection().server_info.organization.id
155
+
156
+
157
+ def init( # pylint: disable=too-many-arguments
158
+ url: str,
159
+ token: str,
160
+ proxies: dict | None = None,
161
+ timeout: int = DEFAULT_CONNECTION_TIMEOUT,
162
+ verify: bool = True,
163
+ validate: bool = True,
164
+ ) -> None:
165
+ """
166
+ Initiate Python Client Connection
167
+
168
+ :param url: URL of Fiddler Platform
169
+ :param token: Authorization token
170
+ :param proxies: Dictionary mapping protocol to the URL of the proxy
171
+ :param timeout: Seconds to wait for the server to send data before giving up
172
+ :param verify: Controls whether we verify the server’s TLS certificate
173
+ :param validate: Whether to validate the server/client version compatibility.
174
+ Some functionalities might not work if this is turned off.
175
+ """
176
+ global connection # pylint: disable=global-statement
177
+ connection = Connection(
178
+ url=url,
179
+ token=token,
180
+ proxies=proxies,
181
+ timeout=timeout,
182
+ verify=verify,
183
+ validate=validate,
184
+ )
@@ -0,0 +1,30 @@
1
+ import enum
2
+
3
+
4
+ @enum.unique
5
+ class BinSize(str, enum.Enum):
6
+ HOUR = 'Hour'
7
+ DAY = 'Day'
8
+ WEEK = 'Week'
9
+ MONTH = 'Month'
10
+
11
+
12
+ @enum.unique
13
+ class CompareTo(str, enum.Enum):
14
+ """Comparison with Absolute(raw_value) or Relative(time_period)"""
15
+
16
+ TIME_PERIOD = 'time_period'
17
+ RAW_VALUE = 'raw_value'
18
+
19
+
20
+ @enum.unique
21
+ class AlertCondition(str, enum.Enum):
22
+ GREATER = 'greater'
23
+ LESSER = 'lesser'
24
+
25
+
26
+ @enum.unique
27
+ class Priority(str, enum.Enum):
28
+ HIGH = 'HIGH'
29
+ MEDIUM = 'MEDIUM'
30
+ LOW = 'LOW'
@@ -0,0 +1,7 @@
1
+ import enum
2
+
3
+
4
+ @enum.unique
5
+ class PublishEventsSourceType(str, enum.Enum):
6
+ EVENTS = 'EVENTS'
7
+ FILE = 'FILE'
@@ -1,6 +1,9 @@
1
1
  """Initialize all new entities."""
2
+ from fiddler3.entities.alert_record import AlertRecord # noqa
3
+ from fiddler3.entities.alert_rule import AlertRule # noqa
2
4
  from fiddler3.entities.baseline import Baseline # noqa
3
5
  from fiddler3.entities.dataset import Dataset # noqa
6
+ from fiddler3.entities.files import File # noqa
4
7
  from fiddler3.entities.job import Job # noqa
5
8
  from fiddler3.entities.model import Model # noqa
6
9
  from fiddler3.entities.model_deployment import ModelDeployment # noqa
@@ -0,0 +1,106 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timedelta
4
+ from typing import Any, Iterator
5
+ from uuid import UUID
6
+
7
+ from fiddler3.decorators import handle_api_error
8
+ from fiddler3.entities.base import BaseEntity
9
+ from fiddler3.schemas.alert_record import AlertRecordResp
10
+
11
+
12
+ class AlertRecord(BaseEntity):
13
+ def __init__(self) -> None:
14
+ """Construct a alert record instance"""
15
+
16
+ self.alert_run_start_time: int | None = None
17
+ self.alert_time_bucket: int | None = None
18
+ self.alert_value: float | None = None
19
+ self.baseline_time_bucket: int | None = None
20
+ self.baseline_value: float | None = None
21
+ self.is_alert: bool | None = None
22
+ self.severity: str | None = None
23
+ self.failure_reason: str | None = None
24
+ self.message: str | None = None
25
+ self.feature_name: str | None = None
26
+ self.alert_record_main_version: int | None = None
27
+ self.alert_record_sub_version: int | None = None
28
+
29
+ self.id: UUID | None = None
30
+ self.alert_rule_id: UUID | None = None
31
+ self.created_at: datetime | None = None
32
+ self.updated_at: datetime | None = None
33
+
34
+ # Deserialized response object
35
+ self._resp: AlertRecordResp | None = None
36
+
37
+ @classmethod
38
+ def _from_dict(cls, data: dict) -> AlertRecord:
39
+ """Build alert record object from the given dictionary"""
40
+
41
+ # Deserialize the response
42
+ resp_obj = AlertRecordResp(**data)
43
+
44
+ # Initialize
45
+ instance = cls()
46
+
47
+ # Add remaining fields
48
+ fields = [
49
+ 'id',
50
+ 'alert_rule_id',
51
+ 'alert_run_start_time',
52
+ 'alert_time_bucket',
53
+ 'alert_value',
54
+ 'baseline_time_bucket',
55
+ 'baseline_value',
56
+ 'is_alert',
57
+ 'severity',
58
+ 'failure_reason',
59
+ 'message',
60
+ 'feature_name',
61
+ 'alert_record_main_version',
62
+ 'alert_record_sub_version',
63
+ 'created_at',
64
+ 'updated_at',
65
+ ]
66
+ for field in fields:
67
+ setattr(instance, field, getattr(resp_obj, field, None))
68
+
69
+ instance._resp = resp_obj
70
+
71
+ return instance
72
+
73
+ @staticmethod
74
+ def _get_url(alert_rule_id: UUID | str, id_: UUID | str | None = None) -> str:
75
+ """Get alert record resource/item url"""
76
+ url = f'/v2/alert-configs/{alert_rule_id}/records'
77
+
78
+ return url if not id_ else f'{url}/{id_}'
79
+
80
+ @classmethod
81
+ @handle_api_error
82
+ def list(
83
+ cls,
84
+ alert_rule_id: UUID | str,
85
+ start_time: datetime | None = None,
86
+ end_time: datetime | None = None,
87
+ ordering: list[str] | None = None,
88
+ ) -> Iterator[AlertRecord]:
89
+ """
90
+ List alert records triggered for an alert rule
91
+
92
+ :param alert_rule_id: unique identifier for alert rule
93
+ :param start_time: Start time to filter trigger alerts :default: 7 days ago
94
+ :param end_time: End time to filter trigger alerts :default: time now
95
+
96
+ :return: paginated list of alert records for the given alert rule
97
+ """
98
+ params: dict[str, Any] = {
99
+ 'start_time': start_time or datetime.now() - timedelta(days=7),
100
+ 'end_time': end_time or datetime.now(),
101
+ }
102
+ if ordering:
103
+ params['ordering'] = ordering
104
+
105
+ for record in cls._paginate(url=cls._get_url(alert_rule_id), params=params):
106
+ yield cls._from_dict(data=record)