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.
- fiddler/_version.py +1 -1
- fiddler/api/alert_mixin.py +54 -1
- fiddler/api/webhooks_mixin.py +6 -8
- fiddler/core_objects.py +40 -32
- fiddler/schema/alert.py +12 -1
- fiddler3/__init__.py +1 -36
- fiddler3/connection.py +68 -1
- fiddler3/constants/alert_rule.py +30 -0
- fiddler3/constants/events.py +7 -0
- fiddler3/entities/__init__.py +3 -0
- fiddler3/entities/alert_record.py +106 -0
- fiddler3/entities/alert_rule.py +246 -0
- fiddler3/entities/base.py +2 -16
- fiddler3/entities/events.py +120 -0
- fiddler3/entities/files.py +170 -0
- fiddler3/entities/helpers.py +6 -6
- fiddler3/entities/model.py +67 -18
- fiddler3/entities/model_artifact.py +48 -220
- fiddler3/entities/project.py +1 -1
- fiddler3/entities/webhook.py +128 -0
- fiddler3/entities/xai.py +267 -0
- fiddler3/libs/json_encoder.py +1 -1
- fiddler3/schemas/alert_record.py +27 -0
- fiddler3/schemas/alert_rule.py +32 -0
- fiddler3/schemas/custom_features.py +27 -6
- fiddler3/schemas/events.py +15 -0
- fiddler3/schemas/files.py +23 -0
- fiddler3/schemas/model.py +1 -6
- fiddler3/schemas/webhook.py +26 -0
- fiddler3/tests/apis/test_alert_record.py +99 -0
- fiddler3/tests/apis/test_alert_rule.py +323 -0
- fiddler3/tests/apis/test_events.py +128 -0
- fiddler3/tests/apis/test_files.py +164 -0
- fiddler3/tests/apis/test_model.py +55 -8
- fiddler3/tests/apis/test_model_artifact.py +21 -0
- fiddler3/tests/apis/test_project.py +2 -2
- fiddler3/tests/apis/test_webhook.py +280 -0
- fiddler3/tests/apis/test_xai.py +233 -1
- fiddler3/tests/conftest.py +2 -2
- fiddler3/tests/constants.py +5 -0
- fiddler3/tests/test_connection.py +24 -1
- fiddler3/tests/test_json_encoder.py +2 -2
- {fiddler_client-2.4.0.dev1.dist-info → fiddler_client-2.5.0.dev1.dist-info}/METADATA +1 -1
- {fiddler_client-2.4.0.dev1.dist-info → fiddler_client-2.5.0.dev1.dist-info}/RECORD +48 -32
- tests/fiddler/test_alert.py +127 -6
- fiddler3/schemas/model_artifact.py +0 -6
- {fiddler_client-2.4.0.dev1.dist-info → fiddler_client-2.5.0.dev1.dist-info}/LICENSE.txt +0 -0
- {fiddler_client-2.4.0.dev1.dist-info → fiddler_client-2.5.0.dev1.dist-info}/WHEEL +0 -0
- {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.
|
|
1
|
+
__version__ = '2.5.0.dev1'
|
fiddler/api/alert_mixin.py
CHANGED
|
@@ -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
|
fiddler/api/webhooks_mixin.py
CHANGED
|
@@ -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 =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
1238
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1280
|
-
|
|
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.
|
|
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,
|
|
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:
|
|
1302
|
-
|
|
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
|
|
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
|
|
1322
|
+
def validate_embedding_cf(
|
|
1319
1323
|
self,
|
|
1320
|
-
feature: TextEmbedding,
|
|
1321
|
-
available_cols:
|
|
1322
|
-
valid_vector_cols:
|
|
1323
|
-
valid_enrichment_names:
|
|
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__})
|
|
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:
|
|
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:
|
|
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
|
|
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'
|
fiddler3/entities/__init__.py
CHANGED
|
@@ -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)
|