fiddler-client 2.4.0.dev2__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 (51) hide show
  1. fiddler/__init__.py +2 -0
  2. fiddler/_version.py +1 -1
  3. fiddler/api/alert_mixin.py +51 -1
  4. fiddler/api/webhooks_mixin.py +6 -8
  5. fiddler/core_objects.py +67 -30
  6. fiddler/schema/alert.py +8 -1
  7. fiddler/schemas/custom_features.py +13 -0
  8. fiddler3/__init__.py +1 -36
  9. fiddler3/connection.py +68 -1
  10. fiddler3/constants/alert_rule.py +30 -0
  11. fiddler3/constants/events.py +7 -0
  12. fiddler3/entities/__init__.py +3 -0
  13. fiddler3/entities/alert_record.py +106 -0
  14. fiddler3/entities/alert_rule.py +246 -0
  15. fiddler3/entities/base.py +2 -16
  16. fiddler3/entities/events.py +120 -0
  17. fiddler3/entities/files.py +170 -0
  18. fiddler3/entities/helpers.py +6 -6
  19. fiddler3/entities/model.py +67 -18
  20. fiddler3/entities/model_artifact.py +48 -220
  21. fiddler3/entities/project.py +1 -1
  22. fiddler3/entities/webhook.py +128 -0
  23. fiddler3/entities/xai.py +267 -0
  24. fiddler3/libs/json_encoder.py +1 -1
  25. fiddler3/schemas/alert_record.py +27 -0
  26. fiddler3/schemas/alert_rule.py +32 -0
  27. fiddler3/schemas/custom_features.py +34 -0
  28. fiddler3/schemas/events.py +15 -0
  29. fiddler3/schemas/files.py +23 -0
  30. fiddler3/schemas/model.py +1 -6
  31. fiddler3/schemas/webhook.py +26 -0
  32. fiddler3/tests/apis/test_alert_record.py +99 -0
  33. fiddler3/tests/apis/test_alert_rule.py +323 -0
  34. fiddler3/tests/apis/test_events.py +128 -0
  35. fiddler3/tests/apis/test_files.py +164 -0
  36. fiddler3/tests/apis/test_model.py +55 -8
  37. fiddler3/tests/apis/test_model_artifact.py +21 -0
  38. fiddler3/tests/apis/test_project.py +2 -2
  39. fiddler3/tests/apis/test_webhook.py +280 -0
  40. fiddler3/tests/apis/test_xai.py +233 -1
  41. fiddler3/tests/conftest.py +2 -2
  42. fiddler3/tests/constants.py +5 -0
  43. fiddler3/tests/test_connection.py +24 -1
  44. fiddler3/tests/test_json_encoder.py +2 -2
  45. {fiddler_client-2.4.0.dev2.dist-info → fiddler_client-2.5.0.dev1.dist-info}/METADATA +1 -1
  46. {fiddler_client-2.4.0.dev2.dist-info → fiddler_client-2.5.0.dev1.dist-info}/RECORD +50 -34
  47. tests/fiddler/test_alert.py +95 -5
  48. fiddler3/schemas/model_artifact.py +0 -6
  49. {fiddler_client-2.4.0.dev2.dist-info → fiddler_client-2.5.0.dev1.dist-info}/LICENSE.txt +0 -0
  50. {fiddler_client-2.4.0.dev2.dist-info → fiddler_client-2.5.0.dev1.dist-info}/WHEEL +0 -0
  51. {fiddler_client-2.4.0.dev2.dist-info → fiddler_client-2.5.0.dev1.dist-info}/top_level.txt +0 -0
fiddler/__init__.py CHANGED
@@ -50,6 +50,7 @@ from fiddler.schemas.custom_features import (
50
50
  Multivariate,
51
51
  TextEmbedding,
52
52
  VectorFeature,
53
+ Enrichment,
53
54
  )
54
55
  from fiddler.utils import ColorLogger
55
56
  from fiddler.utils.logger import get_logger
@@ -71,6 +72,7 @@ __all__ = [
71
72
  'VectorFeature',
72
73
  'TextEmbedding',
73
74
  'ImageEmbedding',
75
+ 'Enrichment',
74
76
  'ColorLogger',
75
77
  'DatasetInfo',
76
78
  'DataType',
fiddler/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '2.4.0.dev2'
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(
@@ -61,7 +65,7 @@ class AlertMixin:
61
65
  warning_threshold: Optional[float] = None,
62
66
  notifications_config: Optional[Dict[str, Any]] = None,
63
67
  ) -> AlertRule:
64
- """
68
+ f"""
65
69
  To add an alert rule
66
70
 
67
71
  :param project_id: Unique project name for which the alert rule is created
@@ -488,3 +492,49 @@ class AlertMixin:
488
492
  },
