fiddler-client 3.1.0.dev2__py3-none-any.whl → 3.1.0.dev4__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 +1 -1
- fiddler/__init__.py +2 -0
- fiddler/entities/alert_rule.py +24 -18
- fiddler/entities/events.py +51 -16
- fiddler/entities/model.py +23 -10
- fiddler/entities/model_deployment.py +5 -1
- fiddler/entities/webhook.py +18 -0
- fiddler/packtools/template_model.py +3 -2
- fiddler/schemas/alert_rule.py +19 -10
- fiddler/schemas/custom_expression.py +7 -0
- fiddler/tests/apis/test_alert_rule.py +141 -90
- fiddler/tests/apis/test_model.py +13 -0
- fiddler/tests/apis/test_model_deployment.py +35 -1
- fiddler/tests/apis/test_webhook.py +57 -0
- {fiddler_client-3.1.0.dev2.dist-info → fiddler_client-3.1.0.dev4.dist-info}/METADATA +2 -1
- {fiddler_client-3.1.0.dev2.dist-info → fiddler_client-3.1.0.dev4.dist-info}/RECORD +19 -19
- {fiddler_client-3.1.0.dev2.dist-info → fiddler_client-3.1.0.dev4.dist-info}/LICENSE.txt +0 -0
- {fiddler_client-3.1.0.dev2.dist-info → fiddler_client-3.1.0.dev4.dist-info}/WHEEL +0 -0
- {fiddler_client-3.1.0.dev2.dist-info → fiddler_client-3.1.0.dev4.dist-info}/top_level.txt +0 -0
fiddler/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.1.0.
|
|
1
|
+
3.1.0.dev4
|
fiddler/__init__.py
CHANGED
|
@@ -61,6 +61,7 @@ from fiddler.schemas.xai import ( # noqa
|
|
|
61
61
|
SqlSliceQueryDataSource,
|
|
62
62
|
)
|
|
63
63
|
from fiddler.schemas.xai_params import XaiParams # noqa
|
|
64
|
+
from fiddler.utils.helpers import group_by # noqa
|
|
64
65
|
from fiddler.utils.logger import set_logging # noqa
|
|
65
66
|
from fiddler.version import __version__ # noqa
|
|
66
67
|
|
|
@@ -141,4 +142,5 @@ __all__ = [
|
|
|
141
142
|
'ApiError',
|
|
142
143
|
# Utilities
|
|
143
144
|
'set_logging',
|
|
145
|
+
'group_by',
|
|
144
146
|
]
|
fiddler/entities/alert_rule.py
CHANGED
|
@@ -8,8 +8,8 @@ from uuid import UUID
|
|
|
8
8
|
from fiddler.constants.alert_rule import AlertCondition, BinSize, CompareTo, Priority
|
|
9
9
|
from fiddler.decorators import handle_api_error
|
|
10
10
|
from fiddler.entities.base import BaseEntity
|
|
11
|
-
from fiddler.entities.baseline import
|
|
12
|
-
from fiddler.entities.model import
|
|
11
|
+
from fiddler.entities.baseline import BaselineCompactMixin
|
|
12
|
+
from fiddler.entities.model import ModelCompactMixin
|
|
13
13
|
from fiddler.entities.project import ProjectCompactMixin
|
|
14
14
|
from fiddler.schemas.alert_rule import AlertRuleResp, NotificationConfig
|
|
15
15
|
from fiddler.schemas.filter_query import OperatorType, QueryCondition, QueryRule
|
|
@@ -34,6 +34,7 @@ class AlertRule(
|
|
|
34
34
|
warning_threshold: float | None = None,
|
|
35
35
|
columns: list[str] | None = None,
|
|
36
36
|
baseline_id: UUID | str | None = None,
|
|
37
|
+
segment_id: UUID | str | None = None,
|
|
37
38
|
compare_bin_delta: int | None = None,
|
|
38
39
|
) -> None:
|
|
39
40
|
"""Construct a alert rule instance."""
|
|
@@ -49,6 +50,7 @@ class AlertRule(
|
|
|
49
50
|
self.critical_threshold = critical_threshold
|
|
50
51
|
self.condition = condition
|
|
51
52
|
self.bin_size = bin_size
|
|
53
|
+
self.segment_id = segment_id
|
|
52
54
|
|
|
53
55
|
self.id: UUID | None = None
|
|
54
56
|
self.project_id: UUID | None = None
|
|
@@ -61,6 +63,12 @@ class AlertRule(
|
|
|
61
63
|
|
|
62
64
|
@staticmethod
|
|
63
65
|
def _get_url(id_: UUID | str | None = None) -> str:
|
|
66
|
+
"""Get model resource/item url."""
|
|
67
|
+
url = '/v3/alert-rules'
|
|
68
|
+
return url if not id_ else f'{url}/{id_}'
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def _get_url_v2(id_: UUID | str | None = None) -> str:
|
|
64
72
|
"""Get model resource/item url."""
|
|
65
73
|
url = '/v2/alert-configs'
|
|
66
74
|
return url if not id_ else f'{url}/{id_}'
|
|
@@ -76,7 +84,7 @@ class AlertRule(
|
|
|
76
84
|
instance = cls(
|
|
77
85
|
name=resp_obj.name,
|
|
78
86
|
model_id=resp_obj.model.id,
|
|
79
|
-
metric_id=resp_obj.
|
|
87
|
+
metric_id=resp_obj.metric.id,
|
|
80
88
|
priority=resp_obj.priority,
|
|
81
89
|
compare_to=resp_obj.compare_to,
|
|
82
90
|
condition=resp_obj.condition,
|
|
@@ -84,7 +92,8 @@ class AlertRule(
|
|
|
84
92
|
critical_threshold=resp_obj.critical_threshold,
|
|
85
93
|
warning_threshold=resp_obj.warning_threshold,
|
|
86
94
|
columns=resp_obj.columns,
|
|
87
|
-
baseline_id=resp_obj.
|
|
95
|
+
baseline_id=resp_obj.baseline.id if resp_obj.baseline else None,
|
|
96
|
+
segment_id=resp_obj.segment.id if resp_obj.segment else None,
|
|
88
97
|
compare_bin_delta=resp_obj.compare_bin_delta,
|
|
89
98
|
)
|
|
90
99
|
|
|
@@ -110,11 +119,11 @@ class AlertRule(
|
|
|
110
119
|
# Reset properties
|
|
111
120
|
self.model_id = resp_obj.model.id
|
|
112
121
|
self.project_id = resp_obj.project.id
|
|
122
|
+
self.metric_id = resp_obj.metric.id
|
|
113
123
|
|
|
114
124
|
# Add remaining fields
|
|
115
125
|
fields = [
|
|
116
126
|
'id',
|
|
117
|
-
'metric_id',
|
|
118
127
|
'created_at',
|
|
119
128
|
'updated_at',
|
|
120
129
|
]
|
|
@@ -176,7 +185,9 @@ class AlertRule(
|
|
|
176
185
|
if columns:
|
|
177
186
|
for column in columns:
|
|
178
187
|
rules.append(
|
|
179
|
-
QueryRule(
|
|
188
|
+
QueryRule(
|
|
189
|
+
field='feature_names', operator=OperatorType.ANY, value=column
|
|
190
|
+
)
|
|
180
191
|
)
|
|
181
192
|
|
|
182
193
|
_filter = QueryCondition(rules=rules)
|
|
@@ -185,7 +196,6 @@ class AlertRule(
|
|
|
185
196
|
if ordering:
|
|
186
197
|
params['ordering'] = ','.join(ordering)
|
|
187
198
|
|
|
188
|
-
params['organization_name'] = cls.get_organization_name()
|
|
189
199
|
for rule in cls._paginate(url=cls._get_url(), params=params):
|
|
190
200
|
yield cls._from_dict(data=rule)
|
|
191
201
|
|
|
@@ -199,26 +209,22 @@ class AlertRule(
|
|
|
199
209
|
@handle_api_error
|
|
200
210
|
def create(self) -> AlertRule:
|
|
201
211
|
"""Create a new alert rule."""
|
|
202
|
-
model = Model.get(self.model_id)
|
|
203
212
|
payload: dict[str, Any] = {
|
|
204
213
|
'name': self.name,
|
|
205
|
-
'
|
|
206
|
-
'model_name': model.name,
|
|
207
|
-
'project_name': model.project.name,
|
|
214
|
+
'model_id': self.model_id,
|
|
208
215
|
'metric_id': self.metric_id,
|
|
209
216
|
'priority': self.priority,
|
|
210
217
|
'compare_to': self.compare_to,
|
|
211
218
|
'condition': self.condition,
|
|
212
219
|
'bin_size': self.bin_size,
|
|
220
|
+
'segment_id': self.segment_id,
|
|
213
221
|
'critical_threshold': self.critical_threshold,
|
|
214
222
|
'warning_threshold': self.warning_threshold,
|
|
215
223
|
'feature_names': self.columns,
|
|
216
224
|
'compare_bin_delta': self.compare_bin_delta,
|
|
217
|
-
'notifications': self._get_notifications_dict(),
|
|
218
225
|
}
|
|
219
226
|
if self.baseline_id:
|
|
220
|
-
|
|
221
|
-
payload['baseline_name'] = baseline.name
|
|
227
|
+
payload['baseline_id'] = self.baseline_id
|
|
222
228
|
|
|
223
229
|
response = self._client().post(
|
|
224
230
|
url=self._get_url(),
|
|
@@ -232,7 +238,7 @@ class AlertRule(
|
|
|
232
238
|
def enable_notifications(self) -> None:
|
|
233
239
|
"""Enable notifications for an alert rule"""
|
|
234
240
|
self._client().patch(
|
|
235
|
-
url=self.
|
|
241
|
+
url=self._get_url_v2(id_=self.id), data={'enable_notification': True}
|
|
236
242
|
)
|
|
237
243
|
logger.info(
|
|
238
244
|
'Notifications have been enabled for alert rule with id: %s', self.id
|
|
@@ -242,7 +248,7 @@ class AlertRule(
|
|
|
242
248
|
def disable_notifications(self) -> None:
|
|
243
249
|
"""Disable notifications for an alert rule"""
|
|
244
250
|
self._client().patch(
|
|
245
|
-
url=self.
|
|
251
|
+
url=self._get_url_v2(id_=self.id), data={'enable_notification': False}
|
|
246
252
|
)
|
|
247
253
|
logger.info(
|
|
248
254
|
'Notifications have been disabled for alert rule with id: %s', self.id
|
|
@@ -273,7 +279,7 @@ class AlertRule(
|
|
|
273
279
|
webhooks=webhooks,
|
|
274
280
|
)
|
|
275
281
|
response = self._client().patch(
|
|
276
|
-
url=self.
|
|
282
|
+
url=self._get_url_v2(id_=self.id),
|
|
277
283
|
data={'notifications': notifications},
|
|
278
284
|
)
|
|
279
285
|
|
|
@@ -289,7 +295,7 @@ class AlertRule(
|
|
|
289
295
|
:return: NotificationConfig object
|
|
290
296
|
"""
|
|
291
297
|
|
|
292
|
-
response = self._client().get(url=self.
|
|
298
|
+
response = self._client().get(url=self._get_url_v2(id_=self.id))
|
|
293
299
|
|
|
294
300
|
return self._get_notifications_from_dict(
|
|
295
301
|
response.json()['data']['notifications']
|
fiddler/entities/events.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import tempfile
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any, Callable
|
|
6
6
|
from uuid import UUID
|
|
7
7
|
|
|
8
8
|
import pandas as pd
|
|
@@ -17,6 +17,9 @@ from fiddler.entities.job import Job
|
|
|
17
17
|
from fiddler.schemas.dataset import EnvType
|
|
18
18
|
from fiddler.schemas.events import EventsSource, FileSource
|
|
19
19
|
from fiddler.schemas.job import JobCompactResp
|
|
20
|
+
from fiddler.utils.logger import get_logger
|
|
21
|
+
|
|
22
|
+
logger = get_logger(__name__)
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
class EventPublisher(ConnectionMixin):
|
|
@@ -51,40 +54,72 @@ class EventPublisher(ConnectionMixin):
|
|
|
51
54
|
|
|
52
55
|
:return: list[UUID] for list of dicts source and Job object for file path or dataframe source.
|
|
53
56
|
"""
|
|
57
|
+
|
|
58
|
+
publish_method = self._get_publish_method(source=source)
|
|
59
|
+
|
|
60
|
+
return publish_method(
|
|
61
|
+
source=source,
|
|
62
|
+
environment=environment,
|
|
63
|
+
dataset_name=dataset_name,
|
|
64
|
+
update=update,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def _get_publish_method(
|
|
68
|
+
self, source: list[dict[str, Any]] | str | Path | pd.DataFrame
|
|
69
|
+
) -> Callable:
|
|
54
70
|
if isinstance(source, (str, Path)):
|
|
55
|
-
return self._publish_file
|
|
56
|
-
source=source,
|
|
57
|
-
environment=environment,
|
|
58
|
-
dataset_name=dataset_name,
|
|
59
|
-
update=update,
|
|
60
|
-
)
|
|
71
|
+
return self._publish_file
|
|
61
72
|
|
|
62
73
|
if isinstance(source, pd.DataFrame):
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
return self._publish_df
|
|
75
|
+
|
|
76
|
+
if isinstance(source, list):
|
|
77
|
+
return self._publish_stream
|
|
78
|
+
|
|
79
|
+
raise ValueError(f'Unsupported source - {type(source)}')
|
|
80
|
+
|
|
81
|
+
def _publish_df(
|
|
82
|
+
self,
|
|
83
|
+
source: pd.DataFrame,
|
|
84
|
+
environment: EnvType,
|
|
85
|
+
dataset_name: str | None = None,
|
|
86
|
+
update: bool = False,
|
|
87
|
+
) -> Job:
|
|
88
|
+
with tempfile.NamedTemporaryFile(suffix='.parquet') as temp_file:
|
|
89
|
+
try:
|
|
90
|
+
source.to_parquet(temp_file.name, index=False)
|
|
65
91
|
return self._publish_file(
|
|
66
92
|
source=temp_file.name,
|
|
67
93
|
environment=environment,
|
|
68
94
|
dataset_name=dataset_name,
|
|
69
95
|
update=update,
|
|
70
96
|
)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
97
|
+
except Exception:
|
|
98
|
+
logger.warning(
|
|
99
|
+
'Failed to convert input dataframe to parquet format. Retrying as a CSV file.'
|
|
100
|
+
)
|
|
101
|
+
with tempfile.NamedTemporaryFile(suffix='.csv') as temp_file:
|
|
102
|
+
source.to_csv(temp_file.name, index=False)
|
|
103
|
+
return self._publish_file(
|
|
104
|
+
source=temp_file.name,
|
|
105
|
+
environment=environment,
|
|
106
|
+
dataset_name=dataset_name,
|
|
107
|
+
update=update,
|
|
108
|
+
)
|
|
76
109
|
|
|
77
110
|
def _publish_stream(
|
|
78
111
|
self,
|
|
79
112
|
source: list[dict[str, Any]],
|
|
113
|
+
environment: EnvType = EnvType.PRODUCTION,
|
|
114
|
+
dataset_name: str | None = None,
|
|
80
115
|
update: bool = False,
|
|
81
116
|
) -> list[UUID]:
|
|
82
117
|
event_ids = []
|
|
83
118
|
for i in tqdm(range(0, len(source), self.STREAM_LIMIT)):
|
|
84
119
|
response = self._publish_call(
|
|
85
120
|
source=EventsSource(events=source[i : i + self.STREAM_LIMIT]),
|
|
86
|
-
environment=
|
|
87
|
-
dataset_name=
|
|
121
|
+
environment=environment,
|
|
122
|
+
dataset_name=dataset_name,
|
|
88
123
|
update=update,
|
|
89
124
|
)
|
|
90
125
|
event_ids.extend(response.json()['data']['event_ids'])
|
fiddler/entities/model.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import builtins
|
|
4
|
-
import copy
|
|
5
4
|
import typing
|
|
5
|
+
from copy import deepcopy
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
from functools import cached_property
|
|
@@ -33,12 +33,15 @@ from fiddler.schemas.model_spec import ModelSpec
|
|
|
33
33
|
from fiddler.schemas.model_task_params import ModelTaskParams
|
|
34
34
|
from fiddler.schemas.xai_params import XaiParams
|
|
35
35
|
from fiddler.utils.helpers import raise_not_found
|
|
36
|
+
from fiddler.utils.logger import get_logger
|
|
36
37
|
from fiddler.utils.model_generator import ModelGenerator
|
|
37
38
|
|
|
38
39
|
if typing.TYPE_CHECKING:
|
|
39
40
|
from fiddler.entities.baseline import Baseline
|
|
40
41
|
from fiddler.entities.dataset import Dataset
|
|
41
42
|
|
|
43
|
+
logger = get_logger(__name__)
|
|
44
|
+
|
|
42
45
|
|
|
43
46
|
class Model(
|
|
44
47
|
BaseEntity,
|
|
@@ -307,7 +310,9 @@ class Model(
|
|
|
307
310
|
params = {'filter': _filter.json()}
|
|
308
311
|
|
|
309
312
|
for model in cls._paginate(url=cls._get_url(), params=params):
|
|
310
|
-
yield ModelCompact(
|
|
313
|
+
yield ModelCompact(
|
|
314
|
+
id=model['id'], name=model['name'], version=model['version']
|
|
315
|
+
)
|
|
311
316
|
|
|
312
317
|
def duplicate(self, version: str | None = None) -> Model:
|
|
313
318
|
"""
|
|
@@ -319,13 +324,21 @@ class Model(
|
|
|
319
324
|
:param version: Version name for the new instance
|
|
320
325
|
:return: Model instance
|
|
321
326
|
"""
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
327
|
+
return Model(
|
|
328
|
+
name=self.name,
|
|
329
|
+
project_id=self.project_id,
|
|
330
|
+
schema=deepcopy(self.schema),
|
|
331
|
+
spec=deepcopy(self.spec),
|
|
332
|
+
version=version if version else self.version,
|
|
333
|
+
input_type=self.input_type,
|
|
334
|
+
task=self.task,
|
|
335
|
+
task_params=deepcopy(self.task_params),
|
|
336
|
+
description=self.description,
|
|
337
|
+
event_id_col=self.event_id_col,
|
|
338
|
+
event_ts_col=self.event_ts_col,
|
|
339
|
+
event_ts_format=self.event_ts_format,
|
|
340
|
+
xai_params=deepcopy(self.xai_params),
|
|
341
|
+
)
|
|
329
342
|
|
|
330
343
|
@property
|
|
331
344
|
def datasets(self) -> Iterator[Dataset]:
|
|
@@ -456,7 +469,7 @@ class Model(
|
|
|
456
469
|
:return: list[UUID] for list of dicts or dataframe source and Job object for
|
|
457
470
|
file path source.
|
|
458
471
|
"""
|
|
459
|
-
|
|
472
|
+
logger.info('Model[%s/%s] - Publishing events', self.name, self.version)
|
|
460
473
|
return self._event_publisher.publish(
|
|
461
474
|
source=source,
|
|
462
475
|
environment=environment,
|
|
@@ -6,7 +6,9 @@ from uuid import UUID
|
|
|
6
6
|
|
|
7
7
|
from fiddler.decorators import handle_api_error
|
|
8
8
|
from fiddler.entities.base import BaseEntity
|
|
9
|
+
from fiddler.entities.job import Job
|
|
9
10
|
from fiddler.entities.project import ProjectCompactMixin
|
|
11
|
+
from fiddler.schemas.job import JobCompactResp
|
|
10
12
|
from fiddler.schemas.model_deployment import ModelDeploymentResponse
|
|
11
13
|
from fiddler.utils.logger import get_logger
|
|
12
14
|
|
|
@@ -94,7 +96,7 @@ class ModelDeployment(
|
|
|
94
96
|
self._resp = resp_obj
|
|
95
97
|
|
|
96
98
|
@handle_api_error
|
|
97
|
-
def update(self) ->
|
|
99
|
+
def update(self) -> Job:
|
|
98
100
|
"""Update an existing model deployment."""
|
|
99
101
|
payload: dict[str, Any] = {'active': self.active}
|
|
100
102
|
if self.replicas is not None:
|
|
@@ -108,6 +110,8 @@ class ModelDeployment(
|
|
|
108
110
|
url=self._get_url(model_id=self.model_id), data=payload
|
|
109
111
|
)
|
|
110
112
|
self._refresh_from_response(response=response)
|
|
113
|
+
job_compact = JobCompactResp(**response.json()['data']['job'])
|
|
114
|
+
return Job.get(id_=job_compact.id)
|
|
111
115
|
|
|
112
116
|
@classmethod
|
|
113
117
|
def of(cls, model_id: UUID | str) -> ModelDeployment:
|
fiddler/entities/webhook.py
CHANGED
|
@@ -6,7 +6,9 @@ from uuid import UUID
|
|
|
6
6
|
|
|
7
7
|
from fiddler.decorators import handle_api_error
|
|
8
8
|
from fiddler.entities.base import BaseEntity
|
|
9
|
+
from fiddler.schemas.filter_query import OperatorType, QueryCondition, QueryRule
|
|
9
10
|
from fiddler.schemas.webhook import WebhookProvider, WebhookResp
|
|
11
|
+
from fiddler.utils.helpers import raise_not_found
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class Webhook(BaseEntity):
|
|
@@ -81,6 +83,22 @@ class Webhook(BaseEntity):
|
|
|
81
83
|
response = cls._client().get(url=cls._get_url(id_))
|
|
82
84
|
return cls._from_response(response=response)
|
|
83
85
|
|
|
86
|
+
@classmethod
|
|
87
|
+
@handle_api_error
|
|
88
|
+
def from_name(cls, name: str) -> Webhook:
|
|
89
|
+
"""Get the webhook instance using webhook name"""
|
|
90
|
+
_filter = QueryCondition(
|
|
91
|
+
rules=[QueryRule(field='name', operator=OperatorType.EQUAL, value=name)]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
response = cls._client().get(
|
|
95
|
+
url=cls._get_url(), params={'filter': _filter.json()}
|
|
96
|
+
)
|
|
97
|
+
if response.json()['data']['total'] == 0:
|
|
98
|
+
raise_not_found('Webhook not found for the given identifier')
|
|
99
|
+
|
|
100
|
+
return cls._from_dict(data=response.json()['data']['items'][0])
|
|
101
|
+
|
|
84
102
|
@classmethod
|
|
85
103
|
@handle_api_error
|
|
86
104
|
def list(cls) -> Iterator[Webhook]:
|
|
@@ -36,7 +36,8 @@ class TemplateKerasTF2Model:
|
|
|
36
36
|
:param hybrid: Boolean value for hybrid data type (for example matrices). Default to False.
|
|
37
37
|
"""
|
|
38
38
|
import tensorflow as tf
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
from fiddler.packtools.keras_ig_helpers import ComputeGradientsKerasTF2
|
|
40
41
|
|
|
41
42
|
embedding_names = embedding_names or []
|
|
42
43
|
batch_input_shape_list = batch_input_shape_list or []
|
|
@@ -197,7 +198,7 @@ class TemplateTreeShap:
|
|
|
197
198
|
:return: explanations_by_output and extras_by_output: two dictionaries with keys the output columns and values
|
|
198
199
|
the corresponding explanations and extra information necessary
|
|
199
200
|
"""
|
|
200
|
-
from packtools.project_attributions_helpers import TreeShapAttributions
|
|
201
|
+
from fiddler.packtools.project_attributions_helpers import TreeShapAttributions
|
|
201
202
|
|
|
202
203
|
if explanation_name != self.tree_shap_explanations:
|
|
203
204
|
raise NotImplementedError(
|
fiddler/schemas/alert_rule.py
CHANGED
|
@@ -7,29 +7,38 @@ from pydantic import Field
|
|
|
7
7
|
from fiddler.constants.alert_rule import AlertCondition, BinSize, CompareTo, Priority
|
|
8
8
|
from fiddler.schemas.base import BaseModel
|
|
9
9
|
from fiddler.schemas.baseline import BaselineCompactResp
|
|
10
|
+
from fiddler.schemas.custom_expression import SegmentCompactResp
|
|
10
11
|
from fiddler.schemas.model import ModelCompactResp
|
|
11
12
|
from fiddler.schemas.project import ProjectCompactResp
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
class MetricResp(BaseModel):
|
|
16
|
+
id: Union[str, UUID]
|
|
17
|
+
display_name: str
|
|
18
|
+
type: str
|
|
19
|
+
type_display_name: str
|
|
20
|
+
|
|
21
|
+
|
|
14
22
|
class AlertRuleResp(BaseModel):
|
|
15
|
-
id: UUID
|
|
23
|
+
id: UUID
|
|
16
24
|
name: str
|
|
17
|
-
model: ModelCompactResp
|
|
18
|
-
project: ProjectCompactResp
|
|
19
|
-
baseline: Optional[BaselineCompactResp]
|
|
20
25
|
priority: Union[str, Priority]
|
|
21
26
|
compare_to: Union[str, CompareTo]
|
|
22
|
-
metric_id: Union[str, UUID]
|
|
23
|
-
critical_threshold: float
|
|
24
27
|
condition: Union[str, AlertCondition]
|
|
25
|
-
bin_size: Union[str, BinSize]
|
|
26
|
-
columns: Optional[List[str]]
|
|
27
|
-
baseline_id: Optional[UUID]
|
|
28
28
|
compare_bin_delta: Optional[int]
|
|
29
|
+
bin_size: Union[str, BinSize]
|
|
30
|
+
columns: Optional[List[str]] = Field(alias='feature_names')
|
|
31
|
+
critical_threshold: float
|
|
29
32
|
warning_threshold: Optional[float]
|
|
30
33
|
|
|
31
34
|
created_at: datetime
|
|
32
|
-
updated_at: datetime
|
|
35
|
+
updated_at: datetime
|
|
36
|
+
|
|
37
|
+
metric: MetricResp
|
|
38
|
+
model: ModelCompactResp
|
|
39
|
+
project: ProjectCompactResp
|
|
40
|
+
baseline: Optional[BaselineCompactResp]
|
|
41
|
+
segment: Optional[SegmentCompactResp]
|
|
33
42
|
|
|
34
43
|
|
|
35
44
|
class NotificationConfig(BaseModel):
|
|
@@ -28,60 +28,113 @@ from fiddler.utils.logger import set_logging
|
|
|
28
28
|
|
|
29
29
|
API_RESPONSE_200 = {
|
|
30
30
|
'data': {
|
|
31
|
-
'
|
|
32
|
-
'id':
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
'alert_type': 'data_integrity',
|
|
38
|
-
'metric_display_name': 'Missing Value Violation',
|
|
39
|
-
'enable_notification': True,
|
|
40
|
-
'last_updated': '2023-11-28T08:04:12.653346+00:00',
|
|
41
|
-
'alert_type_display_name': 'Data Integrity',
|
|
42
|
-
'critical_threshold': 0.0,
|
|
43
|
-
'baseline_name': BASELINE_NAME,
|
|
44
|
-
'baseline': {
|
|
45
|
-
'id': BASELINE_ID,
|
|
46
|
-
'name': BASELINE_NAME,
|
|
31
|
+
'id': ALERT_RULE_ID,
|
|
32
|
+
'model': {'id': MODEL_ID, 'name': MODEL_NAME, 'version': 'v1'},
|
|
33
|
+
'project': {'id': PROJECT_ID, 'name': PROJECT_NAME},
|
|
34
|
+
'organization': {
|
|
35
|
+
'id': 'e03d6f02-6efa-4eb1-b828-7ea9d2a083d0',
|
|
36
|
+
'name': 'scale154',
|
|
47
37
|
},
|
|
48
|
-
'
|
|
49
|
-
'uuid': ALERT_RULE_ID,
|
|
38
|
+
'baseline': None,
|
|
50
39
|
'segment': None,
|
|
51
|
-
'
|
|
52
|
-
'name': '
|
|
53
|
-
'
|
|
54
|
-
'
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
'version': 'rule_v3',
|
|
41
|
+
'name': 'skdfvbj',
|
|
42
|
+
'priority': 'HIGH',
|
|
43
|
+
'feature_names': ['gender'],
|
|
44
|
+
'bin_size': 'Hour',
|
|
45
|
+
'compare_to': 'raw_value',
|
|
46
|
+
'condition': 'greater',
|
|
47
|
+
'warning_threshold': 1.0,
|
|
48
|
+
'critical_threshold': 2.0,
|
|
49
|
+
'updated_at': '2024-04-17T12:42:15.672909+00:00',
|
|
50
|
+
'enable_notification': True,
|
|
51
|
+
'compare_bin_delta': 0,
|
|
52
|
+
'created_at': '2024-04-17T12:42:15.672909+00:00',
|
|
53
|
+
'created_by': {
|
|
54
|
+
'id': '287949c2-13f8-49fc-a559-55b2e6f36e9b',
|
|
55
|
+
'full_name': 'Fiddler Administrator',
|
|
56
|
+
'email': 'admin@fiddler.ai',
|
|
57
57
|
},
|
|
58
|
+
'updated_by': {
|
|
59
|
+
'id': '287949c2-13f8-49fc-a559-55b2e6f36e9b',
|
|
60
|
+
'full_name': 'Fiddler Administrator',
|
|
61
|
+
'email': 'admin@fiddler.ai',
|
|
62
|
+
},
|
|
63
|
+
'metric': {
|
|
64
|
+
'id': 'null_violation_percentage',
|
|
65
|
+
'display_name': '% Missing Value Violation',
|
|
66
|
+
'type': 'data_integrity',
|
|
67
|
+
'type_display_name': 'Data Integrity',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
'api_version': '3.0',
|
|
71
|
+
'kind': 'NORMAL',
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
API_RESPONSE_200_V2 = {
|
|
75
|
+
'data': {
|
|
76
|
+
'id': 794,
|
|
77
|
+
'organization_name': 'mainbuild',
|
|
78
|
+
'project_name': PROJECT_NAME,
|
|
79
|
+
'model_id': 285,
|
|
80
|
+
'name': 'test_rule',
|
|
81
|
+
'metric': 'jsd',
|
|
82
|
+
'metric_id': 'jsd',
|
|
83
|
+
'metric_display_name': 'Jensen-Shannon Distance',
|
|
84
|
+
'alert_type': 'drift',
|
|
85
|
+
'alert_type_display_name': 'Data Drift',
|
|
86
|
+
'priority': 'MEDIUM',
|
|
87
|
+
'baseline_name': BASELINE_NAME,
|
|
88
|
+
'feature_names': ['creditscore'],
|
|
89
|
+
'time_bucket': 3600000,
|
|
90
|
+
'category': None,
|
|
91
|
+
'bin_size': 'Hour',
|
|
92
|
+
'compare_to': 'raw_value',
|
|
93
|
+
'condition': 'greater',
|
|
94
|
+
'compare_period': None,
|
|
95
|
+
'warning_threshold': 1.0,
|
|
96
|
+
'critical_threshold': 2.3,
|
|
58
97
|
'is_active': True,
|
|
59
98
|
'created_by': 'admin@fiddler.ai',
|
|
60
|
-
'
|
|
61
|
-
'
|
|
62
|
-
'
|
|
63
|
-
'
|
|
64
|
-
|
|
65
|
-
'
|
|
99
|
+
'created_at': '2024-04-23T10:34:14.321895+00:00',
|
|
100
|
+
'last_updated': '2024-04-23T10:34:14.323554+00:00',
|
|
101
|
+
'model_name': 'bank_churn',
|
|
102
|
+
'enable_notification': True,
|
|
103
|
+
'segment': {
|
|
104
|
+
'id': '1c9b551a-808b-4ef0-b67b-e788eaf1e6de',
|
|
105
|
+
'organization_name': 'mainbuild',
|
|
106
|
+
'project_name': 'nick_test_project_11',
|
|
107
|
+
'model_name': 'bank_churn',
|
|
108
|
+
'name': 'xyzab',
|
|
109
|
+
'definition': 'Age > 10',
|
|
110
|
+
'description': 'meh',
|
|
111
|
+
'created_at': '2024-04-23T10:31:52.181550+00:00',
|
|
112
|
+
'created_by': {
|
|
113
|
+
'id': 1,
|
|
114
|
+
'full_name': 'Fiddler Administrator',
|
|
115
|
+
'email': 'admin@fiddler.ai',
|
|
116
|
+
},
|
|
66
117
|
},
|
|
67
|
-
'
|
|
68
|
-
'
|
|
69
|
-
'
|
|
70
|
-
'
|
|
118
|
+
'project': {'id': PROJECT_ID, 'name': PROJECT_NAME},
|
|
119
|
+
'model': {'id': MODEL_ID, 'name': MODEL_NAME, 'version': 'v1'},
|
|
120
|
+
'baseline': {'id': BASELINE_ID, 'name': BASELINE_NAME},
|
|
121
|
+
'compare_bin_delta': None,
|
|
122
|
+
'uuid': ALERT_RULE_ID,
|
|
71
123
|
'notifications': {
|
|
72
|
-
'emails': {'alert_config_uuid': ALERT_RULE_ID, 'email': 'admin@fiddler.ai'},
|
|
73
124
|
'pagerduty': {
|
|
74
|
-
'severity': '',
|
|
75
125
|
'alert_config_uuid': ALERT_RULE_ID,
|
|
76
126
|
'service': '',
|
|
127
|
+
'severity': '',
|
|
77
128
|
},
|
|
78
|
-
'
|
|
129
|
+
'emails': {'email': ''},
|
|
130
|
+
'webhooks': [],
|
|
79
131
|
},
|
|
80
132
|
},
|
|
81
133
|
'api_version': '2.0',
|
|
82
134
|
'kind': 'NORMAL',
|
|
83
135
|
}
|
|
84
136
|
|
|
137
|
+
|
|
85
138
|
API_RESPONSE_404 = {
|
|
86
139
|
'error': {
|
|
87
140
|
'code': 404,
|
|
@@ -109,46 +162,44 @@ LIST_API_RESPONSE = {
|
|
|
109
162
|
'items': [
|
|
110
163
|
API_RESPONSE_200['data'],
|
|
111
164
|
{
|
|
112
|
-
'
|
|
113
|
-
'
|
|
114
|
-
'
|
|
115
|
-
'
|
|
116
|
-
|
|
117
|
-
|
|
165
|
+
'id': '3431b746-d18f-4aed-99ea-2da8d5b46fb6',
|
|
166
|
+
'model': {'id': MODEL_ID, 'name': MODEL_NAME, 'version': 'v1'},
|
|
167
|
+
'project': {'id': PROJECT_ID, 'name': PROJECT_NAME},
|
|
168
|
+
'organization': {
|
|
169
|
+
'id': 'e03d6f02-6efa-4eb1-b828-7ea9d2a083d0',
|
|
170
|
+
'name': 'scale154',
|
|
171
|
+
},
|
|
172
|
+
'baseline': {'id': BASELINE_ID, 'name': BASELINE_NAME},
|
|
173
|
+
'segment': None,
|
|
174
|
+
'version': 'rule_v3',
|
|
175
|
+
'name': 'sfdvsefv',
|
|
176
|
+
'priority': 'HIGH',
|
|
177
|
+
'feature_names': ['numofproducts'],
|
|
118
178
|
'bin_size': 'Hour',
|
|
179
|
+
'compare_to': 'raw_value',
|
|
180
|
+
'condition': 'greater',
|
|
181
|
+
'warning_threshold': 1e-05,
|
|
182
|
+
'critical_threshold': 0.001,
|
|
183
|
+
'updated_at': '2024-04-17T12:43:23.871997+00:00',
|
|
119
184
|
'enable_notification': True,
|
|
120
|
-
'
|
|
121
|
-
'
|
|
122
|
-
'
|
|
123
|
-
'id':
|
|
124
|
-
'
|
|
185
|
+
'compare_bin_delta': 0,
|
|
186
|
+
'created_at': '2024-04-17T12:43:23.871997+00:00',
|
|
187
|
+
'created_by': {
|
|
188
|
+
'id': '287949c2-13f8-49fc-a559-55b2e6f36e9b',
|
|
189
|
+
'full_name': 'Fiddler Administrator',
|
|
190
|
+
'email': 'admin@fiddler.ai',
|
|
125
191
|
},
|
|
126
|
-
'
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
'id': BASELINE_ID,
|
|
131
|
-
'name': BASELINE_NAME,
|
|
192
|
+
'updated_by': {
|
|
193
|
+
'id': '287949c2-13f8-49fc-a559-55b2e6f36e9b',
|
|
194
|
+
'full_name': 'Fiddler Administrator',
|
|
195
|
+
'email': 'admin@fiddler.ai',
|
|
132
196
|
},
|
|
133
|
-
'
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
'alert_type': 'service_metrics',
|
|
139
|
-
'time_bucket': 3600000,
|
|
140
|
-
'alert_type_display_name': 'Traffic',
|
|
141
|
-
'compare_period': 5,
|
|
142
|
-
'compare_to': 'time_period',
|
|
143
|
-
'uuid': '42eb6d51-dc5c-4e04-82aa-440b2d840704',
|
|
144
|
-
'id': 618,
|
|
145
|
-
'feature_names': None,
|
|
146
|
-
'model_name': MODEL_NAME,
|
|
147
|
-
'model': {
|
|
148
|
-
'id': MODEL_ID,
|
|
149
|
-
'name': MODEL_NAME,
|
|
197
|
+
'metric': {
|
|
198
|
+
'id': 'jsd',
|
|
199
|
+
'display_name': 'Jensen-Shannon Distance',
|
|
200
|
+
'type': 'drift',
|
|
201
|
+
'type_display_name': 'Data Drift',
|
|
150
202
|
},
|
|
151
|
-
'last_updated': '2023-11-28T08:04:10.918587+00:00',
|
|
152
203
|
},
|
|
153
204
|
],
|
|
154
205
|
}
|
|
@@ -170,7 +221,7 @@ LIST_API_RESPONSE_EMPTY = {
|
|
|
170
221
|
@responses.activate
|
|
171
222
|
def test_get_alert_rule_success() -> None:
|
|
172
223
|
responses.get(
|
|
173
|
-
url=f'{URL}/
|
|
224
|
+
url=f'{URL}/v3/alert-rules/{ALERT_RULE_ID}',
|
|
174
225
|
json=API_RESPONSE_200,
|
|
175
226
|
)
|
|
176
227
|
|
|
@@ -182,7 +233,7 @@ def test_get_alert_rule_success() -> None:
|
|
|
182
233
|
@responses.activate
|
|
183
234
|
def test_get_alert_rule_not_found() -> None:
|
|
184
235
|
responses.get(
|
|
185
|
-
url=f'{URL}/
|
|
236
|
+
url=f'{URL}/v3/alert-rules/{ALERT_RULE_ID}',
|
|
186
237
|
json=API_RESPONSE_404,
|
|
187
238
|
status=HTTPStatus.NOT_FOUND,
|
|
188
239
|
)
|
|
@@ -194,7 +245,7 @@ def test_get_alert_rule_not_found() -> None:
|
|
|
194
245
|
@responses.activate
|
|
195
246
|
def test_alert_rule_list_success() -> None:
|
|
196
247
|
responses.get(
|
|
197
|
-
url=f'{URL}/
|
|
248
|
+
url=f'{URL}/v3/alert-rules',
|
|
198
249
|
json=LIST_API_RESPONSE,
|
|
199
250
|
)
|
|
200
251
|
for rule in AlertRule.list(model_id=MODEL_ID):
|
|
@@ -205,7 +256,7 @@ def test_alert_rule_list_success() -> None:
|
|
|
205
256
|
def test_alert_rule_list_empty() -> None:
|
|
206
257
|
|
|
207
258
|
responses.get(
|
|
208
|
-
url=f'{URL}/
|
|
259
|
+
url=f'{URL}/v3/alert-rules',
|
|
209
260
|
json=LIST_API_RESPONSE_EMPTY,
|
|
210
261
|
)
|
|
211
262
|
|
|
@@ -215,13 +266,13 @@ def test_alert_rule_list_empty() -> None:
|
|
|
215
266
|
@responses.activate
|
|
216
267
|
def test_delete_alert_rule() -> None:
|
|
217
268
|
responses.get(
|
|
218
|
-
url=f'{URL}/
|
|
269
|
+
url=f'{URL}/v3/alert-rules/{ALERT_RULE_ID}',
|
|
219
270
|
json=API_RESPONSE_200,
|
|
220
271
|
)
|
|
221
272
|
rule = AlertRule.get(id_=ALERT_RULE_ID)
|
|
222
273
|
|
|
223
274
|
responses.delete(
|
|
224
|
-
url=f'{URL}/
|
|
275
|
+
url=f'{URL}/v3/alert-rules/{ALERT_RULE_ID}',
|
|
225
276
|
)
|
|
226
277
|
|
|
227
278
|
rule.delete()
|
|
@@ -230,13 +281,13 @@ def test_delete_alert_rule() -> None:
|
|
|
230
281
|
@responses.activate
|
|
231
282
|
def test_delete_alert_rule_not_found() -> None:
|
|
232
283
|
responses.get(
|
|
233
|
-
url=f'{URL}/
|
|
284
|
+
url=f'{URL}/v3/alert-rules/{ALERT_RULE_ID}',
|
|
234
285
|
json=API_RESPONSE_200,
|
|
235
286
|
)
|
|
236
287
|
rule = AlertRule.get(id_=ALERT_RULE_ID)
|
|
237
288
|
|
|
238
289
|
responses.delete(
|
|
239
|
-
url=f'{URL}/
|
|
290
|
+
url=f'{URL}/v3/alert-rules/{ALERT_RULE_ID}',
|
|
240
291
|
json=API_RESPONSE_404,
|
|
241
292
|
status=HTTPStatus.NOT_FOUND,
|
|
242
293
|
)
|
|
@@ -256,7 +307,7 @@ def test_add_alert_rule_success() -> None:
|
|
|
256
307
|
json=BASELINE_API_RESPONSE_200,
|
|
257
308
|
)
|
|
258
309
|
responses.post(
|
|
259
|
-
url=f'{URL}/
|
|
310
|
+
url=f'{URL}/v3/alert-rules',
|
|
260
311
|
json=API_RESPONSE_200,
|
|
261
312
|
)
|
|
262
313
|
alert_rule = AlertRule(
|
|
@@ -281,7 +332,7 @@ def test_add_alert_rule_success() -> None:
|
|
|
281
332
|
@responses.activate
|
|
282
333
|
def test_enable_notifications(caplog) -> None:
|
|
283
334
|
responses.get(
|
|
284
|
-
url=f'{URL}/
|
|
335
|
+
url=f'{URL}/v3/alert-rules/{ALERT_RULE_ID}',
|
|
285
336
|
json=API_RESPONSE_200,
|
|
286
337
|
)
|
|
287
338
|
|
|
@@ -289,7 +340,7 @@ def test_enable_notifications(caplog) -> None:
|
|
|
289
340
|
|
|
290
341
|
resp = responses.patch(
|
|
291
342
|
url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
|
|
292
|
-
json=
|
|
343
|
+
json=API_RESPONSE_200_V2,
|
|
293
344
|
)
|
|
294
345
|
set_logging(logging.INFO)
|
|
295
346
|
|
|
@@ -304,7 +355,7 @@ def test_enable_notifications(caplog) -> None:
|
|
|
304
355
|
@responses.activate
|
|
305
356
|
def test_disable_notifications(caplog) -> None:
|
|
306
357
|
responses.get(
|
|
307
|
-
url=f'{URL}/
|
|
358
|
+
url=f'{URL}/v3/alert-rules/{ALERT_RULE_ID}',
|
|
308
359
|
json=API_RESPONSE_200,
|
|
309
360
|
)
|
|
310
361
|
|
|
@@ -314,7 +365,7 @@ def test_disable_notifications(caplog) -> None:
|
|
|
314
365
|
|
|
315
366
|
resp = responses.patch(
|
|
316
367
|
url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
|
|
317
|
-
json=
|
|
368
|
+
json=API_RESPONSE_200_V2,
|
|
318
369
|
)
|
|
319
370
|
|
|
320
371
|
set_logging(logging.INFO)
|
|
@@ -329,12 +380,12 @@ def test_disable_notifications(caplog) -> None:
|
|
|
329
380
|
@responses.activate
|
|
330
381
|
def test_set_notifications() -> None:
|
|
331
382
|
responses.get(
|
|
332
|
-
url=f'{URL}/
|
|
383
|
+
url=f'{URL}/v3/alert-rules/{ALERT_RULE_ID}',
|
|
333
384
|
json=API_RESPONSE_200,
|
|
334
385
|
)
|
|
335
386
|
|
|
336
387
|
alert_rule = AlertRule.get(id_=ALERT_RULE_ID)
|
|
337
|
-
|
|
388
|
+
API_RESPONSE_200_V2['data']['notifications'] = {
|
|
338
389
|
'webhooks': [
|
|
339
390
|
{'uuid': 'e20bf4cc-d2cf-4540-baef-d96913b14f1b'},
|
|
340
391
|
{'uuid': '6e796fda-0111-4a72-82cd-f0f219e903e1'},
|
|
@@ -351,7 +402,7 @@ def test_set_notifications() -> None:
|
|
|
351
402
|
}
|
|
352
403
|
responses.patch(
|
|
353
404
|
url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
|
|
354
|
-
json=
|
|
405
|
+
json=API_RESPONSE_200_V2,
|
|
355
406
|
)
|
|
356
407
|
notifications = alert_rule.set_notification_config(
|
|
357
408
|
emails=['nikhil@fiddler.ai', 'admin@fiddler.ai'],
|
|
@@ -374,14 +425,14 @@ def test_set_notifications() -> None:
|
|
|
374
425
|
@responses.activate
|
|
375
426
|
def test_get_notifications() -> None:
|
|
376
427
|
responses.get(
|
|
377
|
-
url=f'{URL}/
|
|
428
|
+
url=f'{URL}/v3/alert-rules/{ALERT_RULE_ID}',
|
|
378
429
|
json=API_RESPONSE_200,
|
|
379
430
|
)
|
|
380
431
|
|
|
381
432
|
alert_rule = AlertRule.get(id_=ALERT_RULE_ID)
|
|
382
433
|
responses.get(
|
|
383
434
|
url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
|
|
384
|
-
json=
|
|
435
|
+
json=API_RESPONSE_200_V2,
|
|
385
436
|
)
|
|
386
437
|
notifications = alert_rule.get_notification_config()
|
|
387
438
|
assert notifications == NotificationConfig(
|
fiddler/tests/apis/test_model.py
CHANGED
|
@@ -471,6 +471,7 @@ def test_model_list_success() -> None:
|
|
|
471
471
|
models = Model.list(project_id=PROJECT_ID)
|
|
472
472
|
for model in models:
|
|
473
473
|
assert isinstance(model, ModelCompact)
|
|
474
|
+
assert model.version is not None
|
|
474
475
|
|
|
475
476
|
|
|
476
477
|
@responses.activate
|
|
@@ -654,14 +655,26 @@ def test_model_duplicate() -> None:
|
|
|
654
655
|
model = Model.get(id_=MODEL_ID)
|
|
655
656
|
|
|
656
657
|
assert model.id == UUID(MODEL_ID)
|
|
658
|
+
assert model._event_publisher.model_id == model.id
|
|
657
659
|
|
|
658
660
|
# Without version parameter
|
|
659
661
|
model_copy = model.duplicate()
|
|
662
|
+
|
|
660
663
|
assert model_copy.id is None
|
|
661
664
|
assert model_copy.version == model.version
|
|
662
665
|
|
|
666
|
+
with pytest.raises(AssertionError):
|
|
667
|
+
# since id is None
|
|
668
|
+
_ = model_copy._event_publisher
|
|
669
|
+
|
|
663
670
|
# With version parameter
|
|
664
671
|
new_version = 'v2'
|
|
665
672
|
model_copy = model.duplicate(version=new_version)
|
|
666
673
|
assert model_copy.id is None
|
|
667
674
|
assert model_copy.version == new_version
|
|
675
|
+
|
|
676
|
+
# Update new version
|
|
677
|
+
assert model.schema['CreditScore'].max == 850
|
|
678
|
+
model_copy.schema['CreditScore'].max = 900
|
|
679
|
+
assert model.schema['CreditScore'].max == 850
|
|
680
|
+
assert model_copy.schema['CreditScore'].max == 900
|
|
@@ -3,9 +3,12 @@ from http import HTTPStatus
|
|
|
3
3
|
import pytest
|
|
4
4
|
import responses
|
|
5
5
|
|
|
6
|
+
from fiddler.entities.job import Job
|
|
6
7
|
from fiddler.entities.model_deployment import ModelDeployment
|
|
7
8
|
from fiddler.exceptions import NotFound
|
|
8
9
|
from fiddler.tests.constants import (
|
|
10
|
+
JOB_ID,
|
|
11
|
+
JOB_NAME,
|
|
9
12
|
MODEL_DEPLOYMENT_ID,
|
|
10
13
|
MODEL_ID,
|
|
11
14
|
MODEL_NAME,
|
|
@@ -50,6 +53,7 @@ API_RESPONSE_200 = {
|
|
|
50
53
|
'full_name': USER_NAME,
|
|
51
54
|
'email': USER_EMAIL,
|
|
52
55
|
},
|
|
56
|
+
'job': {'id': JOB_ID, 'name': 'Deploy artifact model - manual'},
|
|
53
57
|
},
|
|
54
58
|
}
|
|
55
59
|
|
|
@@ -67,6 +71,31 @@ API_RESPONSE_404 = {
|
|
|
67
71
|
}
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
JOB_API_RESPONSE_200 = {
|
|
75
|
+
'api_version': '3.0',
|
|
76
|
+
'kind': 'NORMAL',
|
|
77
|
+
'data': {
|
|
78
|
+
'name': JOB_NAME,
|
|
79
|
+
'info': {
|
|
80
|
+
'resource_type': 'MODEL',
|
|
81
|
+
'resource_name': 'bank_churn',
|
|
82
|
+
'project_name': 'bank_churn',
|
|
83
|
+
},
|
|
84
|
+
'id': JOB_ID,
|
|
85
|
+
'status': 'SUCCESS',
|
|
86
|
+
'progress': 100,
|
|
87
|
+
'error_message': None,
|
|
88
|
+
'error_reason': None,
|
|
89
|
+
'extras': {
|
|
90
|
+
'e36d1cf2-766f-4705-8269-b6f93bf1ca14': {
|
|
91
|
+
'status': 'SUCCESS',
|
|
92
|
+
'result': {'result': 'Success'},
|
|
93
|
+
'error_message': None,
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
|
|
70
99
|
|
|
71
100
|
@responses.activate
|
|
72
101
|
def test_update_model_deployment_success() -> None:
|
|
@@ -79,8 +108,13 @@ def test_update_model_deployment_success() -> None:
|
|
|
79
108
|
model_deployment.cpu = 300
|
|
80
109
|
model_deployment.active = True
|
|
81
110
|
|
|
82
|
-
|
|
111
|
+
responses.get(
|
|
112
|
+
url=f'{URL}/v3/jobs/{JOB_ID}',
|
|
113
|
+
json=JOB_API_RESPONSE_200,
|
|
114
|
+
)
|
|
115
|
+
job = model_deployment.update()
|
|
83
116
|
assert isinstance(model_deployment, ModelDeployment)
|
|
117
|
+
assert isinstance(job, Job)
|
|
84
118
|
assert model_deployment.cpu == 300
|
|
85
119
|
assert model_deployment.active
|
|
86
120
|
|
|
@@ -48,6 +48,29 @@ API_RESPONSE_404 = {
|
|
|
48
48
|
'kind': 'ERROR',
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
API_RESPONSE_FROM_NAME = {
|
|
52
|
+
'data': {
|
|
53
|
+
'page_size': 100,
|
|
54
|
+
'total': 1,
|
|
55
|
+
'item_count': 1,
|
|
56
|
+
'page_count': 1,
|
|
57
|
+
'page_index': 1,
|
|
58
|
+
'offset': 0,
|
|
59
|
+
'items': [
|
|
60
|
+
{
|
|
61
|
+
'id': 1,
|
|
62
|
+
'uuid': WEBHOOK_ID,
|
|
63
|
+
'name': WEBHOOK_NAME,
|
|
64
|
+
'url': WEBHOOK_URL,
|
|
65
|
+
'provider': WEBHOOK_PROVIDER,
|
|
66
|
+
'created_at': '2024-04-30T13:38:08.408013+00:00',
|
|
67
|
+
'updated_at': '2024-04-30T13:38:08.408013+00:00',
|
|
68
|
+
'organization_name': ORG_NAME,
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
51
74
|
LIST_API_RESPONSE = {
|
|
52
75
|
'data': {
|
|
53
76
|
'page_size': 100,
|
|
@@ -124,6 +147,40 @@ def test_get_webhook_not_found() -> None:
|
|
|
124
147
|
Webhook.get(id_=webhook_id)
|
|
125
148
|
|
|
126
149
|
|
|
150
|
+
@responses.activate
|
|
151
|
+
def test_webhook_from_name_success() -> None:
|
|
152
|
+
params = {
|
|
153
|
+
'filter': '{"condition": "AND", "rules": [{"field": "name", "operator": "equal", "value": "test_webhook_config_name"}]}'
|
|
154
|
+
}
|
|
155
|
+
responses.get(
|
|
156
|
+
url=f'{URL}/v2/webhooks',
|
|
157
|
+
json=API_RESPONSE_FROM_NAME,
|
|
158
|
+
match=[responses.matchers.query_param_matcher(params)],
|
|
159
|
+
)
|
|
160
|
+
webhook = Webhook.from_name(name=WEBHOOK_NAME)
|
|
161
|
+
assert isinstance(webhook, Webhook)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@responses.activate
|
|
165
|
+
def test_webhook_from_name_not_found() -> None:
|
|
166
|
+
resp = API_RESPONSE_FROM_NAME.copy()
|
|
167
|
+
resp['data']['total'] = 0
|
|
168
|
+
resp['data']['item_count'] = 0
|
|
169
|
+
resp['data']['items'] = []
|
|
170
|
+
|
|
171
|
+
params = {
|
|
172
|
+
'filter': '{"condition": "AND", "rules": [{"field": "name", "operator": "equal", "value": "test_webhook_config_name"}]}'
|
|
173
|
+
}
|
|
174
|
+
responses.get(
|
|
175
|
+
url=f'{URL}/v2/webhooks',
|
|
176
|
+
json=resp,
|
|
177
|
+
match=[responses.matchers.query_param_matcher(params)],
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
with pytest.raises(NotFound):
|
|
181
|
+
Webhook.from_name(name=WEBHOOK_NAME)
|
|
182
|
+
|
|
183
|
+
|
|
127
184
|
@responses.activate
|
|
128
185
|
def test_webhook_list_success() -> None:
|
|
129
186
|
responses.get(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fiddler-client
|
|
3
|
-
Version: 3.1.0.
|
|
3
|
+
Version: 3.1.0.dev4
|
|
4
4
|
Summary: Python client for Fiddler Platform
|
|
5
5
|
Home-page: https://fiddler.ai
|
|
6
6
|
Author: Fiddler Labs
|
|
@@ -20,6 +20,7 @@ Requires-Dist: tqdm
|
|
|
20
20
|
Requires-Dist: simplejson >=3.17.0
|
|
21
21
|
Requires-Dist: pyarrow >=7.0.0
|
|
22
22
|
Requires-Dist: pyyaml
|
|
23
|
+
Requires-Dist: typing-extensions <=4.5.0
|
|
23
24
|
|
|
24
25
|
Fiddler Client
|
|
25
26
|
=============
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
fiddler/VERSION,sha256=
|
|
2
|
-
fiddler/__init__.py,sha256=
|
|
1
|
+
fiddler/VERSION,sha256=H4NIgZvgBtvg0Z66xzllqEkQ_AMRCrUw2l3eCzDowk0,11
|
|
2
|
+
fiddler/__init__.py,sha256=BfKccyHmcEVThtxSPYLBqn5pOtE0EJ9f_y1_bf3suHg,3824
|
|
3
3
|
fiddler/configs.py,sha256=ZimSo0Gk7j1BFkjDHdRdycrGZ8oh-7G5TBXYiOh1HvU,217
|
|
4
4
|
fiddler/connection.py,sha256=fLdSXpu5dwoK-h4Vtc9nbdKENOJ7jfEmrnDhVAtChck,5677
|
|
5
5
|
fiddler/decorators.py,sha256=tWZliQQeUGwY0rU52FO9JXuOBueQXVEJywEbrgUEw1U,1820
|
|
@@ -17,22 +17,22 @@ fiddler/constants/model_deployment.py,sha256=SfagLKVD7tK1QmYQFJLDrA4-6FaSeNF6tik
|
|
|
17
17
|
fiddler/constants/xai.py,sha256=GR8VguB6FTfMJZ291FIbfDsY4S9BGOft3VxNW5jMf90,214
|
|
18
18
|
fiddler/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
fiddler/entities/alert_record.py,sha256=RcDACWDNpntkiId-JpkLQvVeIbuaZAzn6AWl2zYIh9A,3564
|
|
20
|
-
fiddler/entities/alert_rule.py,sha256=
|
|
20
|
+
fiddler/entities/alert_rule.py,sha256=JQknBnSNnjIL4CeYvCBy5TVNN7LVE4-7BtBFpaQ9tJ0,11744
|
|
21
21
|
fiddler/entities/base.py,sha256=YCTLt1ta8UbD7u-5BW7v_ocA4S7LevEncI5XPS_SB60,2106
|
|
22
22
|
fiddler/entities/baseline.py,sha256=k3D-K8WC5MkHDILyejRaH1v9jdCIymT96McDBOVQ9A0,7907
|
|
23
23
|
fiddler/entities/custom_expression.py,sha256=SdS61JiI0gOxWwHAGxkHh1vqS5XsRtyeNSDs5E-Sg2U,6568
|
|
24
24
|
fiddler/entities/dataset.py,sha256=kwD1pZMG6DQGIjejU7A2KUUBRgOi0o1qbbUX-PSlnGg,4399
|
|
25
|
-
fiddler/entities/events.py,sha256=
|
|
25
|
+
fiddler/entities/events.py,sha256=_orqFyg0m5tBREa21f36ZaX_4kKEtpA2Hsff0d__Uw0,5383
|
|
26
26
|
fiddler/entities/file.py,sha256=dk8ISfjy0--F2eF23Ae8vGiEZmJW6e9LMqNmKfrY2mE,4710
|
|
27
27
|
fiddler/entities/job.py,sha256=Feiqo6ns4bRcp2uaV-kuGYAKs90-aYXvzqqi1yLivYE,5220
|
|
28
|
-
fiddler/entities/model.py,sha256=
|
|
28
|
+
fiddler/entities/model.py,sha256=giqLWvnqXsqrekJ3sIeHf3vUUYO43PxrGh6I_KsGfH4,19271
|
|
29
29
|
fiddler/entities/model_artifact.py,sha256=Y7o1mFs-nVDz7HI9mVBu9ICwkqGD_QbT3q6vf8KKesk,5520
|
|
30
|
-
fiddler/entities/model_deployment.py,sha256=
|
|
30
|
+
fiddler/entities/model_deployment.py,sha256=pzS6xPg4zuhrIzMbF44ptK_ZwV8yN6URF0hDwyVhnwY,3966
|
|
31
31
|
fiddler/entities/organization.py,sha256=XvUAdBUOYXzdmblxIs4hi0mAg45NtAwy5DBaMiqi47o,1074
|
|
32
32
|
fiddler/entities/project.py,sha256=G5omjjKHEjvhenMvW-R0bYwrkRVHixIOhTPZb1pS7FI,4631
|
|
33
33
|
fiddler/entities/surrogate.py,sha256=94RkJmv8uwNhRL8PaliuiRv-R0EXDMKJTimE9boXKwQ,3077
|
|
34
34
|
fiddler/entities/user.py,sha256=ev_Xx9QHAdEHYzNDafzQihXOcD8ME5XkpKgc8MiqCnw,1243
|
|
35
|
-
fiddler/entities/webhook.py,sha256=
|
|
35
|
+
fiddler/entities/webhook.py,sha256=BhwV-05EaFtOATbnlPWUaGeziRSewBPSNSGsYFbIRfc,4514
|
|
36
36
|
fiddler/entities/xai.py,sha256=tJ5HQnaV_svR7do1secIIIvQ0ckJMnW0xjoThn8Cz6s,20250
|
|
37
37
|
fiddler/libs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
38
|
fiddler/libs/http_client.py,sha256=bl2V-q66NoPHm950CiDqOoF-kCPztYWUYNy9hVUsbqg,6027
|
|
@@ -42,13 +42,13 @@ fiddler/packtools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
|
|
|
42
42
|
fiddler/packtools/gem.py,sha256=jzODf1sbAbAXMU74nrU9x7T5eqU4kiExzrT2Hd__olo,4637
|
|
43
43
|
fiddler/packtools/keras_ig_helpers.py,sha256=ht_ILtpkOxCl2B5Q-KFnKV0k_oB-HkMbRJm3h3pjb5Y,7941
|
|
44
44
|
fiddler/packtools/project_attributions_helpers.py,sha256=K-xpsKfwRS5GOMYwTmXY-J3t7gtGbdVSpKB38cpe-fY,15799
|
|
45
|
-
fiddler/packtools/template_model.py,sha256=
|
|
45
|
+
fiddler/packtools/template_model.py,sha256=slQjl5eEjhDUtvWeIF_WvSUerX7VjW0822uo4Qkx-gg,10484
|
|
46
46
|
fiddler/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
47
|
fiddler/schemas/alert_record.py,sha256=Edfe264zUwqF856tMblN85CsEa_wn9oZCoLruvsNAbo,668
|
|
48
|
-
fiddler/schemas/alert_rule.py,sha256
|
|
48
|
+
fiddler/schemas/alert_rule.py,sha256=XbNZDBd5adLVvmyTydnG7lkdZ-ieI8maWACwBX-FQD0,1387
|
|
49
49
|
fiddler/schemas/base.py,sha256=upzAmneEz169h_w-UDH_tY0Abo7DeK68YOZ5qVwIMa8,189
|
|
50
50
|
fiddler/schemas/baseline.py,sha256=tBQji1GLYt0ZYgEK6ZTmXxk5nUYTI6oxMmSjNXnRfWw,933
|
|
51
|
-
fiddler/schemas/custom_expression.py,sha256=
|
|
51
|
+
fiddler/schemas/custom_expression.py,sha256=LGHht3LB0HGghEfy808VnaF3nIQVGZjhf__f1CcLRoU,953
|
|
52
52
|
fiddler/schemas/custom_features.py,sha256=U33Rlyt9TxNqEQ2_3aQTgvlIuI2_QPg-6YnQBzJuyoE,5531
|
|
53
53
|
fiddler/schemas/dataset.py,sha256=rGmpMrMfPLSs9d3_sxDTjrKqWp-o-bCe9pf_nlYyHfw,657
|
|
54
54
|
fiddler/schemas/events.py,sha256=dH6SGPBO_rgGZetIhSo4Fq0L1pTU7TEKR7zW7gg3fnI,409
|
|
@@ -78,7 +78,7 @@ fiddler/tests/test_utils.py,sha256=AF6y7UfboEkCa-nNw0aF30SdHC7yW2Hh78J4H_4s78k,3
|
|
|
78
78
|
fiddler/tests/utils.py,sha256=FEDbASW4pYZktdEK2FLnIDyZCwBzpb60m6EzkvOkwSc,335
|
|
79
79
|
fiddler/tests/apis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
80
80
|
fiddler/tests/apis/test_alert_record.py,sha256=_0EBRDn87TqTiO1KYEHKQQVtVZjj4lYjbt8Ix-RRTSg,3118
|
|
81
|
-
fiddler/tests/apis/test_alert_rule.py,sha256=
|
|
81
|
+
fiddler/tests/apis/test_alert_rule.py,sha256=ax4-ravAzwbYbKay_Z8mksamTBaxlwZydFCD3EhLmfc,13196
|
|
82
82
|
fiddler/tests/apis/test_baseline.py,sha256=R_vvbZdSenpYMSydurENJrE_uNWE3sJR9sHJksfU_Jk,7494
|
|
83
83
|
fiddler/tests/apis/test_custom_metric.py,sha256=QlNa80M9PxqaqzW-3nCtH5kKLRHYu2iqFDautBusbos,8208
|
|
84
84
|
fiddler/tests/apis/test_dataset.py,sha256=1Xc9Ng91fLMc2C6dpKfse42fQ0anza3ZOKOD1TqCvTQ,4636
|
|
@@ -87,13 +87,13 @@ fiddler/tests/apis/test_files.py,sha256=-BisPck7bHMSW3N40ffdQJe027Qe3079tjTlU2OI
|
|
|
87
87
|
fiddler/tests/apis/test_generate_model.py,sha256=J8foDsZIUoHJyzLODQM57jw8OFVD81jc67RIubq-CVs,5089
|
|
88
88
|
fiddler/tests/apis/test_job.py,sha256=GlJXhxK60q9u5QJ6ToLrw4y1G3M0DzAAR2u-JDbk3D0,1891
|
|
89
89
|
fiddler/tests/apis/test_mixin.py,sha256=RhA2pY1MXrqxNQG8ujVLvTMBT4iZBwa6Y3Nk0TRfI1o,2517
|
|
90
|
-
fiddler/tests/apis/test_model.py,sha256=
|
|
90
|
+
fiddler/tests/apis/test_model.py,sha256=fpE8bytrlZPUjlwXDSl2NBU9NN_bl9dkmImHXSv0pmc,18779
|
|
91
91
|
fiddler/tests/apis/test_model_artifact.py,sha256=oQVasstDc41EesAzN39wCTZi7KtpeQdVcGcLxERjmw0,5635
|
|
92
|
-
fiddler/tests/apis/test_model_deployment.py,sha256=
|
|
92
|
+
fiddler/tests/apis/test_model_deployment.py,sha256=GBHJ3-aMFn5JBZq6COZHhCYmqdkrrRlBESP4TJWyJng,3350
|
|
93
93
|
fiddler/tests/apis/test_model_surrogate.py,sha256=h3OPgD1-_n6gTd0YH_hDcBBRjBfXv1xUsZxNWUU69KQ,4443
|
|
94
94
|
fiddler/tests/apis/test_project.py,sha256=IEYT3imD4dZJg-OT888G4jQJ_AxxHN5_VkCHcmwUnGA,6015
|
|
95
95
|
fiddler/tests/apis/test_segment.py,sha256=hQiXpZEKLJrnlRBLLMsP3jph8H8K7AEUw17sMK6BuF8,7804
|
|
96
|
-
fiddler/tests/apis/test_webhook.py,sha256=
|
|
96
|
+
fiddler/tests/apis/test_webhook.py,sha256=Tf6bggAFeUA0a9TKTjJ34dM_UM_l6_Q-5N9utyuieeE,8099
|
|
97
97
|
fiddler/tests/apis/test_xai.py,sha256=mV-bcu6Tkw_wM4nCc9Uf6OXU38Hz0gX3eiVMCLGAsbQ,28590
|
|
98
98
|
fiddler/utils/__init__.py,sha256=ooAJR6_tVaF6UKZriz5ynZPj8IeCzaDIPKV23F-e4Fk,53
|
|
99
99
|
fiddler/utils/helpers.py,sha256=Xl_T_se9SOeMTmVJAOVzzmkekUsmpFMlAb2KvPCWfTw,2908
|
|
@@ -101,8 +101,8 @@ fiddler/utils/logger.py,sha256=FdJ3LkS9dbRjWsw5oJmNNsd7q0XRVEIvx5-TWys7L0k,669
|
|
|
101
101
|
fiddler/utils/model_generator.py,sha256=TwQMQDpy-0gcq6HyL68s37N-sk_nGPq33mmppK6i7hI,2203
|
|
102
102
|
fiddler/utils/validations.py,sha256=i8NtgrpCsitq6BPa5lygpKDh07oaOXu7PAlCIcMvqzY,408
|
|
103
103
|
fiddler/utils/version.py,sha256=iC8Ry7UFl9EJW_xU1WbzO_l4yK6V7eQ6U5exB3xT864,531
|
|
104
|
-
fiddler_client-3.1.0.
|
|
105
|
-
fiddler_client-3.1.0.
|
|
106
|
-
fiddler_client-3.1.0.
|
|
107
|
-
fiddler_client-3.1.0.
|
|
108
|
-
fiddler_client-3.1.0.
|
|
104
|
+
fiddler_client-3.1.0.dev4.dist-info/LICENSE.txt,sha256=w8-LUAb_VLBWSsCNmih0pAqLIicJfGu8OJXpDjkIg_o,559
|
|
105
|
+
fiddler_client-3.1.0.dev4.dist-info/METADATA,sha256=QHGMbBeeNbvV5D8mnAz0pedtQn2LgfNUY9ZaHER-spQ,1597
|
|
106
|
+
fiddler_client-3.1.0.dev4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
107
|
+
fiddler_client-3.1.0.dev4.dist-info/top_level.txt,sha256=ndC6LqvKmrTTs8czRlBOYdYgSqZbHuMf0zHt_HWpzzk,8
|
|
108
|
+
fiddler_client-3.1.0.dev4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|