fiddler-client 2.0.0.dev8__py3-none-any.whl → 2.0.0.dev10__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/__init__.py +22 -3
- fiddler/_version.py +1 -1
- fiddler/api/alert_mixin.py +34 -12
- fiddler/api/api.py +2 -0
- fiddler/api/events_mixin.py +18 -7
- fiddler/api/model_mixin.py +1 -1
- fiddler/api/webhooks_mixin.py +121 -0
- fiddler/core_objects.py +20 -52
- fiddler/schema/alert.py +16 -3
- fiddler/schema/webhook.py +10 -0
- fiddler/schemas/custom_features.py +48 -4
- {fiddler_client-2.0.0.dev8.dist-info → fiddler_client-2.0.0.dev10.dist-info}/METADATA +10 -1
- {fiddler_client-2.0.0.dev8.dist-info → fiddler_client-2.0.0.dev10.dist-info}/RECORD +19 -16
- tests/fiddler/test_alert.py +335 -9
- tests/fiddler/test_events_api.py +1 -0
- tests/fiddler/test_webhook.py +124 -0
- {fiddler_client-2.0.0.dev8.dist-info → fiddler_client-2.0.0.dev10.dist-info}/LICENSE.txt +0 -0
- {fiddler_client-2.0.0.dev8.dist-info → fiddler_client-2.0.0.dev10.dist-info}/WHEEL +0 -0
- {fiddler_client-2.0.0.dev8.dist-info → fiddler_client-2.0.0.dev10.dist-info}/top_level.txt +0 -0
fiddler/__init__.py
CHANGED
|
@@ -10,6 +10,11 @@ TODO: Add Licence.
|
|
|
10
10
|
from fiddler import utils
|
|
11
11
|
from fiddler._version import __version__
|
|
12
12
|
from fiddler.api import FiddlerApi, FiddlerClient
|
|
13
|
+
from fiddler.api.explainability_mixin import (
|
|
14
|
+
DatasetDataSource,
|
|
15
|
+
RowDataSource,
|
|
16
|
+
SqlSliceQueryDataSource,
|
|
17
|
+
)
|
|
13
18
|
from fiddler.constants import CSV_EXTENSION
|
|
14
19
|
from fiddler.core_objects import (
|
|
15
20
|
ArtifactStatus,
|
|
@@ -18,7 +23,6 @@ from fiddler.core_objects import (
|
|
|
18
23
|
Column,
|
|
19
24
|
DatasetInfo,
|
|
20
25
|
DataType,
|
|
21
|
-
DeploymentType,
|
|
22
26
|
ExplanationMethod,
|
|
23
27
|
FiddlerPublishSchema,
|
|
24
28
|
FiddlerTimestamp,
|
|
@@ -29,8 +33,18 @@ from fiddler.core_objects import (
|
|
|
29
33
|
WindowSize,
|
|
30
34
|
)
|
|
31
35
|
from fiddler.packtools import gem
|
|
32
|
-
from fiddler.
|
|
33
|
-
|
|
36
|
+
from fiddler.schema.alert import (
|
|
37
|
+
AlertCondition,
|
|
38
|
+
AlertType,
|
|
39
|
+
BinSize,
|
|
40
|
+
ComparePeriod,
|
|
41
|
+
CompareTo,
|
|
42
|
+
Metric,
|
|
43
|
+
Priority,
|
|
44
|
+
)
|
|
45
|
+
from fiddler.schema.model_deployment import DeploymentParams, DeploymentType
|
|
46
|
+
from fiddler.schemas.custom_features import CustomFeature, ImageEmbedding, Multivariate, TextEmbedding, VectorFeature, CustomFeatureType
|
|
47
|
+
from fiddler.utils import ColorLogger, logger
|
|
34
48
|
from fiddler.utils.validator import (
|
|
35
49
|
PackageValidator,
|
|
36
50
|
ValidationChainSettings,
|
|
@@ -46,12 +60,17 @@ __all__ = [
|
|
|
46
60
|
'__version__',
|
|
47
61
|
'BatchPublishType',
|
|
48
62
|
'Column',
|
|
63
|
+
'CustomFeature',
|
|
64
|
+
'CustomFeatureType',
|
|
49
65
|
'Multivariate',
|
|
66
|
+
'VectorFeature',
|
|
50
67
|
'TextEmbedding',
|
|
51
68
|
'ImageEmbedding',
|
|
52
69
|
'ColorLogger',
|
|
53
70
|
'DatasetInfo',
|
|
54
71
|
'DataType',
|
|
72
|
+
'DeploymentParams',
|
|
73
|
+
'DeploymentType',
|
|
55
74
|
'FiddlerClient',
|
|
56
75
|
'FiddlerApi',
|
|
57
76
|
'FiddlerTimestamp',
|
fiddler/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '2.0.0.
|
|
1
|
+
__version__ = '2.0.0.dev10'
|
fiddler/api/alert_mixin.py
CHANGED
|
@@ -35,7 +35,7 @@ class AlertMixin:
|
|
|
35
35
|
FILTER_ALERT_RULES_API_VERSION = '>=23.1.0'
|
|
36
36
|
GROUP_OF_COLUMNS_API_VERSION = '>=23.3.0'
|
|
37
37
|
RULE_V3_API_VERSION = '>=23.4.0'
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
client: RequestClient
|
|
40
40
|
organization_name: str
|
|
41
41
|
|
|
@@ -54,6 +54,7 @@ class AlertMixin:
|
|
|
54
54
|
alert_type: Optional[AlertType] = None,
|
|
55
55
|
baseline_name: Optional[str] = None,
|
|
56
56
|
compare_period: Optional[ComparePeriod] = None,
|
|
57
|
+
column: Optional[str] = None,
|
|
57
58
|
columns: Optional[List[str]] = None,
|
|
58
59
|
warning_threshold: Optional[float] = None,
|
|
59
60
|
project_name: str = None,
|
|
@@ -120,19 +121,13 @@ class AlertMixin:
|
|
|
120
121
|
:param condition: Select from:
|
|
121
122
|
1) AlertCondition.LESSER
|
|
122
123
|
2) AlertCondition.GREATER
|
|
124
|
+
:param column: column name on which alert rule is to be created. Supported by server version 23.3.0 or lower.
|
|
123
125
|
:param columns: List of column names on which alert rule is to be created. It can take ['__ANY__'] to check for all columns
|
|
124
126
|
:param notifications_config: notifications config object created using helper method build_notifications_config()
|
|
125
127
|
:param baseline_name: Name of the baselne, whose histogram is compared against the same derived from current data.
|
|
126
128
|
Used only when alert type is AlertType.DATA_DRIFT.
|
|
127
129
|
:return: created alert rule object
|
|
128
|
-
"""
|
|
129
|
-
if columns and not match_semver(
|
|
130
|
-
self.server_info.server_version, self.GROUP_OF_COLUMNS_API_VERSION
|
|
131
|
-
):
|
|
132
|
-
raise ValueError(
|
|
133
|
-
f'columns parameter works with server version {self.GROUP_OF_COLUMNS_API_VERSION}'
|
|
134
|
-
)
|
|
135
|
-
|
|
130
|
+
"""
|
|
136
131
|
project_name = project_id_compat(
|
|
137
132
|
project_id=project_id, project_name=project_name
|
|
138
133
|
)
|
|
@@ -154,7 +149,7 @@ class AlertMixin:
|
|
|
154
149
|
if compare_period and compare_period not in ComparePeriod.keys():
|
|
155
150
|
raise ValueError(f'compare_period should be one of{ComparePeriod.keys()}')
|
|
156
151
|
|
|
157
|
-
|
|
152
|
+
alert_rule_payload = AlertRulePayload(
|
|
158
153
|
organization_name=self.organization_name,
|
|
159
154
|
project_name=project_name,
|
|
160
155
|
model_name=model_name,
|
|
@@ -166,14 +161,41 @@ class AlertMixin:
|
|
|
166
161
|
compare_period=compare_period,
|
|
167
162
|
priority=priority,
|
|
168
163
|
baseline_name=baseline_name,
|
|
169
|
-
feature_names=columns,
|
|
170
164
|
warning_threshold=warning_threshold,
|
|
171
165
|
critical_threshold=critical_threshold,
|
|
172
166
|
condition=condition,
|
|
173
167
|
time_bucket=bin_size.value[0],
|
|
174
168
|
bin_size=bin_size.value[1],
|
|
175
169
|
notifications=notifications_config,
|
|
176
|
-
)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if columns and not match_semver(
|
|
173
|
+
self.server_info.server_version, self.GROUP_OF_COLUMNS_API_VERSION
|
|
174
|
+
):
|
|
175
|
+
raise ValueError(
|
|
176
|
+
f'columns parameter works with server version {self.GROUP_OF_COLUMNS_API_VERSION}'
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
if match_semver(self.server_info.server_version, self.GROUP_OF_COLUMNS_API_VERSION):
|
|
180
|
+
if columns:
|
|
181
|
+
if match_semver(self.server_info.server_version, self.RULE_V3_API_VERSION):
|
|
182
|
+
alert_rule_payload.feature_names=columns
|
|
183
|
+
else:
|
|
184
|
+
# version 23.3 support columns is comma separated string
|
|
185
|
+
alert_rule_payload.feature_names=",".join(columns)
|
|
186
|
+
elif column:
|
|
187
|
+
if match_semver(self.server_info.server_version, self.RULE_V3_API_VERSION):
|
|
188
|
+
alert_rule_payload.feature_names=[column]
|
|
189
|
+
else:
|
|
190
|
+
alert_rule_payload.feature_names=column
|
|
191
|
+
else:
|
|
192
|
+
# we do not support columns in server version < 23.3
|
|
193
|
+
if columns:
|
|
194
|
+
raise ValueError(f'columns is not supported in the server version {self.server_info.server_version}. Please use column instead.')
|
|
195
|
+
if column:
|
|
196
|
+
alert_rule_payload.feature_name=column
|
|
197
|
+
|
|
198
|
+
request_body = alert_rule_payload.dict()
|
|
177
199
|
response = self.client.post(
|
|
178
200
|
url='alert-configs',
|
|
179
201
|
data=request_body,
|
fiddler/api/api.py
CHANGED
|
@@ -15,6 +15,7 @@ from fiddler.api.job_mixin import JobMixin
|
|
|
15
15
|
from fiddler.api.model_deployment_mixin import ModelDeploymentMixin
|
|
16
16
|
from fiddler.api.model_mixin import ModelMixin
|
|
17
17
|
from fiddler.api.project_mixin import ProjectMixin
|
|
18
|
+
from fiddler.api.webhooks_mixin import WebhookMixin
|
|
18
19
|
from fiddler.constants import (
|
|
19
20
|
CURRENT_API_VERSION,
|
|
20
21
|
FIDDLER_CLIENT_VERSION_HEADER,
|
|
@@ -41,6 +42,7 @@ class FiddlerClient(
|
|
|
41
42
|
ExplainabilityMixin,
|
|
42
43
|
ModelDeploymentMixin,
|
|
43
44
|
GenerateSchemaMixin,
|
|
45
|
+
WebhookMixin
|
|
44
46
|
):
|
|
45
47
|
def __init__(
|
|
46
48
|
self,
|
fiddler/api/events_mixin.py
CHANGED
|
@@ -14,11 +14,12 @@ from fiddler.exceptions import HttpException
|
|
|
14
14
|
from fiddler.libs.http_client import RequestClient
|
|
15
15
|
from fiddler.schema.events import EventIngest, EventsIngest
|
|
16
16
|
from fiddler.utils.compatibility_helpers import (
|
|
17
|
-
project_id_compat,
|
|
18
17
|
model_id_compat,
|
|
18
|
+
project_id_compat,
|
|
19
19
|
update_event_compat,
|
|
20
20
|
)
|
|
21
21
|
from fiddler.utils.decorators import handle_api_error_response
|
|
22
|
+
from fiddler.utils.helpers import match_semver
|
|
22
23
|
from fiddler.utils.logger import get_logger
|
|
23
24
|
from fiddler.utils.response_handler import APIResponseHandler
|
|
24
25
|
|
|
@@ -235,7 +236,7 @@ class EventsMixin:
|
|
|
235
236
|
timestamp_field=timestamp_field,
|
|
236
237
|
timestamp_format=timestamp_format,
|
|
237
238
|
group_by=group_by,
|
|
238
|
-
file_type=
|
|
239
|
+
file_type=file_type,
|
|
239
240
|
wait=wait,
|
|
240
241
|
)
|
|
241
242
|
else:
|
|
@@ -368,14 +369,23 @@ class EventsMixin:
|
|
|
368
369
|
'The batch provided is empty. Please retry with at least one row of data.'
|
|
369
370
|
)
|
|
370
371
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
372
|
+
if self.server_info and match_semver(
|
|
373
|
+
self.server_info.server_version, '>=23.4.0'
|
|
374
|
+
):
|
|
375
|
+
file_type = FileType.PARQUET
|
|
376
|
+
else:
|
|
377
|
+
file_type = FileType.CSV
|
|
378
|
+
|
|
379
|
+
with tempfile.NamedTemporaryFile(suffix=file_type) as temp_file:
|
|
380
|
+
if file_type == FileType.PARQUET:
|
|
381
|
+
events_df.to_parquet(temp_file.name, index=False)
|
|
382
|
+
else:
|
|
383
|
+
events_df.to_csv(temp_file.name, index=False)
|
|
384
|
+
|
|
375
385
|
return self.publish_events_batch(
|
|
376
386
|
project_name=project_name,
|
|
377
387
|
model_name=model_name,
|
|
378
|
-
events_path=
|
|
388
|
+
events_path=Path(temp_file.name),
|
|
379
389
|
batch_id=batch_id,
|
|
380
390
|
id_field=id_field,
|
|
381
391
|
is_update=is_update,
|
|
@@ -383,6 +393,7 @@ class EventsMixin:
|
|
|
383
393
|
timestamp_format=timestamp_format,
|
|
384
394
|
group_by=group_by,
|
|
385
395
|
wait=wait,
|
|
396
|
+
file_type=file_type,
|
|
386
397
|
)
|
|
387
398
|
|
|
388
399
|
@handle_api_error_response
|
fiddler/api/model_mixin.py
CHANGED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from http import HTTPStatus
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import parse_obj_as
|
|
5
|
+
|
|
6
|
+
from fiddler.libs.http_client import RequestClient
|
|
7
|
+
from fiddler.schema.webhook import Webhook
|
|
8
|
+
from fiddler.utils.decorators import handle_api_error_response
|
|
9
|
+
from fiddler.utils.logger import get_logger
|
|
10
|
+
from fiddler.utils.response_handler import (
|
|
11
|
+
APIResponseHandler,
|
|
12
|
+
PaginatedResponseHandler,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
logger = get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class WebhookMixin:
|
|
19
|
+
client: RequestClient
|
|
20
|
+
organization_name: str
|
|
21
|
+
|
|
22
|
+
@handle_api_error_response
|
|
23
|
+
def add_webhook(self, name:str, url:str, provider:str) -> Webhook:
|
|
24
|
+
"""
|
|
25
|
+
Add a new `Webhook`.
|
|
26
|
+
|
|
27
|
+
:params name: name of webhook
|
|
28
|
+
:params url: webhook url
|
|
29
|
+
:params provider: Either 'SLACK' or 'OTHER'
|
|
30
|
+
|
|
31
|
+
:returns: Created `Webhook` object.
|
|
32
|
+
"""
|
|
33
|
+
request_body = {
|
|
34
|
+
'name': name,
|
|
35
|
+
'url': url,
|
|
36
|
+
'provider': provider,
|
|
37
|
+
}
|
|
38
|
+
response = self.client.post(
|
|
39
|
+
url='webhooks',
|
|
40
|
+
params={'organization_name': self.organization_name},
|
|
41
|
+
data=request_body,
|
|
42
|
+
)
|
|
43
|
+
if response.status_code == HTTPStatus.OK:
|
|
44
|
+
logger.info(f'Webhook created successfully.')
|
|
45
|
+
else:
|
|
46
|
+
logger.info('Webhook creation unsuccessful')
|
|
47
|
+
return Webhook.deserialize(APIResponseHandler(response))
|
|
48
|
+
|
|
49
|
+
@handle_api_error_response
|
|
50
|
+
def delete_webhook(self, uuid: str = None) -> None:
|
|
51
|
+
"""
|
|
52
|
+
Delete a webhook
|
|
53
|
+
|
|
54
|
+
:params uuid: uuid of the Webhook to delete
|
|
55
|
+
:returns: None
|
|
56
|
+
"""
|
|
57
|
+
response = self.client.delete(
|
|
58
|
+
url=f'webhooks/{uuid}'
|
|
59
|
+
)
|
|
60
|
+
if response.status_code == HTTPStatus.OK:
|
|
61
|
+
logger.info(f'Webhook {uuid} deleted successfully.')
|
|
62
|
+
else:
|
|
63
|
+
logger.info('Webhook deletion unsuccessful')
|
|
64
|
+
|
|
65
|
+
@handle_api_error_response
|
|
66
|
+
def get_webhook(self, uuid:str) -> List[Webhook]:
|
|
67
|
+
"""
|
|
68
|
+
Get a webhook
|
|
69
|
+
|
|
70
|
+
:params uuid: UUID belongs to the Webhook
|
|
71
|
+
:returns: `Webhook` object
|
|
72
|
+
"""
|
|
73
|
+
response = self.client.get(
|
|
74
|
+
url=f'webhooks/{uuid}',
|
|
75
|
+
)
|
|
76
|
+
return Webhook.deserialize(APIResponseHandler(response))
|
|
77
|
+
|
|
78
|
+
@handle_api_error_response
|
|
79
|
+
def update_webhook(self, uuid: str, name:str, url:str, provider:str) -> Webhook:
|
|
80
|
+
"""
|
|
81
|
+
Update a webhook, by changing one or more of name, url or provider.
|
|
82
|
+
:params name: name of webhook
|
|
83
|
+
:params url: webhook url
|
|
84
|
+
:params provider: Either 'SLACK' or 'OTHER'
|
|
85
|
+
|
|
86
|
+
:returns: Updated `Webhook` object.
|
|
87
|
+
"""
|
|
88
|
+
request_body = Webhook(
|
|
89
|
+
name=name, url=url, provider=provider
|
|
90
|
+
).dict()
|
|
91
|
+
response = self.client.patch(
|
|
92
|
+
url=f'webhooks/{uuid}',
|
|
93
|
+
data=request_body,
|
|
94
|
+
)
|
|
95
|
+
if response.status_code == HTTPStatus.OK:
|
|
96
|
+
logger.info(f'Webhook {uuid} updated successfully.')
|
|
97
|
+
else:
|
|
98
|
+
logger.info('Webhook updation unsuccessful')
|
|
99
|
+
return Webhook.deserialize(APIResponseHandler(response))
|
|
100
|
+
|
|
101
|
+
@handle_api_error_response
|
|
102
|
+
def get_webhooks(self, limit: int = 300, offset: int = 0) -> List[Webhook]:
|
|
103
|
+
"""
|
|
104
|
+
Get a list of all webhooks in the organization
|
|
105
|
+
|
|
106
|
+
:params limit: Number of webhooks to fetch in a call
|
|
107
|
+
:params offset: Number of rows to skip before any rows are retrived
|
|
108
|
+
:returns: List of `Webhook` object
|
|
109
|
+
"""
|
|
110
|
+
response = self.client.get(
|
|
111
|
+
url='webhooks',
|
|
112
|
+
params={
|
|
113
|
+
'organization_name': self.organization_name,
|
|
114
|
+
'limit': limit,
|
|
115
|
+
'offset': offset,
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
items = PaginatedResponseHandler(response).get_pagination_items()
|
|
119
|
+
return parse_obj_as(List[Webhook], items)
|
|
120
|
+
|
|
121
|
+
|
fiddler/core_objects.py
CHANGED
|
@@ -24,7 +24,12 @@ import numpy as np
|
|
|
24
24
|
import pandas as pd
|
|
25
25
|
import pandas.api.types
|
|
26
26
|
|
|
27
|
-
from fiddler.schemas.custom_features import
|
|
27
|
+
from fiddler.schemas.custom_features import (
|
|
28
|
+
CustomFeature,
|
|
29
|
+
ImageEmbedding,
|
|
30
|
+
TextEmbedding,
|
|
31
|
+
VectorFeature,
|
|
32
|
+
)
|
|
28
33
|
from fiddler.utils.formatting import prettyprint_number, validate_sanitized_names
|
|
29
34
|
from fiddler.utils.logger import get_logger
|
|
30
35
|
from fiddler.utils.pandas_helper import is_datetime, is_list, is_vector_value
|
|
@@ -199,6 +204,7 @@ class ModelTask(enum.Enum):
|
|
|
199
204
|
MULTICLASS_CLASSIFICATION = 'multiclass_classification'
|
|
200
205
|
REGRESSION = 'regression'
|
|
201
206
|
RANKING = 'ranking'
|
|
207
|
+
NOT_SET = 'not_set'
|
|
202
208
|
|
|
203
209
|
def is_classification(self):
|
|
204
210
|
return self.value in (
|
|
@@ -1235,6 +1241,9 @@ class ModelInfo:
|
|
|
1235
1241
|
for k, v in numeric_cols.items()
|
|
1236
1242
|
if v in (DataType.INTEGER.value, DataType.FLOAT.value)
|
|
1237
1243
|
]
|
|
1244
|
+
valid_vector_cols = [
|
|
1245
|
+
input.name for input in self.inputs if input.data_type.is_vector()
|
|
1246
|
+
]
|
|
1238
1247
|
|
|
1239
1248
|
unique_cf_names = []
|
|
1240
1249
|
for feature in self.custom_features:
|
|
@@ -1256,17 +1265,17 @@ class ModelInfo:
|
|
|
1256
1265
|
else:
|
|
1257
1266
|
unique_cf_names.append(feature.name)
|
|
1258
1267
|
|
|
1259
|
-
if isinstance(feature
|
|
1268
|
+
if isinstance(feature, (TextEmbedding, ImageEmbedding, VectorFeature)):
|
|
1269
|
+
if not feature.column in valid_vector_cols:
|
|
1270
|
+
raise ValueError(
|
|
1271
|
+
f"Custom features '{feature.name}' defined on column '{feature.column}' is not found"
|
|
1272
|
+
)
|
|
1273
|
+
else:
|
|
1260
1274
|
for col in feature.columns:
|
|
1261
1275
|
if col not in valid_col_names:
|
|
1262
1276
|
raise ValueError(
|
|
1263
1277
|
f"Custom features '{feature.name}' defined on column '{col}' is not found or not of type int/float."
|
|
1264
1278
|
)
|
|
1265
|
-
else:
|
|
1266
|
-
if feature.columns not in valid_col_names:
|
|
1267
|
-
raise ValueError(
|
|
1268
|
-
f"Custom features '{feature.name}' defined on column '{feature.columns}' is not found or not of type int/float."
|
|
1269
|
-
)
|
|
1270
1279
|
|
|
1271
1280
|
def warn_deprecated_parameter(self, param_name: str, from_version: str) -> None:
|
|
1272
1281
|
warnings.warn(
|
|
@@ -1814,14 +1823,9 @@ class ModelInfo:
|
|
|
1814
1823
|
# todo (pradhuman): replace categorical_target_class_details with target_class_order
|
|
1815
1824
|
|
|
1816
1825
|
if not model_task:
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
outputs=outputs,
|
|
1821
|
-
)
|
|
1822
|
-
LOG.warning(
|
|
1823
|
-
f'Fiddler inferred your model as a {model_task.value} model. '
|
|
1824
|
-
f'If this is wrong, please specify your model task.'
|
|
1826
|
+
raise ValueError(
|
|
1827
|
+
'model_task is required to create a model info. Please specify a value '
|
|
1828
|
+
'using the ModelTask enum.'
|
|
1825
1829
|
)
|
|
1826
1830
|
|
|
1827
1831
|
(
|
|
@@ -2290,42 +2294,6 @@ class ModelInfo:
|
|
|
2290
2294
|
)
|
|
2291
2295
|
return is_binary_ranking_model, top_k, bin_class_threshold
|
|
2292
2296
|
|
|
2293
|
-
@classmethod
|
|
2294
|
-
def infer_model_task(
|
|
2295
|
-
cls,
|
|
2296
|
-
dataset_info: DatasetInfo,
|
|
2297
|
-
target: Optional[str],
|
|
2298
|
-
outputs: Optional[List[str]],
|
|
2299
|
-
) -> ModelTask:
|
|
2300
|
-
"""
|
|
2301
|
-
Inference assuming user gave all right parameters.
|
|
2302
|
-
After, we run ModelInfoValidator to check all parameters.
|
|
2303
|
-
:param dataset_info: dataset info object
|
|
2304
|
-
:param target: target column name
|
|
2305
|
-
:param outputs: list of output column name(s)
|
|
2306
|
-
:return: Inferred Model task when possible
|
|
2307
|
-
"""
|
|
2308
|
-
if not outputs or not target:
|
|
2309
|
-
raise ValueError(
|
|
2310
|
-
f'Fiddler cannot infer your model task because target and/or outputs '
|
|
2311
|
-
f'are not specified. Please specify your model task.'
|
|
2312
|
-
)
|
|
2313
|
-
if len(outputs) > 1:
|
|
2314
|
-
return ModelTask.MULTICLASS_CLASSIFICATION
|
|
2315
|
-
|
|
2316
|
-
target_col = dataset_info[target]
|
|
2317
|
-
output_col = dataset_info[outputs[0]]
|
|
2318
|
-
|
|
2319
|
-
if ModelInfo.check_binary_target(target_column=target_col):
|
|
2320
|
-
if output_col.value_range_min >= 0.0 and output_col.value_range_max <= 1.0:
|
|
2321
|
-
return ModelTask.BINARY_CLASSIFICATION
|
|
2322
|
-
return ModelTask.RANKING
|
|
2323
|
-
|
|
2324
|
-
if not target_col.data_type.is_numeric():
|
|
2325
|
-
return ModelTask.RANKING
|
|
2326
|
-
|
|
2327
|
-
return ModelTask.REGRESSION
|
|
2328
|
-
|
|
2329
2297
|
@classmethod
|
|
2330
2298
|
def validate_model_task(
|
|
2331
2299
|
cls,
|
|
@@ -2402,7 +2370,7 @@ class ModelInfo:
|
|
|
2402
2370
|
target_column: Column,
|
|
2403
2371
|
) -> Optional[List[Union[str, bool, float, int]]]:
|
|
2404
2372
|
|
|
2405
|
-
if model_task.value
|
|
2373
|
+
if model_task.value in [ModelTask.REGRESSION.value, ModelTask.NOT_SET.value]:
|
|
2406
2374
|
return None
|
|
2407
2375
|
|
|
2408
2376
|
if isinstance(target_class_order, (str, int, float, bool)):
|
fiddler/schema/alert.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import enum
|
|
2
2
|
from typing import Any, Dict, List, Optional, Union
|
|
3
3
|
|
|
4
|
+
from ast import literal_eval
|
|
4
5
|
from pydantic import Field, root_validator
|
|
5
6
|
|
|
6
7
|
from fiddler.schema.base import BaseDataSchema
|
|
@@ -140,6 +141,7 @@ class AlertRulePayload(BaseDataSchema):
|
|
|
140
141
|
alert_type: Optional[AlertType]
|
|
141
142
|
metric: Metric
|
|
142
143
|
metric_id: Union[Metric, str]
|
|
144
|
+
feature_name: Optional[str]
|
|
143
145
|
feature_names: Optional[List[str]]
|
|
144
146
|
priority: Priority
|
|
145
147
|
compare_to: CompareTo
|
|
@@ -161,7 +163,8 @@ class AlertRule(BaseDataSchema):
|
|
|
161
163
|
name: Optional[str]
|
|
162
164
|
alert_type: Optional[AlertType]
|
|
163
165
|
metric: str
|
|
164
|
-
|
|
166
|
+
column: Optional[str] = Field(alias='feature_name', default=None)
|
|
167
|
+
columns: Optional[List[str]] = Field(alias='feature_names', default=None)
|
|
165
168
|
baseline_name: Optional[str]
|
|
166
169
|
priority: Priority
|
|
167
170
|
compare_to: CompareTo
|
|
@@ -172,7 +175,15 @@ class AlertRule(BaseDataSchema):
|
|
|
172
175
|
bin_size: Optional[str]
|
|
173
176
|
time_bucket: Optional[int]
|
|
174
177
|
|
|
175
|
-
|
|
178
|
+
@root_validator(pre=True)
|
|
179
|
+
def set_feature_names(cls, values) -> dict:
|
|
180
|
+
if 'feature_names' in values and type(values['feature_names']) == str:
|
|
181
|
+
# in release 23.3 feature_names is stringified list. 23.4 onwards its List[str]
|
|
182
|
+
try:
|
|
183
|
+
values['feature_names'] = literal_eval(values['feature_names'])
|
|
184
|
+
except SyntaxError:
|
|
185
|
+
values['feature_names'] = None
|
|
186
|
+
return values
|
|
176
187
|
|
|
177
188
|
class TriggeredAlerts(BaseDataSchema):
|
|
178
189
|
id: int
|
|
@@ -180,10 +191,12 @@ class TriggeredAlerts(BaseDataSchema):
|
|
|
180
191
|
alert_rule_uuid: str = Field(alias='alert_config_uuid')
|
|
181
192
|
alert_run_start_time: int
|
|
182
193
|
alert_time_bucket: int
|
|
183
|
-
alert_value: Dict[str, float]
|
|
194
|
+
alert_value: Union[float, Dict[str, float]]
|
|
184
195
|
baseline_time_bucket: Optional[int]
|
|
185
196
|
baseline_value: Optional[float]
|
|
186
197
|
is_alert: bool
|
|
187
198
|
severity: Optional[str]
|
|
188
199
|
failure_reason: str
|
|
189
200
|
message: str
|
|
201
|
+
multi_col_values: Optional[Dict[str, float]]
|
|
202
|
+
feature_name: Optional[str]
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
-
from typing import List, Optional, Type, TypeVar
|
|
2
|
+
from typing import Any, Dict, List, Optional, Type, TypeVar
|
|
3
3
|
|
|
4
4
|
from pydantic import BaseModel
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
CustomFeatureTypeVar = TypeVar('CustomFeatureTypeVar', bound='CustomFeature')
|
|
7
|
+
DEFAULT_NUM_CLUSTERS = 5
|
|
8
|
+
DEFAULT_NUM_TAGS = 5
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class CustomFeatureType(str, Enum):
|
|
10
12
|
FROM_COLUMNS = 'FROM_COLUMNS'
|
|
13
|
+
FROM_VECTOR = 'FROM_VECTOR'
|
|
11
14
|
FROM_TEXT_EMBEDDING = 'FROM_TEXT_EMBEDDING'
|
|
12
15
|
FROM_IMAGE_EMBEDDING = 'FROM_IMAGE_EMBEDDING'
|
|
13
16
|
|
|
@@ -15,17 +18,31 @@ class CustomFeatureType(str, Enum):
|
|
|
15
18
|
class CustomFeature(BaseModel):
|
|
16
19
|
name: str
|
|
17
20
|
type: CustomFeatureType
|
|
18
|
-
n_clusters: Optional[int] =
|
|
21
|
+
n_clusters: Optional[int] = DEFAULT_NUM_CLUSTERS
|
|
19
22
|
centroids: Optional[List] = None
|
|
20
23
|
|
|
21
24
|
class Config:
|
|
22
25
|
allow_mutation = False
|
|
23
26
|
|
|
24
27
|
@classmethod
|
|
25
|
-
def
|
|
28
|
+
def from_columns(
|
|
29
|
+
cls, custom_name: str, cols: List[str], n_clusters: int = DEFAULT_NUM_CLUSTERS
|
|
30
|
+
) -> 'Multivariate':
|
|
31
|
+
return Multivariate(
|
|
32
|
+
name=custom_name,
|
|
33
|
+
columns=cols,
|
|
34
|
+
n_clusters=n_clusters,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_dict(
|
|
39
|
+
cls: Type[CustomFeatureTypeVar], deserialized_json: dict
|
|
40
|
+
) -> CustomFeatureTypeVar:
|
|
26
41
|
feature_type = CustomFeatureType(deserialized_json['type'])
|
|
27
42
|
if feature_type == CustomFeatureType.FROM_COLUMNS:
|
|
28
43
|
return Multivariate.parse_obj(deserialized_json)
|
|
44
|
+
elif feature_type == CustomFeatureType.FROM_VECTOR:
|
|
45
|
+
return VectorFeature.parse_obj(deserialized_json)
|
|
29
46
|
elif feature_type == CustomFeatureType.FROM_TEXT_EMBEDDING:
|
|
30
47
|
return TextEmbedding.parse_obj(deserialized_json)
|
|
31
48
|
elif feature_type == CustomFeatureType.FROM_IMAGE_EMBEDDING:
|
|
@@ -33,19 +50,46 @@ class CustomFeature(BaseModel):
|
|
|
33
50
|
else:
|
|
34
51
|
raise ValueError(f'Unsupported feature type: {feature_type}')
|
|
35
52
|
|
|
53
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
54
|
+
return_dict = {
|
|
55
|
+
'name': self.name,
|
|
56
|
+
'type': self.type,
|
|
57
|
+
'n_clusters': self.n_clusters,
|
|
58
|
+
}
|
|
59
|
+
if self.type == CustomFeatureType.FROM_COLUMNS:
|
|
60
|
+
return_dict['columns'] = self.columns
|
|
61
|
+
elif self.type == CustomFeatureType.FROM_VECTOR:
|
|
62
|
+
return_dict['column'] = self.column
|
|
63
|
+
elif self.type in (
|
|
64
|
+
CustomFeatureType.FROM_TEXT_EMBEDDING,
|
|
65
|
+
CustomFeatureType.FROM_IMAGE_EMBEDDING,
|
|
66
|
+
):
|
|
67
|
+
return_dict['source_column'] = self.source_column
|
|
68
|
+
return_dict['column'] = self.column
|
|
69
|
+
|
|
70
|
+
if self.type == CustomFeatureType.FROM_TEXT_EMBEDDING:
|
|
71
|
+
return_dict['n_tags'] = self.n_tags
|
|
72
|
+
else:
|
|
73
|
+
raise ValueError(f'Unsupported feature type: {self.type}')
|
|
74
|
+
|
|
75
|
+
return return_dict
|
|
76
|
+
|
|
36
77
|
|
|
37
78
|
class Multivariate(CustomFeature):
|
|
79
|
+
type: CustomFeatureType = CustomFeatureType.FROM_COLUMNS
|
|
38
80
|
columns: List[str]
|
|
39
81
|
monitor_components: bool = False
|
|
40
82
|
|
|
41
83
|
|
|
42
84
|
class VectorFeature(CustomFeature):
|
|
85
|
+
type: CustomFeatureType = CustomFeatureType.FROM_VECTOR
|
|
43
86
|
source_column: Optional[str] = None
|
|
44
87
|
column: str
|
|
45
88
|
|
|
46
89
|
|
|
47
90
|
class TextEmbedding(VectorFeature):
|
|
48
91
|
type: CustomFeatureType = CustomFeatureType.FROM_TEXT_EMBEDDING
|
|
92
|
+
n_tags: Optional[int] = DEFAULT_NUM_TAGS
|
|
49
93
|
|
|
50
94
|
|
|
51
95
|
class ImageEmbedding(VectorFeature):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fiddler-client
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev10
|
|
4
4
|
Summary: Python client for Fiddler Service
|
|
5
5
|
Home-page: https://fiddler.ai
|
|
6
6
|
Author: Fiddler Labs
|
|
@@ -63,6 +63,15 @@ Version History
|
|
|
63
63
|
- Add `monitor_components` as an attribute of `CustomFeature`. Default to `False`
|
|
64
64
|
- Remove `column` as a parameter in `add_alert_rule` and `get_alert_rules` functions
|
|
65
65
|
|
|
66
|
+
### 1.8.4
|
|
67
|
+
- #### **New Features**
|
|
68
|
+
- New DeploymentType enum for MANUAL deployment
|
|
69
|
+
|
|
70
|
+
### 1.8.3
|
|
71
|
+
- ### **Modifications**
|
|
72
|
+
- New `columns` parameter in add_alert_rule and get_alert_rules to support multiple columns to be used for server version >= 23.3.0
|
|
73
|
+
- get_triggered_alerts supports `alert_value` as a float as well as a dict
|
|
74
|
+
|
|
66
75
|
### 1.8.2
|
|
67
76
|
- #### **Modifications**
|
|
68
77
|
- Fixed a bug where `min` and `max` for columns of type `float` in `dataset_info` are cast into `int` after uploaded
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
fiddler/__init__.py,sha256=
|
|
2
|
-
fiddler/_version.py,sha256=
|
|
1
|
+
fiddler/__init__.py,sha256=Vv9ypQhEkjFdnhiIuv79m8ZtBVlsegjt2anDH6DVHds,1960
|
|
2
|
+
fiddler/_version.py,sha256=Vkmm08Vum_RV2_Nb1HuOhJ09Soq68UZ8mIOy9lPfsvY,28
|
|
3
3
|
fiddler/constants.py,sha256=sNPexKXMCVC9iQNIArgIQBITGhgHuBUiNklEXTD_eFI,1822
|
|
4
|
-
fiddler/core_objects.py,sha256=
|
|
4
|
+
fiddler/core_objects.py,sha256=DZnJRwgfeL7lSVco_veU-Depwe_PJ411uFLvQm8ZHkY,111717
|
|
5
5
|
fiddler/exceptions.py,sha256=tis5S9jdGsVmXkFjulLNEHeiFQ8lFjJYlYr68H83euU,2490
|
|
6
6
|
fiddler/api/__init__.py,sha256=9PidyuY4iRgOeEPFyBYo0jcSbTf7KeE3jsDXwFP4ZHs,109
|
|
7
|
-
fiddler/api/alert_mixin.py,sha256=
|
|
8
|
-
fiddler/api/api.py,sha256=
|
|
7
|
+
fiddler/api/alert_mixin.py,sha256=Qgp58GPFY52Dq0JzTmXJ3hjZFn4yieQADWrZKqENrn8,18143
|
|
8
|
+
fiddler/api/api.py,sha256=nNOOJcqUPzYpY1jYL9brzZI5_OxDJYq48K3rQAZJ-1M,4256
|
|
9
9
|
fiddler/api/baseline_mixin.py,sha256=D-IvvUDHN-J4y_g_irp5_Y5g53cTL3AEYuu121YnHGo,9813
|
|
10
10
|
fiddler/api/compatibility_mixin.py,sha256=_1vaScKiJeKbPmFjT30IbhCbUoDDQoxHDwQ6o0qSrm0,1313
|
|
11
11
|
fiddler/api/dataset_mixin.py,sha256=5LuaPU1PbTS3srq3vSUUK-e2-Fc56M7ClNl9oRVX8e0,16963
|
|
12
|
-
fiddler/api/events_mixin.py,sha256=
|
|
12
|
+
fiddler/api/events_mixin.py,sha256=zw_wIjrQVIg9LqaAzz8Ye0u4m26LtYMp-dau2v9UVDk,22447
|
|
13
13
|
fiddler/api/explainability_mixin.py,sha256=WsyDEtGAWfQYN9HdS8b8uUan9vjKWvUSB43F7KkjiCg,15332
|
|
14
14
|
fiddler/api/generate_schema_mixin.py,sha256=QWFnMRi0k5mf0wPpCxAkrffbQz5JNU2OgERy5KgVP5w,9792
|
|
15
15
|
fiddler/api/helpers.py,sha256=-ZLyXnZhWJK9IvdKqZVgDo2c3wpYwI5EizFFu4stQjk,4722
|
|
16
16
|
fiddler/api/job_mixin.py,sha256=wGJ8yrKYvNa3_6-4o28-IJ8Zrep_uZ1L2DO2XMaLUuI,4496
|
|
17
17
|
fiddler/api/model_artifact_deploy.py,sha256=t72HkQ2JsQzCB5bUsFuS71KWDJ-KVcKkdtyef-dwnMY,8265
|
|
18
18
|
fiddler/api/model_deployment_mixin.py,sha256=xZ26FwixQ4FvsGSmQvrUB7joveIGqE07Vph7ADoGDRc,4118
|
|
19
|
-
fiddler/api/model_mixin.py,sha256=
|
|
19
|
+
fiddler/api/model_mixin.py,sha256=7_Y4NSFx8NpxbPQdHpXNVSMN2wBN445YAZOnFPOchuI,21055
|
|
20
20
|
fiddler/api/project_mixin.py,sha256=_RRuPnBzANP2Kyo0J_CqG6wqFawRz1Y29z88BUx66Ag,3198
|
|
21
|
+
fiddler/api/webhooks_mixin.py,sha256=nh7EU-VAabxZr42f7iUbphBOudyt-DEnjRLfypetBdw,3800
|
|
21
22
|
fiddler/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
23
|
fiddler/assets/pg_reserved_words.py,sha256=Ar8Y4vrw5cJxc6xMmul42cNw0NEu-LjEV5jK6QlKisY,8200
|
|
23
24
|
fiddler/libs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -28,7 +29,7 @@ fiddler/packtools/keras_ig_helpers.py,sha256=66KhHhiYYHryieF7DYhmHC-S6D5NUgrm7lM
|
|
|
28
29
|
fiddler/packtools/project_attributions_helpers.py,sha256=PzMTDPUX0KqE-kLyrkW46W6F4nDRvRIka0HjzLYxI4k,14619
|
|
29
30
|
fiddler/packtools/template_model.py,sha256=-I0hMcuZsHc3jw59o4hyTztU-V1onKJ5z0rKkZzSy1A,9500
|
|
30
31
|
fiddler/schema/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
-
fiddler/schema/alert.py,sha256=
|
|
32
|
+
fiddler/schema/alert.py,sha256=8OUJPWZbHHOjdgKS4mvP7rrDoiNYmoCSTrIdUXs8GTo,5199
|
|
32
33
|
fiddler/schema/base.py,sha256=3P0h6r1flDQeXfLRANSt6Rng2mTy17hKPYtopFxjKF8,343
|
|
33
34
|
fiddler/schema/baseline.py,sha256=h8SyR32L1AMo2DrcwgEOqWN-U538-XFngjAtQQfMklI,487
|
|
34
35
|
fiddler/schema/dataset.py,sha256=kFr_BlP9-hFRjYxy_ezr3M7bmedfK_ZznAdLfvBxYlQ,3045
|
|
@@ -39,8 +40,9 @@ fiddler/schema/model_deployment.py,sha256=CkYAzgJN4WQFNgElaZrhTvpLnRFMWnZ3F4hYkz
|
|
|
39
40
|
fiddler/schema/project.py,sha256=7kKUMIrGmLWjzuvKsIIIEZj9PIoJ8dXsAnyVOSL35pc,121
|
|
40
41
|
fiddler/schema/server_info.py,sha256=RKpFDUtpzicQ7I6FB9sMNaFEx-esdSjMiNrc0UIUKjg,388
|
|
41
42
|
fiddler/schema/user.py,sha256=8p4r2ZMqPEggggd10l62w_7A5KsRr6Se5QP-xfrEtks,109
|
|
43
|
+
fiddler/schema/webhook.py,sha256=0OknRLjXWocHNxMFWrxV2VvEfFzGn91vLw74FiGQSFU,218
|
|
42
44
|
fiddler/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
|
-
fiddler/schemas/custom_features.py,sha256=
|
|
45
|
+
fiddler/schemas/custom_features.py,sha256=p-kIEd6vx5LH4ZjjqPwAuS33N-4Yp9eu3EMNthKcB7g,3180
|
|
44
46
|
fiddler/schemas/model_schema.py,sha256=ByUVfnPzImNbl3GsiyqCpf8o0bDxOzwk-sIg0zeHpeQ,1124
|
|
45
47
|
fiddler/schemas/model_spec.py,sha256=RiZd-efabsJVfHCI5kXRs_6RrWj4xZB6vqajHSDqxgc,642
|
|
46
48
|
fiddler/schemas/model_task_params.py,sha256=jsBGgHStwBSkW9LZQtddCJJPMY_6EEWdS8lnVolJlbI,679
|
|
@@ -67,12 +69,12 @@ tests/fiddler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
67
69
|
tests/fiddler/base.py,sha256=QeDFGtcVP1LscfSvjJeEBPoQN1bp3s726EKaytY1Gis,1418
|
|
68
70
|
tests/fiddler/helper.py,sha256=lE0blhcQysB9BuVKScK-rpaxau04ddWD7NrjD5AZkb4,1785
|
|
69
71
|
tests/fiddler/test_add_model.py,sha256=rWbgfMZa3GExL9Y8ldVvw99BqGH-Zvrww09Z8JPhvTY,3787
|
|
70
|
-
tests/fiddler/test_alert.py,sha256=
|
|
72
|
+
tests/fiddler/test_alert.py,sha256=LEgk0iU7DC7QEXHcjpip6FzS-8w3uuCExkKlunJItSU,50919
|
|
71
73
|
tests/fiddler/test_baseline.py,sha256=JoAX3KkNYC52_uRVOHZzMNBy5dG57pXtwkT-WtmihHk,10277
|
|
72
74
|
tests/fiddler/test_check_version_decorator.py,sha256=MGcrIX7nscVpIYy1thuLrSvsXBC6KDXfdsHCEST5Xnw,906
|
|
73
75
|
tests/fiddler/test_custom_features.py,sha256=yjz-5Aq_Ey7FoDJiIzugLPcbh_5QdazE6trwpT38rEw,3028
|
|
74
76
|
tests/fiddler/test_dataset_api.py,sha256=donx8nw_BQ6s4-61Wq-Ab4CN5oG5UShnAwY9TX-vskk,6307
|
|
75
|
-
tests/fiddler/test_events_api.py,sha256=
|
|
77
|
+
tests/fiddler/test_events_api.py,sha256=YG0wWBBe0ygpqFvMs7PWAxu0DkYUH76EDY0u4iXCQY8,7748
|
|
76
78
|
tests/fiddler/test_exceptions.py,sha256=3dUe85gqf8MVxEM_9D1wrNRU8jvmu1-ja36jaDKgOQk,2801
|
|
77
79
|
tests/fiddler/test_explainability_apis.py,sha256=B73inIm2tNNRDnTLgiqU0o1Sy3N_QAuzlOms0QvA5oE,15111
|
|
78
80
|
tests/fiddler/test_file_upload.py,sha256=TBy0gLfu2sfbqcvx_rrgNF0I4keqkBTReG2EkVIei_Y,7611
|
|
@@ -84,12 +86,13 @@ tests/fiddler/test_project.py,sha256=sADiYMDeMnS7d90FHsHugUw3Tz1XASmlhjlSOOVSqnk
|
|
|
84
86
|
tests/fiddler/test_response_handler.py,sha256=YWhd8QIJrW3nUag-nZ4jutAs1B9eOqQekgUN1vtsbU0,2958
|
|
85
87
|
tests/fiddler/test_upload_dataset.py,sha256=fQ7fNtB4-kuY7g6XSNftklxgi_-uhkiuXb7CtxmWz9Q,13719
|
|
86
88
|
tests/fiddler/test_validations.py,sha256=1FUlfXzf1L-DrSth12u4MUBHGGgGAXDCn-x2TS79Y10,581
|
|
89
|
+
tests/fiddler/test_webhook.py,sha256=Js0zNdXYLOyh3k0Y1qRSS94ACITdiId4YXTQPTpeoc0,4395
|
|
87
90
|
tests/fiddler/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
88
91
|
tests/fiddler/utils/test_general_checks.py,sha256=pwXqEf1KdXnMugej8fpr8zfiRsN1-yDGwQWuVheIl3c,1473
|
|
89
92
|
tests/fiddler/utils/test_pandas.py,sha256=rsJg_a1Z60FbY1v5WNhhyqXJIjW-qVFZhNjfwptL7is,8084
|
|
90
93
|
tests/fiddler/utils/tests_utils.py,sha256=kPmMoOlaSIWw6Gk7QnAnn8QJvEyqtd2Rz32kS6RGEc8,1800
|
|
91
|
-
fiddler_client-2.0.0.
|
|
92
|
-
fiddler_client-2.0.0.
|
|
93
|
-
fiddler_client-2.0.0.
|
|
94
|
-
fiddler_client-2.0.0.
|
|
95
|
-
fiddler_client-2.0.0.
|
|
94
|
+
fiddler_client-2.0.0.dev10.dist-info/LICENSE.txt,sha256=w8-LUAb_VLBWSsCNmih0pAqLIicJfGu8OJXpDjkIg_o,559
|
|
95
|
+
fiddler_client-2.0.0.dev10.dist-info/METADATA,sha256=eSsma_0KiXXx4-YmihMJ4Jqwlpb2JtsaTv8BG7ejUFk,17922
|
|
96
|
+
fiddler_client-2.0.0.dev10.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
|
|
97
|
+
fiddler_client-2.0.0.dev10.dist-info/top_level.txt,sha256=GdJimrE4Q9O20-dA6QljDHm5KBrlz25oQTdbMHu41So,14
|
|
98
|
+
fiddler_client-2.0.0.dev10.dist-info/RECORD,,
|
tests/fiddler/test_alert.py
CHANGED
|
@@ -395,6 +395,185 @@ class TestAlert(BaseTestCase):
|
|
|
395
395
|
self.assertEqual(response_items[0]['alert_type'], 'drift')
|
|
396
396
|
self.assertEqual(response_items[0]['name'], 'alert_name_1')
|
|
397
397
|
|
|
398
|
+
def test_get_alert_rules_diff_feature_names_format(self):
|
|
399
|
+
response = {
|
|
400
|
+
'data': {
|
|
401
|
+
'page_size': 100,
|
|
402
|
+
'total': 84,
|
|
403
|
+
'item_count': 84,
|
|
404
|
+
'page_count': 1,
|
|
405
|
+
'page_index': 1,
|
|
406
|
+
'offset': 0,
|
|
407
|
+
'items': [
|
|
408
|
+
{
|
|
409
|
+
'critical_threshold': 0.5,
|
|
410
|
+
'organization_name': self._org,
|
|
411
|
+
'id': 1,
|
|
412
|
+
'sub_metric': 'jsd',
|
|
413
|
+
'metric': 'jsd',
|
|
414
|
+
'condition': 'greater',
|
|
415
|
+
'time_bucket': 3600000,
|
|
416
|
+
'compare_period': 86400000,
|
|
417
|
+
'details': 'New Alert',
|
|
418
|
+
'model_name': self._model_name,
|
|
419
|
+
'name': 'alert_name_1',
|
|
420
|
+
'feature_names': "['probability_churned']",
|
|
421
|
+
'baseline_name': 'baseline_name',
|
|
422
|
+
'created_by': 'admin@fiddler.ai',
|
|
423
|
+
'alert_type': 'drift',
|
|
424
|
+
'uuid': '932bb123-3fc6-4ee5-a48c-1f96bb29f7ee',
|
|
425
|
+
'is_active': True,
|
|
426
|
+
'created_at': '2022-09-08T18:47:26.889000+00:00',
|
|
427
|
+
'project_name': self._project_name,
|
|
428
|
+
'warning_threshold': 0.2,
|
|
429
|
+
'compare_to': 'time_period',
|
|
430
|
+
'alert_log_time': '2022-09-08T18:47:28.034165+00:00',
|
|
431
|
+
'priority': 'LOW',
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
'critical_threshold': 0.7,
|
|
435
|
+
'organization_name': self._org,
|
|
436
|
+
'id': 2,
|
|
437
|
+
'sub_metric': 'jsd',
|
|
438
|
+
'metric': 'jsd',
|
|
439
|
+
'condition': 'lesser',
|
|
440
|
+
'time_bucket': 3600000,
|
|
441
|
+
'compare_period': 86400000,
|
|
442
|
+
'details': 'New Alert',
|
|
443
|
+
'model_name': self._model_name,
|
|
444
|
+
'name': 'alert_name_2',
|
|
445
|
+
'feature_names': ['probability_churned'],
|
|
446
|
+
'created_by': 'admin@fiddler.ai',
|
|
447
|
+
'alert_type': 'drift',
|
|
448
|
+
'uuid': 'e7c7f891-53ba-4246-b691-53b6efda332b',
|
|
449
|
+
'is_active': True,
|
|
450
|
+
'created_at': '2022-09-08T18:47:27.923000+00:00',
|
|
451
|
+
'project_name': self._project_name,
|
|
452
|
+
'warning_threshold': 0.1,
|
|
453
|
+
'compare_to': 'time_period',
|
|
454
|
+
'alert_log_time': '2022-09-08T18:47:29.087871+00:00',
|
|
455
|
+
'priority': 'LOW',
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
'critical_threshold': 0.8,
|
|
459
|
+
'organization_name': self._org,
|
|
460
|
+
'id': 3,
|
|
461
|
+
'sub_metric': 'jsd',
|
|
462
|
+
'metric': 'jsd',
|
|
463
|
+
'condition': 'greater',
|
|
464
|
+
'time_bucket': 3600000,
|
|
465
|
+
'model_name': self._model_name,
|
|
466
|
+
'name': 'alert_name_3',
|
|
467
|
+
'feature_name': 'probability_churned',
|
|
468
|
+
'created_by': 'admin@fiddler.ai',
|
|
469
|
+
'alert_type': 'drift',
|
|
470
|
+
'uuid': '320060fa-abeb-4cae-9718-17d6f917b414',
|
|
471
|
+
'is_active': True,
|
|
472
|
+
'created_at': '2022-09-08T18:47:28.975000+00:00',
|
|
473
|
+
'project_name': self._project_name,
|
|
474
|
+
'warning_threshold': 0.4,
|
|
475
|
+
'compare_to': 'raw_value',
|
|
476
|
+
'alert_log_time': '2022-09-08T18:47:30.100808+00:00',
|
|
477
|
+
'priority': 'LOW',
|
|
478
|
+
},
|
|
479
|
+
],
|
|
480
|
+
},
|
|
481
|
+
'api_version': '2.0',
|
|
482
|
+
'kind': 'PAGINATED',
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
url = self._get_url()
|
|
486
|
+
query_params = {
|
|
487
|
+
'organization_name': self._org,
|
|
488
|
+
'filter': json.dumps(
|
|
489
|
+
{
|
|
490
|
+
'condition': 'AND',
|
|
491
|
+
'rules': [
|
|
492
|
+
{
|
|
493
|
+
'field': 'project_name',
|
|
494
|
+
'operator': 'equal',
|
|
495
|
+
'value': 'test_project',
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
'field': 'model_name',
|
|
499
|
+
'operator': 'equal',
|
|
500
|
+
'value': 'test_model',
|
|
501
|
+
},
|
|
502
|
+
{'field': 'alert_type', 'operator': 'equal', 'value': 'drift'},
|
|
503
|
+
{'field': 'metric', 'operator': 'equal', 'value': 'jsd'},
|
|
504
|
+
{
|
|
505
|
+
'field': 'feature_names',
|
|
506
|
+
'operator': 'contains',
|
|
507
|
+
'value': ['test_column'],
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
'field': 'baseline_name',
|
|
511
|
+
'operator': 'equal',
|
|
512
|
+
'value': 'test_baseline',
|
|
513
|
+
},
|
|
514
|
+
],
|
|
515
|
+
}
|
|
516
|
+
),
|
|
517
|
+
'ordering': 'alert_type,metric',
|
|
518
|
+
'limit': 20,
|
|
519
|
+
'offset': 0,
|
|
520
|
+
}
|
|
521
|
+
self.requests_mock.get(
|
|
522
|
+
url, json=response, match=[matchers.query_param_matcher(query_params)]
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
server_info = ServerInfo(**{'feature_flags': {}, 'server_version': '23.3.0'})
|
|
526
|
+
with mock.patch.object(
|
|
527
|
+
self.client, 'server_info', server_info
|
|
528
|
+
) as mock_server_info:
|
|
529
|
+
alert_rules = self.client.get_alert_rules(
|
|
530
|
+
project_name='test_project',
|
|
531
|
+
model_name='test_model',
|
|
532
|
+
alert_type='drift',
|
|
533
|
+
metrics=['jsd'],
|
|
534
|
+
columns=['test_column'],
|
|
535
|
+
baseline_name='test_baseline',
|
|
536
|
+
ordering=['alert_type', 'metric'],
|
|
537
|
+
)
|
|
538
|
+
for alert_rule in alert_rules:
|
|
539
|
+
self.assertTrue(alert_rule.columns or alert_rule.column)
|
|
540
|
+
if alert_rule.columns:
|
|
541
|
+
self.assertEqual(alert_rule.columns, ['probability_churned'])
|
|
542
|
+
else:
|
|
543
|
+
self.assertEqual(alert_rule.column, 'probability_churned')
|
|
544
|
+
|
|
545
|
+
query_params = {
|
|
546
|
+
'filter': json.dumps(
|
|
547
|
+
{"condition": "AND", "rules": [{"field": "project_name", "operator": "equal", "value": "test_project"}, {"field": "model_name", "operator": "equal", "value": "test_model"}, {"field": "metric_id", "operator": "contains", "value": ["jsd"]}, {"field": "feature_names", "operator": "contains", "value": ["test_column"]}, {"field": "baseline_name", "operator": "equal", "value": "test_baseline"}]}
|
|
548
|
+
),
|
|
549
|
+
'organization_name': 'test_organization',
|
|
550
|
+
'ordering': 'alert_type,metric',
|
|
551
|
+
'limit': 20,
|
|
552
|
+
'offset': 0,
|
|
553
|
+
}
|
|
554
|
+
self.requests_mock.get(
|
|
555
|
+
url, json=response, match=[matchers.query_param_matcher(query_params)]
|
|
556
|
+
)
|
|
557
|
+
server_info = ServerInfo(**{'feature_flags': {}, 'server_version': '23.4.0'})
|
|
558
|
+
with mock.patch.object(
|
|
559
|
+
self.client, 'server_info', server_info
|
|
560
|
+
) as mock_server_info:
|
|
561
|
+
alert_rules = self.client.get_alert_rules(
|
|
562
|
+
project_name='test_project',
|
|
563
|
+
model_name='test_model',
|
|
564
|
+
alert_type='drift',
|
|
565
|
+
metrics=['jsd'],
|
|
566
|
+
columns=['test_column'],
|
|
567
|
+
baseline_name='test_baseline',
|
|
568
|
+
ordering=['alert_type', 'metric'],
|
|
569
|
+
)
|
|
570
|
+
for alert_rule in alert_rules:
|
|
571
|
+
self.assertTrue(alert_rule.columns or alert_rule.column)
|
|
572
|
+
if alert_rule.columns:
|
|
573
|
+
self.assertEqual(alert_rule.columns, ['probability_churned'])
|
|
574
|
+
else:
|
|
575
|
+
self.assertEqual(alert_rule.column, 'probability_churned')
|
|
576
|
+
|
|
398
577
|
def test_delete_alert_rule(self):
|
|
399
578
|
url = self._get_url(alert_rule_uuid=self._alert_rule_uuid)
|
|
400
579
|
self.requests_mock.delete(url)
|
|
@@ -769,8 +948,8 @@ class TestAlert(BaseTestCase):
|
|
|
769
948
|
'offset': 0,
|
|
770
949
|
'items': [
|
|
771
950
|
{
|
|
772
|
-
'id':
|
|
773
|
-
'uuid': '
|
|
951
|
+
'id': 1,
|
|
952
|
+
'uuid': 'uuid_1',
|
|
774
953
|
'alert_config_uuid': self._alert_rule_uuid,
|
|
775
954
|
'alert_run_start_time': 1658481873833,
|
|
776
955
|
'alert_time_bucket': 1657710000000,
|
|
@@ -783,12 +962,12 @@ class TestAlert(BaseTestCase):
|
|
|
783
962
|
'message': 'In project bank_churn and model bank_churn, during the time period of one hour starting |2022-07-13 11:00:00 (UTC time)|, the prediction drift score was 0.789, greater than 0.1.',
|
|
784
963
|
},
|
|
785
964
|
{
|
|
786
|
-
'id':
|
|
787
|
-
'uuid': '
|
|
965
|
+
'id': 2,
|
|
966
|
+
'uuid': 'uuid_2',
|
|
788
967
|
'alert_config_uuid': self._alert_rule_uuid,
|
|
789
968
|
'alert_run_start_time': 1658481873991,
|
|
790
969
|
'alert_time_bucket': 1657720800000,
|
|
791
|
-
'alert_value':
|
|
970
|
+
'alert_value': 0.789176795308359,
|
|
792
971
|
'baseline_time_bucket': None,
|
|
793
972
|
'baseline_value': None,
|
|
794
973
|
'is_alert': True,
|
|
@@ -797,18 +976,19 @@ class TestAlert(BaseTestCase):
|
|
|
797
976
|
'message': 'In project bank_churn and model bank_churn, during the time period of one hour starting |2022-07-13 14:00:00 (UTC time)|, the prediction drift score was 0.789, greater than 0.1.',
|
|
798
977
|
},
|
|
799
978
|
{
|
|
800
|
-
'id':
|
|
801
|
-
'uuid': '
|
|
979
|
+
'id': 3,
|
|
980
|
+
'uuid': 'uuid_3',
|
|
802
981
|
'alert_config_uuid': self._alert_rule_uuid,
|
|
803
982
|
'alert_run_start_time': 1658481874167,
|
|
804
983
|
'alert_time_bucket': 1657724400000,
|
|
805
|
-
'alert_value':
|
|
984
|
+
'alert_value': 0.789176795308359,
|
|
806
985
|
'baseline_time_bucket': None,
|
|
807
986
|
'baseline_value': None,
|
|
808
987
|
'is_alert': True,
|
|
809
988
|
'severity': None,
|
|
810
989
|
'failure_reason': 'NA',
|
|
811
990
|
'message': 'In project bank_churn and model bank_churn, during the time period of one hour starting |2022-07-13 15:00:00 (UTC time)|, the prediction drift score was 0.789, greater than 0.1.',
|
|
991
|
+
'feature_name':'feature_name'
|
|
812
992
|
},
|
|
813
993
|
],
|
|
814
994
|
}
|
|
@@ -830,14 +1010,160 @@ class TestAlert(BaseTestCase):
|
|
|
830
1010
|
)
|
|
831
1011
|
self.assertEqual(triggered_alerts[0].failure_reason, 'NA')
|
|
832
1012
|
self.assertEqual(triggered_alerts[0].is_alert, True)
|
|
1013
|
+
self.assertEqual(triggered_alerts[1].alert_value, 0.789176795308359)
|
|
1014
|
+
self.assertTrue('feature_names' not in triggered_alerts[2].dict())
|
|
1015
|
+
self.assertEqual(triggered_alerts[2].alert_value, 0.789176795308359)
|
|
1016
|
+
self.assertEqual(triggered_alerts[2].feature_name, 'feature_name')
|
|
833
1017
|
|
|
834
1018
|
response_items = self.requests_mock.calls[0].response.json()['data']['items']
|
|
835
1019
|
|
|
836
1020
|
self.assertEqual(response_items[0]['is_alert'], True)
|
|
837
|
-
self.assertEqual(response_items[0]['id'],
|
|
1021
|
+
self.assertEqual(response_items[0]['id'], 1)
|
|
838
1022
|
self.assertEqual(response_items[0]['alert_time_bucket'], 1657710000000)
|
|
839
1023
|
self.assertEqual(response_items[0]['alert_config_uuid'], self._alert_rule_uuid)
|
|
840
1024
|
|
|
1025
|
+
def test_alert_rule_serializer(self):
|
|
1026
|
+
alert_rule_231 = {
|
|
1027
|
+
'critical_threshold': 0.8,
|
|
1028
|
+
'organization_name': self._org,
|
|
1029
|
+
'id': 3,
|
|
1030
|
+
'sub_metric': 'jsd',
|
|
1031
|
+
'metric': 'jsd',
|
|
1032
|
+
'condition': 'greater',
|
|
1033
|
+
'time_bucket': 3600000,
|
|
1034
|
+
'model_name': self._model_name,
|
|
1035
|
+
'name': 'alert_name_3',
|
|
1036
|
+
'feature_name': 'probability_churned',
|
|
1037
|
+
'created_by': 'admin@fiddler.ai',
|
|
1038
|
+
'alert_type': 'drift',
|
|
1039
|
+
'uuid': '320060fa-abeb-4cae-9718-17d6f917b414',
|
|
1040
|
+
'is_active': True,
|
|
1041
|
+
'created_at': '2022-09-08T18:47:28.975000+00:00',
|
|
1042
|
+
'project_name': self._project_name,
|
|
1043
|
+
'warning_threshold': 0.4,
|
|
1044
|
+
'compare_to': 'raw_value',
|
|
1045
|
+
'alert_log_time': '2022-09-08T18:47:30.100808+00:00',
|
|
1046
|
+
'priority': 'LOW',
|
|
1047
|
+
}
|
|
1048
|
+
alert_rule_parsed_231:AlertRule = AlertRule.parse_obj(alert_rule_231)
|
|
1049
|
+
self.assertEqual(alert_rule_parsed_231.column, 'probability_churned')
|
|
1050
|
+
self.assertEqual(alert_rule_parsed_231.columns, None)
|
|
1051
|
+
|
|
1052
|
+
alert_rule_232 = {
|
|
1053
|
+
'critical_threshold': 0.8,
|
|
1054
|
+
'organization_name': self._org,
|
|
1055
|
+
'id': 3,
|
|
1056
|
+
'sub_metric': 'jsd',
|
|
1057
|
+
'metric': 'jsd',
|
|
1058
|
+
'condition': 'greater',
|
|
1059
|
+
'time_bucket': 3600000,
|
|
1060
|
+
'model_name': self._model_name,
|
|
1061
|
+
'name': 'alert_name_3',
|
|
1062
|
+
'feature_names': "['probability_churned']",
|
|
1063
|
+
'created_by': 'admin@fiddler.ai',
|
|
1064
|
+
'alert_type': 'drift',
|
|
1065
|
+
'uuid': '320060fa-abeb-4cae-9718-17d6f917b414',
|
|
1066
|
+
'is_active': True,
|
|
1067
|
+
'created_at': '2022-09-08T18:47:28.975000+00:00',
|
|
1068
|
+
'project_name': self._project_name,
|
|
1069
|
+
'warning_threshold': 0.4,
|
|
1070
|
+
'compare_to': 'raw_value',
|
|
1071
|
+
'alert_log_time': '2022-09-08T18:47:30.100808+00:00',
|
|
1072
|
+
'priority': 'LOW',
|
|
1073
|
+
}
|
|
1074
|
+
alert_rule_parsed_232:AlertRule = AlertRule.parse_obj(alert_rule_232)
|
|
1075
|
+
self.assertEqual(alert_rule_parsed_232.column, None)
|
|
1076
|
+
self.assertEqual(alert_rule_parsed_232.columns, ['probability_churned'])
|
|
1077
|
+
|
|
1078
|
+
alert_rule_233 = {
|
|
1079
|
+
'critical_threshold': 0.8,
|
|
1080
|
+
'organization_name': self._org,
|
|
1081
|
+
'id': 3,
|
|
1082
|
+
'sub_metric': 'jsd',
|
|
1083
|
+
'metric': 'jsd',
|
|
1084
|
+
'condition': 'greater',
|
|
1085
|
+
'time_bucket': 3600000,
|
|
1086
|
+
'model_name': self._model_name,
|
|
1087
|
+
'name': 'alert_name_3',
|
|
1088
|
+
'feature_names': ['probability_churned'],
|
|
1089
|
+
'created_by': 'admin@fiddler.ai',
|
|
1090
|
+
'alert_type': 'drift',
|
|
1091
|
+
'uuid': '320060fa-abeb-4cae-9718-17d6f917b414',
|
|
1092
|
+
'is_active': True,
|
|
1093
|
+
'created_at': '2022-09-08T18:47:28.975000+00:00',
|
|
1094
|
+
'project_name': self._project_name,
|
|
1095
|
+
'warning_threshold': 0.4,
|
|
1096
|
+
'compare_to': 'raw_value',
|
|
1097
|
+
'alert_log_time': '2022-09-08T18:47:30.100808+00:00',
|
|
1098
|
+
'priority': 'LOW',
|
|
1099
|
+
}
|
|
1100
|
+
alert_rule_parsed_233:AlertRule = AlertRule.parse_obj(alert_rule_233)
|
|
1101
|
+
self.assertEqual(alert_rule_parsed_233.column, None)
|
|
1102
|
+
self.assertEqual(alert_rule_parsed_233.columns, ['probability_churned'])
|
|
1103
|
+
|
|
1104
|
+
alert_rule_empty_features = {
|
|
1105
|
+
'critical_threshold': 0.8,
|
|
1106
|
+
'organization_name': self._org,
|
|
1107
|
+
'id': 3,
|
|
1108
|
+
'sub_metric': 'jsd',
|
|
1109
|
+
'metric': 'jsd',
|
|
1110
|
+
'condition': 'greater',
|
|
1111
|
+
'time_bucket': 3600000,
|
|
1112
|
+
'model_name': self._model_name,
|
|
1113
|
+
'name': 'alert_name_3',
|
|
1114
|
+
'created_by': 'admin@fiddler.ai',
|
|
1115
|
+
'alert_type': 'drift',
|
|
1116
|
+
'uuid': '320060fa-abeb-4cae-9718-17d6f917b414',
|
|
1117
|
+
'is_active': True,
|
|
1118
|
+
'created_at': '2022-09-08T18:47:28.975000+00:00',
|
|
1119
|
+
'project_name': self._project_name,
|
|
1120
|
+
'warning_threshold': 0.4,
|
|
1121
|
+
'compare_to': 'raw_value',
|
|
1122
|
+
'alert_log_time': '2022-09-08T18:47:30.100808+00:00',
|
|
1123
|
+
'priority': 'LOW',
|
|
1124
|
+
}
|
|
1125
|
+
alert_rule_parsed_empty_features:AlertRule = AlertRule.parse_obj(alert_rule_empty_features)
|
|
1126
|
+
self.assertEqual(alert_rule_parsed_empty_features.column, None)
|
|
1127
|
+
self.assertEqual(alert_rule_parsed_empty_features.columns, None)
|
|
1128
|
+
|
|
1129
|
+
def test_triggered_alert_record_serializer(self):
|
|
1130
|
+
tar_with_value_as_float = {
|
|
1131
|
+
'id': 1,
|
|
1132
|
+
'uuid': 'uuid',
|
|
1133
|
+
'alert_config_uuid': 'alert_config_uuid',
|
|
1134
|
+
'alert_run_start_time': 1,
|
|
1135
|
+
'alert_time_bucket': 1,
|
|
1136
|
+
'alert_value': 1.0,
|
|
1137
|
+
'baseline_time_bucket': 1,
|
|
1138
|
+
'baseline_value': 0.0,
|
|
1139
|
+
'is_alert': True,
|
|
1140
|
+
'severity': 'HIGH',
|
|
1141
|
+
'failure_reason': 'NA',
|
|
1142
|
+
'message': 'some message',
|
|
1143
|
+
'multi_col_values': {'a' : 1.0, 'b': 2.0},
|
|
1144
|
+
'feature_name': 'age'
|
|
1145
|
+
}
|
|
1146
|
+
tar:TriggeredAlerts = TriggeredAlerts.parse_obj(tar_with_value_as_float)
|
|
1147
|
+
self.assertEqual(tar.alert_value, 1.0)
|
|
1148
|
+
self.assertEqual(tar.feature_name, 'age')
|
|
1149
|
+
|
|
1150
|
+
tar_with_value_as_dict = {
|
|
1151
|
+
'id': 1,
|
|
1152
|
+
'uuid': 'uuid',
|
|
1153
|
+
'alert_config_uuid': 'alert_config_uuid',
|
|
1154
|
+
'alert_run_start_time': 1,
|
|
1155
|
+
'alert_time_bucket': 1,
|
|
1156
|
+
'alert_value': {'a' : 1.0, 'b': 2.0},
|
|
1157
|
+
'baseline_time_bucket': 1,
|
|
1158
|
+
'baseline_value': 0.0,
|
|
1159
|
+
'is_alert': True,
|
|
1160
|
+
'severity': 'HIGH',
|
|
1161
|
+
'failure_reason': 'NA',
|
|
1162
|
+
'message': 'some message',
|
|
1163
|
+
}
|
|
1164
|
+
tar:TriggeredAlerts = TriggeredAlerts.parse_obj(tar_with_value_as_dict)
|
|
1165
|
+
self.assertEqual(tar.alert_value, {'a' : 1.0, 'b': 2.0})
|
|
841
1166
|
|
|
842
1167
|
if __name__ == '__main__':
|
|
843
1168
|
unittest.main()
|
|
1169
|
+
|
tests/fiddler/test_events_api.py
CHANGED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import unittest
|
|
3
|
+
from http import HTTPStatus
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
|
|
6
|
+
from responses import matchers
|
|
7
|
+
|
|
8
|
+
from fiddler.exceptions import (
|
|
9
|
+
BadRequest,
|
|
10
|
+
Conflict,
|
|
11
|
+
NotFound,
|
|
12
|
+
)
|
|
13
|
+
from fiddler.schema.webhook import Webhook
|
|
14
|
+
from tests.fiddler.base import BaseTestCase
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestWebhook(BaseTestCase):
|
|
18
|
+
def setUp(self):
|
|
19
|
+
super(TestWebhook, self).setUp()
|
|
20
|
+
|
|
21
|
+
def _get_url(self, uuid: str = None) -> str:
|
|
22
|
+
base_url = f'{self._url}/webhooks'
|
|
23
|
+
if uuid:
|
|
24
|
+
return f'{base_url}/{uuid}',
|
|
25
|
+
return base_url
|
|
26
|
+
|
|
27
|
+
def test_get_webhooks(self):
|
|
28
|
+
response = {
|
|
29
|
+
'data': {
|
|
30
|
+
'page_size': 100,
|
|
31
|
+
'total': 3,
|
|
32
|
+
'item_count': 3,
|
|
33
|
+
'page_count': 1,
|
|
34
|
+
'page_index': 1,
|
|
35
|
+
'offset': 0,
|
|
36
|
+
'items': [
|
|
37
|
+
{
|
|
38
|
+
'id': 0,
|
|
39
|
+
'uuid': 'uuid_0',
|
|
40
|
+
'name': 'webhook_0',
|
|
41
|
+
'url': 'url_0',
|
|
42
|
+
'provider': 'SLACK',
|
|
43
|
+
'organization_name': 'test_organization',
|
|
44
|
+
'created_at': '2022-05-19T18:20:25.991688+00:00',
|
|
45
|
+
'updated_at': '2022-05-19T18:20:25.991688+00:00',
|
|
46
|
+
'created_by': 'test_user',
|
|
47
|
+
'updated_by': 'test_user',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
'id': 1,
|
|
51
|
+
'uuid': 'uuid_1',
|
|
52
|
+
'name': 'webhook_1',
|
|
53
|
+
'url': 'url_1',
|
|
54
|
+
'provider': 'SLACK',
|
|
55
|
+
'organization_name': 'test_organization',
|
|
56
|
+
'created_at': '2022-05-19T18:20:25.991688+00:00',
|
|
57
|
+
'updated_at': '2022-05-19T18:20:25.991688+00:00',
|
|
58
|
+
'created_by': 'test_user',
|
|
59
|
+
'updated_by': 'test_user',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
'id': 2,
|
|
63
|
+
'uuid': 'uuid_2',
|
|
64
|
+
'name': 'webhook_2',
|
|
65
|
+
'url': 'url_2',
|
|
66
|
+
'provider': 'SLACK',
|
|
67
|
+
'organization_name': 'test_organization',
|
|
68
|
+
'created_at': '2022-05-19T18:20:25.991688+00:00',
|
|
69
|
+
'updated_at': '2022-05-19T18:20:25.991688+00:00',
|
|
70
|
+
'created_by': 'test_user',
|
|
71
|
+
'updated_by': 'test_user',
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
'api_version': '2.0',
|
|
76
|
+
'kind': 'PAGINATED',
|
|
77
|
+
}
|
|
78
|
+
url = self._get_url()
|
|
79
|
+
query_params = {'limit': 300, 'offset': 0, 'organization_name': 'test_organization'}
|
|
80
|
+
|
|
81
|
+
self.requests_mock.get(
|
|
82
|
+
url, json=response, match=[matchers.query_param_matcher(query_params)]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
webhooks = self.client.get_webhooks()
|
|
86
|
+
self.assertIsInstance(webhooks[0], Webhook)
|
|
87
|
+
self.assertEqual(webhooks[0].name, 'webhook_0')
|
|
88
|
+
self.assertEqual(webhooks[0].organization_name, self._org)
|
|
89
|
+
self.assertEqual(len(webhooks), 3)
|
|
90
|
+
|
|
91
|
+
def test_add_webhook(self):
|
|
92
|
+
response_body = {
|
|
93
|
+
'data': {
|
|
94
|
+
'id': 0,
|
|
95
|
+
'uuid': 'uuid_0',
|
|
96
|
+
'name': 'webhook_0',
|
|
97
|
+
'url': 'url_0',
|
|
98
|
+
'provider': 'SLACK',
|
|
99
|
+
'organization_name': 'test_organization',
|
|
100
|
+
'created_at': '2022-05-19T18:20:25.991688+00:00',
|
|
101
|
+
'updated_at': '2022-05-19T18:20:25.991688+00:00',
|
|
102
|
+
'created_by': 'test_user',
|
|
103
|
+
'updated_by': 'test_user',
|
|
104
|
+
},
|
|
105
|
+
'api_version': '2.0',
|
|
106
|
+
'kind': 'NORMAL',
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
url = self._get_url()
|
|
110
|
+
query_params = {'organization_name':'test_organization'}
|
|
111
|
+
|
|
112
|
+
self.requests_mock.post(
|
|
113
|
+
url, json=response_body, match=[matchers.query_param_matcher(query_params)]
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
webhook = self.client.add_webhook(
|
|
117
|
+
name='webhook_0', url='url_i', provider='SLACK'
|
|
118
|
+
)
|
|
119
|
+
request_body = json.loads(self.requests_mock.calls[0].request.body)
|
|
120
|
+
self.assertEqual(
|
|
121
|
+
webhook.uuid, 'uuid_0'
|
|
122
|
+
)
|
|
123
|
+
self.assertEqual(len(self.requests_mock.calls), 1)
|
|
124
|
+
self.assertEqual(request_body['name'], 'webhook_0')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|