489
493
  'webhooks': webhooks_dict,
490
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
@@ -28,6 +28,7 @@ from pandas.api.types import CategoricalDtype
28
28
 
29
29
  from fiddler.schemas.custom_features import (
30
30
  CustomFeature,
31
+ Enrichment,
31
32
  ImageEmbedding,
32
33
  Multivariate,
33
34
  TextEmbedding,
@@ -1231,10 +1232,10 @@ class ModelInfo:
1231
1232
  'The custom_features argument only accepts a list of CustomFeature objects.'
1232
1233
  )
1233
1234
 
1234
- available_cols = (
1235
- self.get_input_names()
1236
- + self.get_target_names()
1237
- + 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(),
1238
1239
  )
1239
1240
 
1240
1241
  numeric_cols = self.get_input_pandas_dtypes()
@@ -1243,19 +1244,18 @@ class ModelInfo:
1243
1244
  if self.targets:
1244
1245
  numeric_cols.update(self.get_target_pandas_dtypes())
1245
1246
 
1246
- valid_numeric_names = [
1247
+ valid_numeric_names = set(
1247
1248
  k
1248
1249
  for k, v in numeric_cols.items()
1249
1250
  if v in (DataType.INTEGER.value, DataType.FLOAT.value)
1250
- ]
1251
-
1252
- valid_vector_cols = [
1251
+ )
1252
+ valid_vector_cols = set(
1253
1253
  vec_col.name
1254
1254
  for vec_col in (self.inputs or []) + (self.metadata or [])
1255
1255
  if vec_col.data_type.is_vector()
1256
- ]
1256
+ )
1257
1257
 
1258
- unique_cf_names = []
1258
+ valid_cf_names, valid_enrichment_names = set(), set()
1259
1259
  for feature in self.custom_features:
1260
1260
  if not isinstance(feature, CustomFeature):
1261
1261
  raise ValueError(
@@ -1267,51 +1267,88 @@ class ModelInfo:
1267
1267
  f'A column name "{feature.name}" already exists in the dataset. Please use a different name for custom feature {feature}.'
1268
1268
  )
1269
1269
 
1270
- if feature.name in unique_cf_names:
1270
+ if feature.name in valid_cf_names:
1271
1271
  raise ValueError(
1272
1272
  f'Multiple custom features are defined with the same name {feature.name}.'
1273
1273
  )
1274
- else:
1275
- 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)
1276
1277
 
1278
+ for feature in self.custom_features:
1277
1279
  if isinstance(feature, Multivariate):
1278
1280
  self.validate_multivariate_cf(feature, valid_numeric_names)
1279
1281
  elif isinstance(feature, TextEmbedding):
