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
fiddler3/entities/xai.py CHANGED
@@ -9,8 +9,11 @@ from uuid import UUID
9
9
 
10
10
  import pandas as pd
11
11
 
12
+ from fiddler3.constants.dataset import EnvType
12
13
  from fiddler3.constants.xai import ExplainMethod
13
14
  from fiddler3.decorators import handle_api_error
15
+ from fiddler3.entities.job import Job
16
+ from fiddler3.schemas.job import JobCompactResp
14
17
  from fiddler3.schemas.xai import (
15
18
  DatasetDataSource,
16
19
  EventIdDataSource,
@@ -18,12 +21,19 @@ from fiddler3.schemas.xai import (
18
21
  SqlSliceQueryDataSource,
19
22
  )
20
23
  from fiddler3.utils.helpers import try_series_retype
24
+ from fiddler3.utils.logger import get_logger
25
+
26
+ logger = get_logger(__name__)
21
27
 
22
28
 
23
29
  class XaiMixin:
24
30
  id: UUID | None
25
31
  _client: Callable
26
32
 
33
+ def _get_method(self, update: bool = False) -> Callable:
34
+ """Get HTTP method"""
35
+ return self._client().put if update else self._client().post
36
+
27
37
  @handle_api_error
28
38
  def explain( # pylint: disable=too-many-arguments
29
39
  self,
@@ -273,6 +283,221 @@ class XaiMixin:
273
283
  )
274
284
  return pd.DataFrame(response.json()['data']['predictions'])
275
285
 
286
+ @handle_api_error
287
+ def get_feature_impact( # pylint: disable=too-many-arguments
288
+ self,
289
+ data_source: DatasetDataSource | SqlSliceQueryDataSource,
290
+ num_iterations: int | None = None,
291
+ num_refs: int | None = None,
292
+ ci_level: float | None = None,
293
+ min_support: int | None = None,
294
+ output_columns: list[str] | None = None,
295
+ ) -> tuple:
296
+ """
297
+ Get global feature impact for a model over a dataset or a slice.
298
+
299
+ :param data_source: DataSource for the input dataset to compute feature
300
+ impact on (DatasetDataSource or SqlSliceQueryDataSource)
301
+ :param num_iterations: The maximum number of ablated model inferences per feature
302
+ :param num_refs: The number of reference points used in the explanation
303
+ :param ci_level: The confidence level (between 0 and 1)
304
+ :param min_support: Only used for NLP (TEXT inputs) models. Specify a minimum
305
+ support (number of times a specific word was present in the sample data)
306
+ to retrieve top words. Default to 15.
307
+ :param output_columns: Only used for NLP (TEXT inputs) models. Output column
308
+ names to compute feature impact on.
309
+
310
+ :return: Feature Impact tuple
311
+ """
312
+
313
+ self._check_id_attributes()
314
+
315
+ payload: dict[str, Any] = {
316
+ 'model_id': self.id,
317
+ 'data_source': data_source.dict(),
318
+ }
319
+
320
+ if num_refs:
321
+ payload['num_refs'] = num_refs
322
+ if num_iterations:
323
+ payload['num_iterations'] = num_iterations
324
+ if ci_level:
325
+ payload['ci_level'] = ci_level
326
+ if min_support:
327
+ payload['min_support'] = min_support
328
+ if output_columns:
329
+ payload['output_columns'] = output_columns
330
+
331
+ response = self._client().post(
332
+ url='/v3/analytics/feature-impact',
333
+ data=payload,
334
+ )
335
+
336
+ return namedtuple('FeatureImpact', response.json()['data'])(
337
+ **response.json()['data']
338
+ )
339
+
340
+ @handle_api_error
341
+ def get_feature_importance( # pylint: disable=too-many-arguments
342
+ self,
343
+ data_source: DatasetDataSource | SqlSliceQueryDataSource,
344
+ num_iterations: int | None = None,
345
+ num_refs: int | None = None,
346
+ ci_level: float | None = None,
347
+ ) -> tuple:
348
+ """
349
+ Get global feature importance for a model over a dataset or a slice.
350
+
351
+ :param data_source: DataSource for the input dataset to compute feature
352
+ importance on (DatasetDataSource or SqlSliceQueryDataSource)
353
+ :param num_iterations: The maximum number of ablated model inferences per feature
354
+ :param num_refs: The number of reference points used in the explanation
355
+ :param ci_level: The confidence level (between 0 and 1)
356
+
357
+ :return: Feature Importance tuple
358
+ """
359
+
360
+ self._check_id_attributes()
361
+
362
+ payload: dict[str, Any] = {
363
+ 'model_id': self.id,
364
+ 'data_source': data_source.dict(),
365
+ }
366
+
367
+ if num_refs:
368
+ payload['num_refs'] = num_refs
369
+ if num_iterations:
370
+ payload['num_iterations'] = num_iterations
371
+ if ci_level:
372
+ payload['ci_level'] = ci_level
373
+
374
+ response = self._client().post(
375
+ url='/v3/analytics/feature-importance',
376
+ data=payload,
377
+ )
378
+
379
+ return namedtuple('FeatureImportance', response.json()['data'])(
380
+ **response.json()['data']
381
+ )
382
+
383
+ @handle_api_error
384
+ def precompute_feature_impact( # pylint: disable=too-many-arguments
385
+ self,
386
+ dataset_id: UUID,
387
+ num_samples: int | None = None,
388
+ num_iterations: int | None = None,
389
+ num_refs: int | None = None,
390
+ ci_level: float | None = None,
391
+ min_support: int | None = None,
392
+ update: bool = False,
393
+ ) -> Job:
394
+ """Pre-compute feature impact for a model on a dataset.
395
+
396
+ This is used in various places in the UI.
397
+ A single feature impact can be precomputed (computed and cached) for a model.
398
+
399
+ :param dataset_id: The unique identifier of the dataset
400
+ :param num_samples: The number of samples used
401
+ :param num_iterations: The maximum number of ablated model inferences per feature
402
+ :param num_refs: The number of reference points used in the explanation
403
+ :param ci_level: The confidence level (between 0 and 1)
404
+ :param min_support: Only used for NLP (TEXT inputs) models. Specify a minimum
405
+ support (number of times a specific word was present in the sample data)
406
+ to retrieve top words. Default to 15.
407
+ :param update: Whether the precomputed feature impact should be recomputed and updated
408
+
409
+ :return: Async Job
410
+ """
411
+
412
+ self._check_id_attributes()
413
+
414
+ payload: dict[str, Any] = {
415
+ 'model_id': self.id,
416
+ 'env_id': dataset_id,
417
+ 'env_type': EnvType.PRE_PRODUCTION,
418
+ }
419
+ if num_samples:
420
+ payload['num_samples'] = num_samples
421
+ if num_refs:
422
+ payload['num_refs'] = num_refs
423
+ if num_iterations:
424
+ payload['num_iterations'] = num_iterations
425
+ if ci_level:
426
+ payload['ci_level'] = ci_level
427
+ if min_support:
428
+ payload['min_support'] = min_support
429
+
430
+ method = self._get_method(update)
431
+
432
+ response = method(
433
+ url='/v3/analytics/precompute-feature-impact',
434
+ params=payload,
435
+ )
436
+
437
+ job_compact = JobCompactResp(**response.json()['data']['job'])
438
+ logger.info(
439
+ 'Model[%s] - Submitted job (%s) for precomputing feature impact',
440
+ self.id,
441
+ job_compact.id,
442
+ )
443
+ return Job.get(id_=job_compact.id)
444
+
445
+ @handle_api_error
446
+ def precompute_feature_importance( # pylint: disable=too-many-arguments
447
+ self,
448
+ dataset_id: UUID,
449
+ num_samples: int | None = None,
450
+ num_iterations: int | None = None,
451
+ num_refs: int | None = None,
452
+ ci_level: float | None = None,
453
+ update: bool = False,
454
+ ) -> Job:
455
+ """Pre-compute feature importance for a model on a dataset.
456
+
457
+ This is used in various places in the UI.
458
+ A single feature importance can be precomputed (computed and cached) for a model.
459
+
460
+ :param dataset_id: The unique identifier of the dataset
461
+ :param num_samples: The number of samples used
462
+ :param num_iterations: The maximum number of ablated model inferences per feature
463
+ :param num_refs: The number of reference points used in the explanation
464
+ :param ci_level: The confidence level (between 0 and 1)
465
+ :param update: Whether the precomputed feature impact should be recomputed and updated
466
+
467
+ :return: Async Job
468
+ """
469
+
470
+ self._check_id_attributes()
471
+
472
+ payload: dict[str, Any] = {
473
+ 'model_id': self.id,
474
+ 'env_id': dataset_id,
475
+ 'env_type': EnvType.PRE_PRODUCTION,
476
+ }
477
+ if num_samples:
478
+ payload['num_samples'] = num_samples
479
+ if num_refs:
480
+ payload['num_refs'] = num_refs
481
+ if num_iterations:
482
+ payload['num_iterations'] = num_iterations
483
+ if ci_level:
484
+ payload['ci_level'] = ci_level
485
+
486
+ method = self._get_method(update)
487
+
488
+ response = method(
489
+ url='/v3/analytics/precompute-feature-importance',
490
+ params=payload,
491
+ )
492
+
493
+ job_compact = JobCompactResp(**response.json()['data']['job'])
494
+ logger.info(
495
+ 'Model[%s] - Submitted job (%s) for precomputing feature importance',
496
+ self.id,
497
+ job_compact.id,
498
+ )
499
+ return Job.get(id_=job_compact.id)
500
+
276
501
  @handle_api_error
277
502
  def get_precomputed_feature_importance(self) -> tuple:
278
503
  """Get precomputed feature importance for a model"""
@@ -301,6 +526,48 @@ class XaiMixin:
301
526
  **response.json()['data']
302
527
  )