1280
- self.validate_text_embedding_cf(
1282
+ self.validate_embedding_cf(
1283
+ feature,
1284
+ available_cols,
1285
+ valid_vector_cols,
1286
+ valid_enrichment_names,
1287
+ )
1288
+ elif isinstance(feature, ImageEmbedding):
1289
+ self.validate_embedding_cf(
1281
1290
  feature,
1282
1291
  available_cols,
1283
1292
  valid_vector_cols,
1293
+ [],
1284
1294
  )
1285
1295
  elif isinstance(feature, VectorFeature):
1286
1296
  self.validate_vector_cf(feature, valid_vector_cols)
1297
+ elif isinstance(feature, Enrichment):
1298
+ self.validate_enrichment_cf(
1299
+ feature, available_cols, valid_cf_names
1300
+ )
1287
1301
 
1288
- def validate_text_embedding_cf(
1302
+ def validate_enrichment_cf(
1289
1303
  self,
1290
- feature: TextEmbedding,
1291
- available_cols: List[str],
1292
- valid_vector_cols: List[str],
1304
+ feature: Enrichment,
1305
+ available_cols: Iterable[str],
1306
+ valid_cf_names: Iterable[str],
1293
1307
  ) -> None:
1294
- if feature.column not in valid_vector_cols:
1308
+ if not feature.columns:
1295
1309
  raise ValueError(
1296
- f"Column '{feature.column}' defined in {feature.name} ({feature.__class__.__name__}) does not exist or not of type vector."
1297
- )
1298
-
1299
- if feature.source_column not in available_cols:
1300
- raise ValueError(
1301
- f'{feature.name} ({feature.__class__.__name__}) needs to specify source_col.'
1310
+ f'{feature.name} ({feature.__class__.__name__}) must specify at least one enrichment/column/custom feature in columns.'
1302
1311
  )
1312
+ for column in feature.columns:
1313
+ if column == feature.name:
1314
+ raise ValueError(
1315
+ f'{feature.name} ({feature.__class__.__name__}) cannot depend on itself.'
1316
+ )
1317
+ if column not in valid_cf_names and column not in available_cols:
1318
+ raise ValueError(
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."
1320
+ )
1303
1321
 
1304
- def validate_vector_cf(
1305
- self, feature: VectorFeature, valid_vector_cols: List[str]
1322
+ def validate_embedding_cf(
1323
+ self,
1324
+ feature: Union[TextEmbedding, ImageEmbedding],
1325
+ available_cols: Iterable[str],
1326
+ valid_vector_cols: Iterable[str],
1327
+ valid_enrichment_names: Iterable[str],
1306
1328
  ) -> None:
1329
+ if feature.column not in valid_vector_cols:
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
+ )
1335
+ if feature.source_column not in available_cols:
1336
+ raise ValueError(
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.'
1338
+ )
1339
+ # column could be the result of an enrichment
1340
+ if feature.column not in valid_enrichment_names:
1341
+ raise ValueError(
1342
+ f"Column/Enrichment'{feature.column}' defined in {feature.name} ({feature.__class__.__name__}) does not exist or not of type vector."
1343
+ )
1344
+
1345
+ def validate_vector_cf(self, feature: VectorFeature, valid_vector_cols: Iterable[str]) -> None:
1307
1346
  if feature.column not in valid_vector_cols:
1308
1347
  raise ValueError(
1309
1348
  f"Column '{feature.column}' defined in {feature.name} ({feature.__class__.__name__}) does not exist or not of type vector."
1310
1349
  )
1311
1350
 
1312
- def validate_multivariate_cf(
1313
- self, feature: Multivariate, valid_numeric_names: List[str]
1314
- ) -> None:
1351
+ def validate_multivariate_cf(self, feature: Multivariate, valid_numeric_names: Iterable[str]) -> None:
1315
1352
  if len(feature.columns or []) < 2:
1316
1353
  raise ValueError(
1317
1354
  f'{feature.name} ({feature.__class__.__name__}) must specify at least two columns.'
fiddler/schema/alert.py CHANGED
@@ -34,6 +34,12 @@ class Metric(str, enum.Enum):
34
34
  MISSING_VALUE = 'null_violation_count'
35
35
  RANGE_VIOLATION = 'range_violation_count'
36
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'
37
43
 
38
44
  ## Performance metrics
39
45
  # Binary Classification
@@ -47,7 +53,6 @@ class Metric(str, enum.Enum):
47
53
  CALIBRATED_THRESHOLD = 'calibrated_threshold'
48
54
  GMEAN = 'geometric_mean'
49
55
  LOG_LOSS = 'log_loss'
50
-
51
56
 
52
57
  # Regression
53
58
  R2 = 'r2'
@@ -224,4 +229,6 @@ class TriggeredAlerts(BaseDataSchema):
224
229
  message: str
225
230
  multi_col_values: Optional[Dict[str, float]]
226
231
  feature_name: Optional[str]
232
+ alert_record_main_version: int
233
+ alert_record_sub_version: int
227
234
  segment: Optional[Segment]
@@ -47,6 +47,8 @@ class CustomFeature(BaseModel):
47
47
  return TextEmbedding.parse_obj(deserialized_json)
48
48
  elif feature_type == CustomFeatureType.FROM_IMAGE_EMBEDDING:
49
49
  return ImageEmbedding.parse_obj(deserialized_json)
50
+ elif feature_type == CustomFeatureType.ENRICHMENT:
51
+ return Enrichment.parse_obj(deserialized_json)
50
52
  else:
51
53
  raise ValueError(f'Unsupported feature type: {feature_type}')
52
54
 
@@ -64,6 +66,10 @@ class CustomFeature(BaseModel):
64
66
  return_dict['source_column'] = self.source_column
65
67
  if isinstance(self, TextEmbedding):
66
68
  return_dict['n_tags'] = self.n_tags
69
+ elif isinstance(self, Enrichment):
70
+ return_dict['columns'] = self.columns
71
+ return_dict['enrichment'] = self.enrichment
72
+ return_dict['config'] = self.config
67
73
  else:
68
74
  raise ValueError(f'Unsupported feature type: {self.type} {type(self)}')
69
75
 
@@ -89,3 +95,10 @@ class TextEmbedding(VectorFeature):
89
95
 
90
96
  class ImageEmbedding(VectorFeature):
91
97
  type: CustomFeatureType = CustomFeatureType.FROM_IMAGE_EMBEDDING
98
+
99
+
100
+ class Enrichment(CustomFeature):
101
+ type: CustomFeatureType = CustomFeatureType.ENRICHMENT
102
+ columns: List[str]
103
+ enrichment: str
104
+ config: Dict[str, Any] = {}
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)