303
528
 
529
+ @handle_api_error
530
+ def precompute_predictions(
531
+ self,
532
+ dataset_id: UUID,
533
+ chunk_size: int | None = None,
534
+ update: bool = False,
535
+ ) -> Job:
536
+ """
537
+ Pre-compute predictions for a model on a dataset
538
+
539
+ :param dataset_id: The unique identifier of the dataset
540
+ :param chunk_size: Chunk size for fetching predictions
541
+ :param update: Whether the pre-computed predictions should be re-computed and updated for this dataset
542
+
543
+ :return: Dataframe of the predictions
544
+ """
545
+ self._check_id_attributes()
546
+
547
+ payload: dict[str, Any] = {
548
+ 'model_id': self.id,
549
+ 'env_id': dataset_id,
550
+ }
551
+
552
+ if chunk_size:
553
+ payload['batch_size'] = chunk_size
554
+
555
+ method = self._get_method(update)
556
+
557
+ response = method(
558
+ url='/v3/analytics/precompute-predictions',
559
+ params=payload,
560
+ )
561
+
562
+ job_compact = JobCompactResp(**response.json()['data']['job'])
563
+ logger.info(
564
+ 'Model[%s] - Submitted job (%s) for precomputing predictions on dataset[%s]',
565
+ self.id,
566
+ job_compact.id,
567
+ dataset_id,
568
+ )
569
+ return Job.get(id_=job_compact.id)
570
+
304
571
  def _check_id_attributes(self) -> None:
305
572
  if not self.id:
306
573
  raise AttributeError(
@@ -8,5 +8,5 @@ class RequestClientJSONEncoder(JSONEncoder):
8
8
  def default(self, o: Any) -> Any:
9
9
  """Override JSONEncoder.default to support uuid serialization"""
10
10
  if isinstance(o, UUID):
11
- return o.hex
11
+ return str(o)
12
12
  return super().default(o)
@@ -0,0 +1,27 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+ from uuid import UUID
4
+
5
+ from pydantic import Field
6
+
7
+ from fiddler3.schemas.base import BaseModel
8
+
9
+
10
+ class AlertRecordResp(BaseModel):
11
+ id: UUID = Field(alias='uuid')
12
+ alert_rule_id: UUID = Field(alias='alert_config_uuid')
13
+ alert_run_start_time: int
14
+ alert_time_bucket: int
15
+ alert_value: float
16
+ baseline_time_bucket: Optional[int]
17
+ baseline_value: Optional[float]
18
+ is_alert: bool
19
+ severity: str
20
+ failure_reason: str
21
+ message: str
22
+ feature_name: Optional[str]
23
+ alert_record_main_version: int
24
+ alert_record_sub_version: int
25
+
26
+ created_at: datetime
27
+ updated_at: datetime
@@ -0,0 +1,32 @@
1
+ from datetime import datetime
2
+ from typing import List, Optional, Union
3
+ from uuid import UUID
4
+
5
+ from pydantic import Field
6
+
7
+ from fiddler3.constants.alert_rule import AlertCondition, BinSize, CompareTo, Priority
8
+ from fiddler3.schemas.base import BaseModel
9
+ from fiddler3.schemas.baseline import BaselineCompactResp
10
+ from fiddler3.schemas.model import ModelCompactResp
11
+ from fiddler3.schemas.project import ProjectCompactResp
12
+
13
+
14
+ class AlertRuleResp(BaseModel):
15
+ id: UUID = Field(alias='uuid')
16
+ name: str
17
+ model: ModelCompactResp
18
+ project: ProjectCompactResp
19
+ baseline: Optional[BaselineCompactResp]
20
+ priority: Union[str, Priority]
21
+ compare_to: Union[str, CompareTo]
22
+ metric_id: Union[str, UUID] = Field(alias='metric')
23
+ critical_threshold: float
24
+ condition: Union[str, AlertCondition]
25
+ bin_size: Union[str, BinSize]
26
+ columns: Optional[List[str]]
27
+ baseline_id: Optional[UUID]
28
+ compare_bin_delta: Optional[int]
29
+ warning_threshold: Optional[float]
30
+
31
+ created_at: datetime
32
+ updated_at: datetime = Field(alias='last_updated')
@@ -46,6 +46,8 @@ class CustomFeature(BaseModel):
46
46
  return TextEmbedding.parse_obj(deserialized_json)
47
47
  if feature_type == CustomFeatureType.FROM_IMAGE_EMBEDDING:
48
48
  return ImageEmbedding.parse_obj(deserialized_json)
49
+ if feature_type == CustomFeatureType.ENRICHMENT:
50
+ return Enrichment.parse_obj(deserialized_json)
49
51
 
50
52
  raise ValueError(f'Unsupported feature type: {feature_type}')
51
53
 
@@ -63,6 +65,10 @@ class CustomFeature(BaseModel):
63
65
  return_dict['source_column'] = self.source_column
64
66
  if isinstance(self, TextEmbedding):
65
67
  return_dict['n_tags'] = self.n_tags
68
+ elif isinstance(self, Enrichment):
69
+ return_dict['columns'] = self.columns
70
+ return_dict['enrichment'] = self.enrichment
71
+ return_dict['config'] = self.config
66
72
  else:
67
73
  raise ValueError(f'Unsupported feature type: {self.type} {type(self)}')
68
74
 
@@ -88,3 +94,31 @@ class TextEmbedding(VectorFeature):
88
94
 
89
95
  class ImageEmbedding(VectorFeature):
90
96
  type: CustomFeatureType = CustomFeatureType.FROM_IMAGE_EMBEDDING
97
+
98
+
99
+ class Enrichment(CustomFeature):
100
+ """
101
+ Represents an enrichment feature in a data processing or machine learning context.
102
+
103
+ This class inherits from CustomFeature and is used to apply specific enrichment
104
+ operations to a set of columns in a dataset. The type of enrichment and its
105
+ configuration can be specified.
106
+
107
+ Attributes:
108
+ type (CustomFeatureType): The type of the custom feature, set to ENRICHMENT.
109
+ columns (List[str]): The list of input column names in the dataset used to generate the enrichment.
110
+ enrichment (str): A string identifier for the type of enrichment to be applied.
111
+ config (Dict[str, Any]): A dictionary containing configuration options for the enrichment.
112
+ """
113
+
114
+ # Setting the feature type to ENRICHMENT
115
+ type: CustomFeatureType = CustomFeatureType.ENRICHMENT
116
+
117
+ # List of input column names used to generate the enrichment
118
+ columns: List[str]
119
+
120
+ # String identifier for the enrichment to be applied. e.g. "embedding" or "toxicity"
121
+ enrichment: str
122
+
123
+ # Dictionary for additional configuration options for the enrichment
124
+ config: Dict[str, Any] = {}
@@ -0,0 +1,15 @@
1
+ from typing import Any, Dict, List
2
+ from uuid import UUID
3
+
4
+ from fiddler3.constants.events import PublishEventsSourceType
5
+ from fiddler3.schemas.base import BaseModel
6
+
7
+
8
+ class FileSource(BaseModel):
9
+ file_id: UUID
10
+ type: PublishEventsSourceType = PublishEventsSourceType.FILE
11
+
12
+
13
+ class EventsSource(BaseModel):
14
+ events: List[Dict[Any, Any]]
15
+ type: PublishEventsSourceType = PublishEventsSourceType.EVENTS
@@ -0,0 +1,23 @@
1
+ from datetime import datetime
2
+ from uuid import UUID
3
+
4
+ from pydantic import Field
5
+
6
+ from fiddler3.schemas.base import BaseModel
7
+ from fiddler3.schemas.user import UserCompactResp
8
+
9
+
10
+ class FileResp(BaseModel):
11
+ id: UUID
12
+ name: str = Field(alias='filename')
13
+ status: str
14
+ type: str
15
+ created_at: datetime
16
+ updated_at: datetime
17
+ created_by: UserCompactResp
18
+ updated_by: UserCompactResp
19
+
20
+
21
+ class FileCompactResp(BaseModel):
22
+ id: UUID
23
+ name: str
fiddler3/schemas/model.py CHANGED
@@ -5,7 +5,7 @@ from uuid import UUID
5
5
  from pydantic.fields import Field
6
6
 
7
7
  from fiddler3.schemas.base import BaseModel
8
- from fiddler3.schemas.model_schema import Column, ModelSchema
8
+ from fiddler3.schemas.model_schema import ModelSchema
9
9
  from fiddler3.schemas.model_spec import ModelSpec
10
10
  from fiddler3.schemas.model_task_params import ModelTaskParams
11
11
  from fiddler3.schemas.organization import OrganizationCompactResp
@@ -40,9 +40,4 @@ class ModelResp(BaseModel):
40
40
  updated_by: UserCompactResp
41
41
  organization: OrganizationCompactResp
42
42
  project: ProjectCompactResp
43
- input_cols: List[Column]
44
- output_cols: List[Column]
45
- target_cols: List[Column]
46
- metadata_cols: List[Column]
47
- decision_cols: List[Column]
48
43
  is_binary_ranking_model: bool
@@ -0,0 +1,26 @@
1
+ import enum
2
+ from datetime import datetime
3
+ from uuid import UUID
4
+
5
+ from pydantic.fields import Field
6
+
7
+ from fiddler3.schemas.base import BaseModel
8
+ from fiddler3.schemas.user import UserCompactResp
9
+
10
+
11
+ @enum.unique
12
+ class WebhookProvider(str, enum.Enum):
13
+ # provider is 'SLACK' or 'OTHER' as of Aug 2023.
14
+ SLACK = 'SLACK'
15
+ OTHER = 'OTHER'
16
+
17
+
18
+ class WebhookResp(BaseModel):
19
+ id: UUID = Field(alias='uuid')
20
+ name: str
21
+ url: str
22
+ provider: WebhookProvider
23
+ created_at: datetime
24
+ updated_at: datetime
25
+ created_by: UserCompactResp
26
+ updated_by: UserCompactResp
@@ -0,0 +1,99 @@
1
+ from datetime import datetime
2
+
3
+ import responses
4
+
5
+ from fiddler3.entities.alert_record import AlertRecord
6
+ from fiddler3.tests.constants import ALERT_RULE_ID, URL
7
+
8
+ ALERT_RECORD_LIST_RESPONSE = {
9
+ 'data': {
10
+ 'page_size': 100,
11
+ 'total': 2,
12
+ 'item_count': 2,
13
+ 'page_count': 1,
14
+ 'page_index': 1,
15
+ 'offset': 0,
16
+ 'items': [
17
+ {
18
+ 'id': 26410,
19
+ 'uuid': 'b520da6b-c01c-4375-bde6-2560cd972485',
20
+ 'alert_config_uuid': ALERT_RULE_ID,
21
+ 'alert_run_start_time': 1703255503133,
22
+ 'alert_time_bucket': 1703116800000,
23
+ 'baseline_time_bucket': None,
24
+ 'baseline_value': None,
25
+ 'is_alert': True,
26
+ 'severity': 'CRITICAL',
27
+ 'failure_reason': 'NA',
28
+ 'message': '',
29
+ 'alert_value': 37.0,
30
+ 'feature_name': 'estimatedsalary',
31
+ 'alert_record_main_version': 1,
32
+ 'alert_record_sub_version': 2,
33
+ 'created_at': '2023-12-22T14:31:43.499697+00:00',
34
+ 'updated_at': '2023-12-22T14:31:43.499697+00:00',
35
+ },
36
+ {
37
+ 'id': 26418,
38
+ 'uuid': '6e55540c-9053-4af8-b20e-1728b1436f35',
39
+ 'alert_config_uuid': ALERT_RULE_ID,
40
+ 'alert_run_start_time': 1703259102527,
41
+ 'alert_time_bucket': 1703116800000,
42
+ 'baseline_time_bucket': None,
43
+ 'baseline_value': None,
44
+ 'is_alert': True,
45
+ 'severity': 'CRITICAL',
46
+ 'failure_reason': 'NA',
47
+ 'message': '',
48
+ 'alert_value': 37.0,
49
+ 'feature_name': 'estimatedsalary',
50
+ 'alert_record_main_version': 1,
51
+ 'alert_record_sub_version': 3,
52
+ 'created_at': '2023-12-22T15:31:42.698623+00:00',
53
+ 'updated_at': '2023-12-22T15:31:42.698623+00:00',
54
+ },
55
+ ],
56
+ },
57
+ 'api_version': '2.0',
58
+ 'kind': 'PAGINATED',
59
+ }
60
+
61
+ ALERT_RECORD_EMPTY_LIST_RESPONSE = {
62
+ 'data': {
63
+ 'page_size': 100,
64
+ 'total': 0,
65
+ 'item_count': 0,
66
+ 'page_count': 1,
67
+ 'page_index': 1,
68
+ 'offset': 0,
69
+ 'items': [],
70
+ },
71
+ 'api_version': '2.0',
72
+ 'kind': 'PAGINATED',
73
+ }
74
+
75
+
76
+ @responses.activate
77
+ def test_alert_record_list_success() -> None:
78
+ responses.get(
79
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}/records',
80
+ json=ALERT_RECORD_LIST_RESPONSE,
81
+ )
82
+ alert_records = AlertRecord.list(
83
+ alert_rule_id=ALERT_RULE_ID,
84
+ start_time=datetime(2023, 12, 18),
85
+ end_time=datetime(2023, 12, 25),
86
+ )
87
+ for record in alert_records:
88
+ assert isinstance(record, AlertRecord)
89
+
90
+
91
+ @responses.activate
92
+ def test_alert_record_list_success() -> None:
93
+ responses.get(
94
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}/records',
95
+ json=ALERT_RECORD_EMPTY_LIST_RESPONSE,
96
+ )
97
+ alert_records = list(AlertRecord.list(alert_rule_id=ALERT_RULE_ID))
98
+
99
+ assert len(alert_records) == 0